diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/convert/QueryByExamplePredicateBuilder.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/convert/QueryByExamplePredicateBuilder.java index 6e45701f9..fc8732ec5 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/convert/QueryByExamplePredicateBuilder.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/convert/QueryByExamplePredicateBuilder.java @@ -15,12 +15,6 @@ */ package org.springframework.data.jpa.convert; -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; - import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.Expression; import jakarta.persistence.criteria.From; @@ -32,6 +26,12 @@ import jakarta.persistence.metamodel.Attribute.PersistentAttributeType; import jakarta.persistence.metamodel.ManagedType; import jakarta.persistence.metamodel.SingularAttribute; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.domain.Example; import org.springframework.data.domain.ExampleMatcher; @@ -76,8 +76,9 @@ public class QueryByExamplePredicateBuilder { * @param root must not be {@literal null}. * @param cb must not be {@literal null}. * @param example must not be {@literal null}. - * @return never {@literal null}. + * @return {@literal null} indicates no {@link Predicate}. */ + @Nullable public static Predicate getPredicate(Root root, CriteriaBuilder cb, Example example) { return getPredicate(root, cb, example, EscapeCharacter.DEFAULT); } @@ -89,8 +90,9 @@ public class QueryByExamplePredicateBuilder { * @param cb must not be {@literal null}. * @param example must not be {@literal null}. * @param escapeCharacter Must not be {@literal null}. - * @return never {@literal null}. + * @return {@literal null} indicates no constraints */ + @Nullable public static Predicate getPredicate(Root root, CriteriaBuilder cb, Example example, EscapeCharacter escapeCharacter) { @@ -105,7 +107,7 @@ public class QueryByExamplePredicateBuilder { escapeCharacter); if (predicates.isEmpty()) { - return cb.isTrue(cb.literal(true)); + return null; } if (predicates.size() == 1) { diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/convert/QueryByExamplePredicateBuilderUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/convert/QueryByExamplePredicateBuilderUnitTests.java index 459604cb8..1d9fec410 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/convert/QueryByExamplePredicateBuilderUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/convert/QueryByExamplePredicateBuilderUnitTests.java @@ -20,10 +20,6 @@ import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import static org.springframework.data.domain.Example.*; -import java.lang.reflect.Member; -import java.util.LinkedHashSet; -import java.util.Set; - import jakarta.persistence.Id; import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.Expression; @@ -37,6 +33,10 @@ import jakarta.persistence.metamodel.ManagedType; import jakarta.persistence.metamodel.SingularAttribute; import jakarta.persistence.metamodel.Type; +import java.lang.reflect.Member; +import java.util.LinkedHashSet; +import java.util.Set; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -45,7 +45,6 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; - import org.springframework.data.domain.Example; import org.springframework.data.domain.ExampleMatcher; import org.springframework.data.domain.ExampleMatcher.GenericPropertyMatcher; @@ -90,19 +89,16 @@ class QueryByExamplePredicateBuilderUnitTests { void setUp() { personIdAttribute = new SingularAttributeStub<>("id", PersistentAttributeType.BASIC, Long.class); - personFirstnameAttribute = new SingularAttributeStub<>("firstname", PersistentAttributeType.BASIC, - String.class); + personFirstnameAttribute = new SingularAttributeStub<>("firstname", PersistentAttributeType.BASIC, String.class); personAgeAttribute = new SingularAttributeStub<>("age", PersistentAttributeType.BASIC, Long.class); - personFatherAttribute = new SingularAttributeStub<>("father", PersistentAttributeType.MANY_TO_ONE, - Person.class, personEntityType); - personSkillAttribute = new SingularAttributeStub<>("skill", PersistentAttributeType.EMBEDDED, - Skill.class, skillEntityType); - personAddressAttribute = new SingularAttributeStub<>("address", PersistentAttributeType.EMBEDDED, - Address.class); - skillNameAttribute = new SingularAttributeStub<>("name", PersistentAttributeType.BASIC, - String.class); - skillNestedAttribute = new SingularAttributeStub<>("nested", PersistentAttributeType.MANY_TO_ONE, - Skill.class, skillEntityType); + personFatherAttribute = new SingularAttributeStub<>("father", PersistentAttributeType.MANY_TO_ONE, Person.class, + personEntityType); + personSkillAttribute = new SingularAttributeStub<>("skill", PersistentAttributeType.EMBEDDED, Skill.class, + skillEntityType); + personAddressAttribute = new SingularAttributeStub<>("address", PersistentAttributeType.EMBEDDED, Address.class); + skillNameAttribute = new SingularAttributeStub<>("name", PersistentAttributeType.BASIC, String.class); + skillNestedAttribute = new SingularAttributeStub<>("nested", PersistentAttributeType.MANY_TO_ONE, Skill.class, + skillEntityType); personEntityAttribtues = new LinkedHashSet<>(); personEntityAttribtues.add(personIdAttribute); @@ -153,9 +149,9 @@ class QueryByExamplePredicateBuilderUnitTests { } @Test // DATAJPA-218 - void emptyCriteriaListShouldResultTruePredicate() { + void emptyCriteriaListShouldResultInNullPredicate() { assertThat(QueryByExamplePredicateBuilder.getPredicate(root, cb, of(new Person()), EscapeCharacter.DEFAULT)) - .isEqualTo(truePredicate); + .isNull(); } @Test // DATAJPA-218 @@ -306,13 +302,11 @@ class QueryByExamplePredicateBuilderUnitTests { private Class javaType; private Type type; - SingularAttributeStub(String name, - jakarta.persistence.metamodel.Attribute.PersistentAttributeType attributeType, Class javaType) { + SingularAttributeStub(String name, PersistentAttributeType attributeType, Class javaType) { this(name, attributeType, javaType, null); } - SingularAttributeStub(String name, - jakarta.persistence.metamodel.Attribute.PersistentAttributeType attributeType, Class javaType, Type type) { + SingularAttributeStub(String name, PersistentAttributeType attributeType, Class javaType, Type type) { this.name = name; this.attributeType = attributeType; this.javaType = javaType; @@ -325,7 +319,7 @@ class QueryByExamplePredicateBuilderUnitTests { } @Override - public jakarta.persistence.metamodel.Attribute.PersistentAttributeType getPersistentAttributeType() { + public PersistentAttributeType getPersistentAttributeType() { return attributeType; } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/QueryByExampleEclipseLinkIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/QueryByExampleEclipseLinkIntegrationTests.java new file mode 100644 index 000000000..0016bc852 --- /dev/null +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/QueryByExampleEclipseLinkIntegrationTests.java @@ -0,0 +1,86 @@ +/* + * Copyright 2008-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository; + +import static org.assertj.core.api.Assertions.*; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Example; +import org.springframework.data.jpa.convert.QueryByExamplePredicateBuilder; +import org.springframework.data.jpa.domain.sample.Role; +import org.springframework.data.jpa.repository.sample.RoleRepository; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.transaction.annotation.Transactional; + +/** + * @author Greg Turnquist + * @since 3.0 + */ +@ExtendWith(SpringExtension.class) +@ContextConfiguration({ "classpath:eclipselink.xml", "classpath:config/namespace-application-context.xml" }) +@Transactional +public class QueryByExampleEclipseLinkIntegrationTests { + + @Autowired RoleRepository repository; + @Autowired EntityManager em; + + private Role drummer; + private Role guitarist; + private Role singer; + + @BeforeEach + void setUp() { + + drummer = repository.save(new Role("drummer")); + guitarist = repository.save(new Role("guitarist")); + singer = repository.save(new Role("singer")); + } + + @AfterEach + void clearUp() { + repository.deleteAll(); + } + + @Test // GH-2283 + void queryByExampleWithNoPredicatesShouldHaveNoWhereClause() { + + // given + Role probe = new Role(); + Example example = Example.of(probe); + + CriteriaBuilder builder = em.getCriteriaBuilder(); + CriteriaQuery query = builder.createQuery(Role.class); + Root root = query.from(Role.class); + + // when + Predicate predicate = QueryByExamplePredicateBuilder.getPredicate(root, builder, example); + + // then + assertThat(predicate).isNull(); + assertThat(repository.findAll(example)).containsExactlyInAnyOrder(drummer, guitarist, singer); + } +} diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/QueryByExampleHibernateIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/QueryByExampleHibernateIntegrationTests.java new file mode 100644 index 000000000..510cf4abb --- /dev/null +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/QueryByExampleHibernateIntegrationTests.java @@ -0,0 +1,86 @@ +/* + * Copyright 2008-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository; + +import static org.assertj.core.api.Assertions.*; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Example; +import org.springframework.data.jpa.convert.QueryByExamplePredicateBuilder; +import org.springframework.data.jpa.domain.sample.Role; +import org.springframework.data.jpa.repository.sample.RoleRepository; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.transaction.annotation.Transactional; + +/** + * @author Greg Turnquist + * @since 3.0 + */ +@ExtendWith(SpringExtension.class) +@ContextConfiguration({ "classpath:hibernate.xml", "classpath:config/namespace-application-context.xml" }) +@Transactional +public class QueryByExampleHibernateIntegrationTests { + + @Autowired RoleRepository repository; + @Autowired EntityManager em; + + private Role drummer; + private Role guitarist; + private Role singer; + + @BeforeEach + void setUp() { + + drummer = repository.save(new Role("drummer")); + guitarist = repository.save(new Role("guitarist")); + singer = repository.save(new Role("singer")); + } + + @AfterEach + void clearUp() { + repository.deleteAll(); + } + + @Test // GH-2283 + void queryByExampleWithNoPredicatesShouldHaveNoWhereClause() { + + // given + Role probe = new Role(); + Example example = Example.of(probe); + + CriteriaBuilder builder = em.getCriteriaBuilder(); + CriteriaQuery query = builder.createQuery(Role.class); + Root root = query.from(Role.class); + + // when + Predicate predicate = QueryByExamplePredicateBuilder.getPredicate(root, builder, example); + + // then + assertThat(predicate).isNull(); + assertThat(repository.findAll(example)).containsExactlyInAnyOrder(drummer, guitarist, singer); + } +} diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/RoleRepository.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/RoleRepository.java index e166589fb..d16181111 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/RoleRepository.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/RoleRepository.java @@ -18,13 +18,14 @@ package org.springframework.data.jpa.repository.sample; import jakarta.persistence.LockModeType; import jakarta.persistence.QueryHint; +import java.util.List; import java.util.Optional; import org.springframework.data.jpa.domain.sample.Role; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Lock; import org.springframework.data.jpa.repository.QueryHints; import org.springframework.data.querydsl.QuerydslPredicateExecutor; -import org.springframework.data.repository.CrudRepository; import com.querydsl.core.types.Predicate; @@ -35,12 +36,12 @@ import com.querydsl.core.types.Predicate; * @author Thomas Darimont * @author Yanming Zhou */ -public interface RoleRepository extends CrudRepository, QuerydslPredicateExecutor { +public interface RoleRepository extends JpaRepository, QuerydslPredicateExecutor { @Override @Lock(LockModeType.READ) @QueryHints(@QueryHint(name = "foo", value = "bar")) - Iterable findAll(); + List findAll(); @Override @Lock(LockModeType.READ)