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.