Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

spring data jpa - Custom type conversion in interface-based projection

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());
    }
}
like image 697
ki77y5tyl3 Avatar asked Oct 16 '19 15:10

ki77y5tyl3


2 Answers

The ConversionService used is DefaultConversionService.getSharedInstance().

So you should be able to access that and add your converter.

like image 168
Jens Schauder Avatar answered Nov 15 '22 05:11

Jens Schauder


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").

like image 44
MikaelF Avatar answered Nov 15 '22 03:11

MikaelF