I'm trying to implement Interface-based Projection but I cannot make it work with my custom type column.
Below example of what I'm trying to do:
Repository:
@Query(value = "SELECT customType from TABLE", nativeQuery = true)
List<TestClass> getResults();
Interface projection:
public interface TestClass {
@Convert(converter = MyCustomTypeConverter.class)
MyCustomType getCustomType();
}
Converter:
@Converter
public class MyCustomTypeConverter implements Converter<String, MyCustomType> {
@Override
public MyCustomType convert(String source) {
// whatever
}
}
When I call getResults() on repository I receive list of results as expected, but when I try to call getCustomType() on one of results I get exception:
java.lang.IllegalArgumentException: Projection type must be an interface!
at org.springframework.util.Assert.isTrue(Assert.java:118)
at org.springframework.data.projection.ProxyProjectionFactory.createProjection(ProxyProjectionFactory.java:100)
at org.springframework.data.projection.SpelAwareProxyProjectionFactory.createProjection(SpelAwareProxyProjectionFactory.java:45)
at org.springframework.data.projection.ProjectingMethodInterceptor.getProjection(ProjectingMethodInterceptor.java:131)
at org.springframework.data.projection.ProjectingMethodInterceptor.invoke(ProjectingMethodInterceptor.java:80)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.projection.ProxyProjectionFactory$TargetAwareMethodInterceptor.invoke(ProxyProjectionFactory.java:245)
I found that problem lies in
org.springframework.data.projection.ProxyProjectionFactory
which uses
org.springframework.core.convert.support.DefaultConversionService
which obviously doesn't have my custom type converter registered.
If I stop on breakpoint in ConversionService and manually add my converter in runtime, projection will work without any problem.
So question is: can I somehow register my custom converter to ConversionService used by spring jpa during interface-based projection?
EDIT:
I added my converter to DefaultConversionService's sharedInstance in InitializingBean like below and it worked.
@Component
public class DefaultConversionServiceInitializer implements InitializingBean {
@Override
public void afterPropertiesSet() {
DefaultConversionService conversionService = (DefaultConversionService) DefaultConversionService.getSharedInstance();
conversionService.addConverter(new MyCustomTypeConverter());
}
}
The ConversionService
used is DefaultConversionService.getSharedInstance()
.
So you should be able to access that and add your converter.
This broke again in Spring Data Jpa 2.4.0 (see this closed GitHub issue).
One workaround, as suggested by user ajobra76 (link), is to specify the converter to use like so:
public interface TestClass {
@Value("#{@myCustomTypeConverter.convert(target.customType)}")
MyCustomType getCustomType();
}
Where myCustomTypeConverter
would be a bean in the ApplicationContext
. Of course there are other ways of specifying a method using SpEL, including creating a new
object, and calling a static method. But this is just to show that it can be done.
target
is an identifier that will be bound to the "aggregate root backing the projection" (Spring Data Jpa docs, section "An Open Projection").
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With