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 9b7cfb818..6b2314a2d 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 @@ -36,6 +36,7 @@ import java.util.Set; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.domain.Example; import org.springframework.data.domain.ExampleMatcher; +import org.springframework.data.domain.ExampleMatcher.MatchMode; import org.springframework.data.domain.ExampleMatcher.PropertyValueTransformer; import org.springframework.data.jpa.repository.query.EscapeCharacter; import org.springframework.data.support.ExampleMatcherAccessor; @@ -105,9 +106,8 @@ public class QueryByExamplePredicateBuilder { ExampleMatcher matcher = example.getMatcher(); List predicates = getPredicates("", cb, root, root.getModel(), example.getProbe(), - example.getProbeType(), matcher, new ExampleMatcherAccessor(matcher), - new PathNode("root", null, example.getProbe()), - escapeCharacter); + example.getProbeType(), matcher.getMatchMode(), new ExampleMatcherAccessor(matcher), + new PathNode("root", null, example.getProbe()), escapeCharacter); if (predicates.isEmpty()) { return null; @@ -124,7 +124,7 @@ public class QueryByExamplePredicateBuilder { @SuppressWarnings({ "rawtypes", "unchecked" }) static List getPredicates(String path, CriteriaBuilder cb, Path from, ManagedType type, Object value, - Class probeType, ExampleMatcher matcher, ExampleMatcherAccessor exampleAccessor, PathNode currentNode, + Class probeType, MatchMode matchMode, ExampleMatcherAccessor exampleAccessor, PathNode currentNode, EscapeCharacter escapeCharacter) { List predicates = new ArrayList<>(); @@ -161,7 +161,7 @@ public class QueryByExamplePredicateBuilder { predicates .addAll(getPredicates(currentPath, cb, from.get(attribute.getName()), (ManagedType) attribute.getType(), - attributeValue, probeType, matcher, exampleAccessor, currentNode, escapeCharacter)); + attributeValue, probeType, matchMode, exampleAccessor, currentNode, escapeCharacter)); continue; } @@ -174,9 +174,9 @@ public class QueryByExamplePredicateBuilder { ClassUtils.getShortName(probeType), node)); } - JoinType joinType = matcher.isAllMatching() ? JoinType.INNER : JoinType.LEFT; + JoinType joinType = matchMode.equals(MatchMode.ALL) ? JoinType.INNER : JoinType.LEFT; predicates.addAll(getPredicates(currentPath, cb, ((From) from).join(attribute.getName(), joinType), - (ManagedType) attribute.getType(), attributeValue, probeType, matcher, exampleAccessor, node, + (ManagedType) attribute.getType(), attributeValue, probeType, matchMode, exampleAccessor, node, escapeCharacter)); continue; 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 0b80e764e..544644ef8 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 @@ -277,7 +277,7 @@ class QueryByExamplePredicateBuilderUnitTests { verify(cb, times(1)).like(any(Expression.class), eq("%f\\\\o\\_o"), eq('\\')); } - @ParameterizedTest(name = "Matching {0} on association should join using JoinType.{1} ") // DATAJPA-3763 + @ParameterizedTest(name = "Matching {0} on association should join using JoinType.{1} ") // GH-3763 @CsvSource({ "ALL, INNER", "ANY, LEFT" }) void matchingAssociationShouldUseTheCorrectJoinType(MatchMode matchMode, JoinType expectedJoinType) { diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/QueryByExampleIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/QueryByExampleIntegrationTests.java index 458954f3e..fa9de5e26 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/QueryByExampleIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/QueryByExampleIntegrationTests.java @@ -15,7 +15,7 @@ */ package org.springframework.data.jpa.repository; -import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; import jakarta.persistence.EntityManager; import jakarta.persistence.criteria.CriteriaBuilder; @@ -23,21 +23,27 @@ import jakarta.persistence.criteria.CriteriaQuery; import jakarta.persistence.criteria.Predicate; import jakarta.persistence.criteria.Root; +import java.util.List; + 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.domain.ExampleMatcher; import org.springframework.data.jpa.convert.QueryByExamplePredicateBuilder; import org.springframework.data.jpa.domain.sample.Role; +import org.springframework.data.jpa.domain.sample.User; import org.springframework.data.jpa.repository.sample.RoleRepository; +import org.springframework.data.jpa.repository.sample.UserRepository; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.transaction.annotation.Transactional; /** * @author Greg Turnquist + * @author Christoph Strobl * @since 3.0 */ @ExtendWith(SpringExtension.class) @@ -45,7 +51,8 @@ import org.springframework.transaction.annotation.Transactional; @Transactional class QueryByExampleIntegrationTests { - @Autowired RoleRepository repository; + @Autowired RoleRepository roleRepository; + @Autowired UserRepository userRepository; @Autowired EntityManager em; private Role drummer; @@ -55,14 +62,14 @@ class QueryByExampleIntegrationTests { @BeforeEach void setUp() { - drummer = repository.save(new Role("drummer")); - guitarist = repository.save(new Role("guitarist")); - singer = repository.save(new Role("singer")); + drummer = roleRepository.save(new Role("drummer")); + guitarist = roleRepository.save(new Role("guitarist")); + singer = roleRepository.save(new Role("singer")); } @AfterEach void clearUp() { - repository.deleteAll(); + roleRepository.deleteAll(); } @Test // GH-2283 @@ -81,6 +88,39 @@ class QueryByExampleIntegrationTests { // then assertThat(predicate).isNull(); - assertThat(repository.findAll(example)).containsExactlyInAnyOrder(drummer, guitarist, singer); + assertThat(roleRepository.findAll(example)).containsExactlyInAnyOrder(drummer, guitarist, singer); + } + + @Test // GH-3763 + void usesAnyMatchOnJoins() { + + User manager = new User("mighty", "super user", "msu@u.io"); + + userRepository.save(manager); + + User dave = new User(); + dave.setFirstname("dave"); + dave.setLastname("matthews"); + dave.setEmailAddress("d@dmb.com"); + dave.addRole(singer); + + User carter = new User(); + carter.setFirstname("carter"); + carter.setLastname("beaufort"); + carter.setEmailAddress("c@dmb.com"); + carter.addRole(drummer); + carter.addRole(singer); + carter.setManager(manager); + + userRepository.saveAllAndFlush(List.of(dave, carter)); + + User probe = new User(); + probe.setLastname(dave.getLastname()); + probe.setManager(manager); + + Example example = Example.of(probe, + ExampleMatcher.matchingAny().withIgnorePaths("id", "createdAt", "age", "active", "emailAddress", + "secondaryEmailAddress", "colleagues", "address", "binaryData", "attributes", "dateOfBirth")); + assertThat(userRepository.findAll(example)).containsExactly(dave, carter); } }