-
Notifications
You must be signed in to change notification settings - Fork 364
Description
Description
When using the fluent query API with findBy()
, the .project()
method appears to be ignored when combined with .as()
for class-based projections. The query executes successfully but fetches all columns from the database instead of only the projected columns, leading to unnecessary data transfer and potential performance issues.
Moreover it fails at converting to the Projection class (this does not happen if using a projection interface)
My setup
Entity
@Data
@NoArgsConstructor
@Table(name = "users")
public class User {
@Id
private Long id;
private String username;
private String password;
private String email;
}
Projection class
public record UserPreview(String username) {
}
Repository usage
User user = new User();
user.setUsername("admin");
Example<User> example = Example.of(user);
var result = userRepository.findBy(example, q -> q
.as(UserPreview.class)
.project("username")
.page(Pageable.unpaged()));
Error/Issue
The above code produce sql similar to
/*SQL l:167 #:1*/SELECT \"users\".\"ID\" AS \"ID\", \"users\".\"EMAIL\" AS \"EMAIL\", \"users\".\"PASSWORD\" AS \"PASSWORD\", \"users\".\"USERNAME\" AS \"USERNAME\" FROM \"users\" WHERE (\"users\".\"USERNAME\" = ?) {1: 'admin'};
and later crashes with stack trace as
org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [it.variabilealeatoria.springdata.demo.User] to type [it.variabilealeatoria.springdata.demo.UserPreview]
at org.springframework.core.convert.support.GenericConversionService.handleConverterNotFound(GenericConversionService.java:294)
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:185)
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:165)
at org.springframework.data.jdbc.repository.support.FluentQuerySupport.lambda$getConversionFunction$1(FluentQuerySupport.java:125)
at org.springframework.data.jdbc.repository.support.FetchableFluentQueryByExample.lambda$page$2(FetchableFluentQueryByExample.java:125)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:215)
at java.base/java.util.ArrayList$Itr.forEachRemaining(ArrayList.java:1086)
at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1939)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:570)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:560)
at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:265)
at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:727)
at org.springframework.data.domain.Chunk.getConvertedContent(Chunk.java:131)
at org.springframework.data.domain.PageImpl.map(PageImpl.java:86)
at org.springframework.data.jdbc.repository.support.FetchableFluentQueryByExample.page(FetchableFluentQueryByExample.java:125)
at it.variabilealeatoria.springdata.demo.DemoApplicationTests.lambda$0(DemoApplicationTests.java:29)
at org.springframework.data.jdbc.repository.support.SimpleJdbcRepository.findBy(SimpleJdbcRepository.java:202)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:360)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new$0(RepositoryMethodInvoker.java:277)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:170)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:158)
at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:515)
at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:284)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:734)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:174)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:149)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:138)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:223)
at jdk.proxy2/jdk.proxy2.$Proxy80.findBy(Unknown Source)
at it.variabilealeatoria.springdata.demo.DemoApplicationTests.userRepositoryTest(DemoApplicationTests.java:26)
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1604)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1604)
Expected behaviour
If the Fluent Api of Query by Example is not specific to spring data JPA it should work also for spring data jdbc.
The same code, switching from spring-data-jdbc to spring-data-jpa works as a charm
The updated User class
@Data
@NoArgsConstructor
@Table(name = "users")
@Entity
public class User {
@Id
private Long id;
private String username;
private String password;
private String email;
}
Everyhing else is unmodified
The executed sql is
/*SQL l:58 #:1*/select u1_0.username from users u1_0 where u1_0.username=? {1: 'admin'};
Why is .project
ignored in spring-data-jdbc implementation?