8

I'm trying to use a JPA Projection (Spring v1.5.9.RELEASE) to return a custom type from a JpaRepository as follows (MCVE):

@Repository
public interface TestRepository extends JpaRepository<Void, Void> {

    public interface TestValue {
        UUID getId();
        int getNum();
    }

    @Query(value = "SELECT :id as id, 1 as num", nativeQuery = true)
    List<TestValue> getTestValues(@Param("id") UUID id);
    
}

I would like to use the repository as follows:

List<TestValue> testValues = testRepository.getTestValues(UUID.randomUUID());
int num = testValues.get(0).getNum(); // Returns 1 as expected
UUID id = testValues.get(0).getId(); // ***Fails with an exception***

However, calling getId() fails with the following exception:

java.lang.IllegalArgumentException: Projection type must be an interface!
    at org.springframework.util.Assert.isTrue(Assert.java:92) ~[spring-core-4.3.13.RELEASE.jar:4.3.13.RELEASE]
    at org.springframework.data.projection.ProxyProjectionFactory.createProjection(ProxyProjectionFactory.java:110) ~[spring-data-commons-1.13.9.RELEASE.jar:?]
    at org.springframework.data.projection.SpelAwareProxyProjectionFactory.createProjection(SpelAwareProxyProjectionFactory.java:42) ~[spring-data-commons-1.13.9.RELEASE.jar:?]
    at org.springframework.data.projection.ProjectingMethodInterceptor.getProjection(ProjectingMethodInterceptor.java:126) ~[spring-data-commons-1.13.9.RELEASE.jar:?]
    at org.springframework.data.projection.ProjectingMethodInterceptor.invoke(ProjectingMethodInterceptor.java:76) ~[spring-data-commons-1.13.9.RELEASE.jar:?]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.13.RELEASE.jar:4.3.13.RELEASE]
    at org.springframework.data.projection.ProxyProjectionFactory$TargetAwareMethodInterceptor.invoke(ProxyProjectionFactory.java:264) ~[spring-data-commons-1.13.9.RELEASE.jar:?]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.13.RELEASE.jar:4.3.13.RELEASE]
    at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:56) ~[spring-data-commons-1.13.9.RELEASE.jar:?]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.13.RELEASE.jar:4.3.13.RELEASE]
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213) ~[spring-aop-4.3.13.RELEASE.jar:4.3.13.RELEASE]
    at com.sun.proxy.$Proxy199.getId(Unknown Source) ~[?:?]
    at ***my.company***

The returned TestValue proxy object stores the id attribute as a raw byte array. I would expect that the ProjectingMethodInterceptor eventually performs the actual type conversion to a UUID instance. Looking at the source code (ProjectingMethodInterceptor.invoke(..):74) it seems that it uses a ConversionService to do conversions. I tried the following:

@Configuration  
public class MyApplicationConfig {

    @Bean
    public ConversionService conversionService() {
        DefaultConversionService service = new DefaultConversionService();
        service.addConverter(new UUIDConverter());
        return service;
    }
}

The class UUIDConverter implements the interface org.springframework.core.convert.converter.Converter<byte[], UUID>. Unfortunately, this approach does not work as the used ProjectingMethodInterceptor is created via the ProxyProjectingFactory which bypasses the configured ConversionService and instantiates a DefaultConversionService (ProxyProjectionFactory.ctor():65).

I also tried to add the annotation @Convert(converter = UUIDConverter.class) to the getId() declaration in TestValue, however, this approach does not work either.

I obviously could change the return type of the getId() method to byte[] and perform the actual conversion to UUID in my application code, but this approach does not feel to be the right way to do it.

Question 1: How are custom type conversions defined in JPA Interface Projections?

Question 2: If this is not possible, what is the "Spring-intended" way to implement the above repository method with that exact method signature and underlying query.

Community
  • 1
  • 1
trylimits
  • 2,575
  • 1
  • 22
  • 32
  • I found a working solution here (in the question itself, not in the accepted answer): https://stackoverflow.com/questions/58417190/spring-data-jpa-custom-type-conversion-in-interface-based-projection – AJPerez Dec 05 '19 at 09:29

0 Answers0