From 78abfcdb9633d7d41b0e6606a424844cc846b244 Mon Sep 17 00:00:00 2001 From: ArnaudLec Date: Fri, 28 Feb 2025 18:17:53 +0100 Subject: [PATCH] Fix JoinType on associations when using MatchMode.ANY Signed-off-by: Arnaud Lecointre --- .../QueryByExamplePredicateBuilder.java | 15 ++++++++----- ...eryByExamplePredicateBuilderUnitTests.java | 21 +++++++++++++++++++ 2 files changed, 31 insertions(+), 5 deletions(-) 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 993f42f3b2..9b7cfb818a 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 @@ -18,6 +18,7 @@ import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.Expression; import jakarta.persistence.criteria.From; +import jakarta.persistence.criteria.JoinType; import jakarta.persistence.criteria.Path; import jakarta.persistence.criteria.Predicate; import jakarta.persistence.criteria.Root; @@ -57,6 +58,7 @@ * @author Oliver Gierke * @author Jens Schauder * @author Greg Turnquist + * @author Arnaud Lecointre * @since 1.10 */ public class QueryByExamplePredicateBuilder { @@ -103,7 +105,8 @@ public static Predicate getPredicate(Root root, CriteriaBuilder cb, Examp ExampleMatcher matcher = example.getMatcher(); List predicates = getPredicates("", cb, root, root.getModel(), example.getProbe(), - example.getProbeType(), new ExampleMatcherAccessor(matcher), new PathNode("root", null, example.getProbe()), + example.getProbeType(), matcher, new ExampleMatcherAccessor(matcher), + new PathNode("root", null, example.getProbe()), escapeCharacter); if (predicates.isEmpty()) { @@ -121,7 +124,7 @@ public static Predicate getPredicate(Root root, CriteriaBuilder cb, Examp @SuppressWarnings({ "rawtypes", "unchecked" }) static List getPredicates(String path, CriteriaBuilder cb, Path from, ManagedType type, Object value, - Class probeType, ExampleMatcherAccessor exampleAccessor, PathNode currentNode, + Class probeType, ExampleMatcher matcher, ExampleMatcherAccessor exampleAccessor, PathNode currentNode, EscapeCharacter escapeCharacter) { List predicates = new ArrayList<>(); @@ -158,7 +161,7 @@ static List getPredicates(String path, CriteriaBuilder cb, Path fr predicates .addAll(getPredicates(currentPath, cb, from.get(attribute.getName()), (ManagedType) attribute.getType(), - attributeValue, probeType, exampleAccessor, currentNode, escapeCharacter)); + attributeValue, probeType, matcher, exampleAccessor, currentNode, escapeCharacter)); continue; } @@ -171,8 +174,10 @@ static List getPredicates(String path, CriteriaBuilder cb, Path fr ClassUtils.getShortName(probeType), node)); } - predicates.addAll(getPredicates(currentPath, cb, ((From) from).join(attribute.getName()), - (ManagedType) attribute.getType(), attributeValue, probeType, exampleAccessor, node, escapeCharacter)); + JoinType joinType = matcher.isAllMatching() ? JoinType.INNER : JoinType.LEFT; + predicates.addAll(getPredicates(currentPath, cb, ((From) from).join(attribute.getName(), joinType), + (ManagedType) attribute.getType(), attributeValue, probeType, matcher, 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 db43691aa5..86ccc13d7a 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 @@ -16,6 +16,7 @@ package org.springframework.data.jpa.convert; import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import static org.springframework.data.domain.Example.*; @@ -23,6 +24,7 @@ import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.Expression; import jakarta.persistence.criteria.Join; +import jakarta.persistence.criteria.JoinType; import jakarta.persistence.criteria.Path; import jakarta.persistence.criteria.Predicate; import jakarta.persistence.criteria.Root; @@ -39,6 +41,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.mockito.ArgumentMatchers; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -47,6 +51,7 @@ import org.springframework.data.domain.Example; import org.springframework.data.domain.ExampleMatcher; import org.springframework.data.domain.ExampleMatcher.GenericPropertyMatcher; +import org.springframework.data.domain.ExampleMatcher.MatchMode; import org.springframework.data.jpa.repository.query.EscapeCharacter; import org.springframework.util.ObjectUtils; @@ -57,6 +62,7 @@ * @author Mark Paluch * @author Oliver Gierke * @author Jens Schauder + * @author Arnaud Lecointre */ @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) @@ -271,6 +277,21 @@ void likePatternsGetEscapedEnding() { verify(cb, times(1)).like(any(Expression.class), eq("%f\\\\o\\_o"), eq('\\')); } + @ParameterizedTest(name = "Matching {0} on association should use join using JoinType.{1} ") // DATAJPA-3763 + @CsvSource({ "ALL, INNER", "ANY, LEFT" }) + void matchingAssociationShouldUseTheCorrectJoinType(MatchMode matchMode, JoinType expectedJoinType) { + + Person person = new Person(); + person.father = new Person(); + + ExampleMatcher matcher = matchMode == MatchMode.ALL ? ExampleMatcher.matchingAll() : ExampleMatcher.matchingAny(); + Example example = of(person, matcher); + + QueryByExamplePredicateBuilder.getPredicate(root, cb, example, EscapeCharacter.DEFAULT); + + verify(root, times(1)).join("father", expectedJoinType); + } + @SuppressWarnings("unused") static class Person {