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.