spring-projects / spring-data-jpa

Simplifies the development of creating a JPA-based data access layer.
https://spring.io/projects/spring-data-jpa/
Apache License 2.0
3.01k stars 1.42k forks source link

Custom finders with PageRequest (instead of Pageable) method parameter causes IllegalArgumentException #2013

Closed spring-projects-issues closed 2 years ago

spring-projects-issues commented 4 years ago

Mark Paluch opened DATAJPA-1718 and commented

A query using the In keyword accepting an array/collection and a PageRequest causes: IllegalArgumentException: At least 2 parameter(s) provided but only 1 parameter(s) present in query.

 

Repository declaration:

 

interface MyEntityRepository extends JpaRepository<MyEntity, Long> {
    Page<MyEntity> findByStatusIn(List<Integer> status, PageRequest pageRequest);
}

@Entity
@Data
class MyEntity {

    @Id
    Long myId;
    int status;
}

Stack trace:

Caused by: org.springframework.dao.InvalidDataAccessApiUsageException: At least 2 parameter(s) provided but only 1 parameter(s) present in query.; nested exception is java.lang.IllegalArgumentException: At least 2 parameter(s) provided but only 1 parameter(s) present in query.
    at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:374) ~[spring-orm-5.2.5.RELEASE.jar:5.2.5.RELEASE]
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:257) ~[spring-orm-5.2.5.RELEASE.jar:5.2.5.RELEASE]
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:528) ~[spring-orm-5.2.5.RELEASE.jar:5.2.5.RELEASE]
    at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61) ~[spring-tx-5.2.5.RELEASE.jar:5.2.5.RELEASE]
    at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242) ~[spring-tx-5.2.5.RELEASE.jar:5.2.5.RELEASE]
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:153) ~[spring-tx-5.2.5.RELEASE.jar:5.2.5.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.2.5.RELEASE.jar:5.2.5.RELEASE]
    at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:149) ~[spring-data-jpa-2.2.6.RELEASE.jar:2.2.6.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.2.5.RELEASE.jar:5.2.5.RELEASE]
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:95) ~[spring-aop-5.2.5.RELEASE.jar:5.2.5.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.2.5.RELEASE.jar:5.2.5.RELEASE]
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212) ~[spring-aop-5.2.5.RELEASE.jar:5.2.5.RELEASE]
    at com.sun.proxy.$Proxy65.findByStatusIn(Unknown Source) ~[na:na]
    at com.example.demo.DemoApplication.lambda$clr$0(DemoApplication.java:36) [classes/:na]
    at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:784) [spring-boot-2.2.6.RELEASE.jar:2.2.6.RELEASE]
    ... 5 common frames omitted
Caused by: java.lang.IllegalArgumentException: At least 2 parameter(s) provided but only 1 parameter(s) present in query.
    at org.springframework.util.Assert.isTrue(Assert.java:136) ~[spring-core-5.2.5.RELEASE.jar:5.2.5.RELEASE]
    at org.springframework.data.jpa.repository.query.QueryParameterSetterFactory$CriteriaQueryParameterSetterFactory.create(QueryParameterSetterFactory.java:297) ~[spring-data-jpa-2.2.6.RELEASE.jar:2.2.6.RELEASE]
    at org.springframework.data.jpa.repository.query.ParameterBinderFactory.createQueryParameterSetter(ParameterBinderFactory.java:140) ~[spring-data-jpa-2.2.6.RELEASE.jar:2.2.6.RELEASE]
    at org.springframework.data.jpa.repository.query.ParameterBinderFactory.createSetters(ParameterBinderFactory.java:129) ~[spring-data-jpa-2.2.6.RELEASE.jar:2.2.6.RELEASE]
    at org.springframework.data.jpa.repository.query.ParameterBinderFactory.createSetters(ParameterBinderFactory.java:121) ~[spring-data-jpa-2.2.6.RELEASE.jar:2.2.6.RELEASE]
    at org.springframework.data.jpa.repository.query.ParameterBinderFactory.createCriteriaBinder(ParameterBinderFactory.java:72) ~[spring-data-jpa-2.2.6.RELEASE.jar:2.2.6.RELEASE]
    at org.springframework.data.jpa.repository.query.PartTreeJpaQuery$QueryPreparer.getBinder(PartTreeJpaQuery.java:328) ~[spring-data-jpa-2.2.6.RELEASE.jar:2.2.6.RELEASE]
    at org.springframework.data.jpa.repository.query.PartTreeJpaQuery$QueryPreparer.createQuery(PartTreeJpaQuery.java:234) ~[spring-data-jpa-2.2.6.RELEASE.jar:2.2.6.RELEASE]
    at org.springframework.data.jpa.repository.query.PartTreeJpaQuery.doCreateQuery(PartTreeJpaQuery.java:106) ~[spring-data-jpa-2.2.6.RELEASE.jar:2.2.6.RELEASE]
    at org.springframework.data.jpa.repository.query.AbstractJpaQuery.createQuery(AbstractJpaQuery.java:226) ~[spring-data-jpa-2.2.6.RELEASE.jar:2.2.6.RELEASE]
    at org.springframework.data.jpa.repository.query.JpaQueryExecution$PagedExecution.doExecute(JpaQueryExecution.java:175) ~[spring-data-jpa-2.2.6.RELEASE.jar:2.2.6.RELEASE]
    at org.springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:88) ~[spring-data-jpa-2.2.6.RELEASE.jar:2.2.6.RELEASE]
    at org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:154) ~[spring-data-jpa-2.2.6.RELEASE.jar:2.2.6.RELEASE]
    at org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:142) ~[spring-data-jpa-2.2.6.RELEASE.jar:2.2.6.RELEASE]
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:618) ~[spring-data-commons-2.2.6.RELEASE.jar:2.2.6.RELEASE]
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:605) ~[spring-data-commons-2.2.6.RELEASE.jar:2.2.6.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.2.5.RELEASE.jar:5.2.5.RELEASE]
    at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:80) ~[spring-data-commons-2.2.6.RELEASE.jar:2.2.6.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.2.5.RELEASE.jar:5.2.5.RELEASE]
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:366) ~[spring-tx-5.2.5.RELEASE.jar:5.2.5.RELEASE]
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:118) ~[spring-tx-5.2.5.RELEASE.jar:5.2.5.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.2.5.RELEASE.jar:5.2.5.RELEASE]
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139) ~[spring-tx-5.2.5.RELEASE.jar:5.2.5.RELEASE]
    ... 14 common frames omitted

 


Reference URL: https://gitter.im/spring-projects/spring-data?at=5eaab03a7975db7ebfd654a8

gregturn commented 2 years ago

Further details says that this...

Page<MyResult> findByStatusIn(Collection<Integer> status, PageRequest pageRequest);

...fails. But this...

Page<MyResult> findByStatusIn(Collection<Integer> status, Pageable pageRequest);

...works.

Should lead to a suitable test case.

gregturn commented 2 years ago

Confirmed. The first test case below works, the second one fails.

    @Test // GH-2013
    void findByCollectionWithPageable() {

        flushTestUsers();

        Page<User> userPage = repository.findByAgeIn(List.of(28, 35), (Pageable) PageRequest.of(0, 2));

        assertThat(userPage).hasSize(2);
        assertThat(userPage.getTotalElements()).isEqualTo(2);
        assertThat(userPage.getTotalPages()).isEqualTo(1);
        assertThat(userPage.getContent()).containsExactlyInAnyOrder(firstUser, secondUser);
    }

    @Test
    void findByCollectionWithPageRequest() {

        flushTestUsers();

        Page<User> userPage = repository.findByAgeIn(List.of(28, 35), (PageRequest) PageRequest.of(0, 2));

        assertThat(userPage).hasSize(2);
        assertThat(userPage.getTotalElements()).isEqualTo(2);
        assertThat(userPage.getTotalPages()).isEqualTo(1);
        assertThat(userPage.getContent()).containsExactlyInAnyOrder(firstUser, secondUser);
    }
gregturn commented 2 years ago

The problem is based on Spring Data Commons, where Pageable is on a special list, while PageRequest is not.

https://github.com/spring-projects/spring-data-commons/blob/main/src/main/java/org/springframework/data/repository/query/Parameter.java#L60

We need to possibly update the check for these parameters to do an .IsInstance() check.

gregturn commented 2 years ago

Related https://github.com/spring-projects/spring-data-commons/issues/2626

odrotbohm commented 2 years ago

This should be fixed by spring-projects/spring-data-commons#2626.

gregturn commented 2 years ago

Thanks @odrotbohm. I'm going to add a couple test cases using this issue to ensure things are working.