Skip to content

Commit 44c88db

Browse files
ArnaudLecchristophstrobl
authored andcommitted
Consider MatchMode when joining associations for QueryByExample.
This commit makes sure to use different join types depending on the actual match mode. Closes: #3763 Original Pull Request: #3794 Signed-off-by: ArnaudLec <[email protected]>
1 parent 4376c41 commit 44c88db

File tree

2 files changed

+31
-5
lines changed

2 files changed

+31
-5
lines changed

spring-data-jpa/src/main/java/org/springframework/data/jpa/convert/QueryByExamplePredicateBuilder.java

+10-5
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import jakarta.persistence.criteria.CriteriaBuilder;
1919
import jakarta.persistence.criteria.Expression;
2020
import jakarta.persistence.criteria.From;
21+
import jakarta.persistence.criteria.JoinType;
2122
import jakarta.persistence.criteria.Path;
2223
import jakarta.persistence.criteria.Predicate;
2324
import jakarta.persistence.criteria.Root;
@@ -57,6 +58,7 @@
5758
* @author Oliver Gierke
5859
* @author Jens Schauder
5960
* @author Greg Turnquist
61+
* @author Arnaud Lecointre
6062
* @since 1.10
6163
*/
6264
public class QueryByExamplePredicateBuilder {
@@ -103,7 +105,8 @@ public static <T> Predicate getPredicate(Root<T> root, CriteriaBuilder cb, Examp
103105
ExampleMatcher matcher = example.getMatcher();
104106

105107
List<Predicate> predicates = getPredicates("", cb, root, root.getModel(), example.getProbe(),
106-
example.getProbeType(), new ExampleMatcherAccessor(matcher), new PathNode("root", null, example.getProbe()),
108+
example.getProbeType(), matcher, new ExampleMatcherAccessor(matcher),
109+
new PathNode("root", null, example.getProbe()),
107110
escapeCharacter);
108111

109112
if (predicates.isEmpty()) {
@@ -121,7 +124,7 @@ public static <T> Predicate getPredicate(Root<T> root, CriteriaBuilder cb, Examp
121124

122125
@SuppressWarnings({ "rawtypes", "unchecked" })
123126
static List<Predicate> getPredicates(String path, CriteriaBuilder cb, Path<?> from, ManagedType<?> type, Object value,
124-
Class<?> probeType, ExampleMatcherAccessor exampleAccessor, PathNode currentNode,
127+
Class<?> probeType, ExampleMatcher matcher, ExampleMatcherAccessor exampleAccessor, PathNode currentNode,
125128
EscapeCharacter escapeCharacter) {
126129

127130
List<Predicate> predicates = new ArrayList<>();
@@ -158,7 +161,7 @@ static List<Predicate> getPredicates(String path, CriteriaBuilder cb, Path<?> fr
158161

159162
predicates
160163
.addAll(getPredicates(currentPath, cb, from.get(attribute.getName()), (ManagedType<?>) attribute.getType(),
161-
attributeValue, probeType, exampleAccessor, currentNode, escapeCharacter));
164+
attributeValue, probeType, matcher, exampleAccessor, currentNode, escapeCharacter));
162165
continue;
163166
}
164167

@@ -171,8 +174,10 @@ static List<Predicate> getPredicates(String path, CriteriaBuilder cb, Path<?> fr
171174
ClassUtils.getShortName(probeType), node));
172175
}
173176

174-
predicates.addAll(getPredicates(currentPath, cb, ((From<?, ?>) from).join(attribute.getName()),
175-
(ManagedType<?>) attribute.getType(), attributeValue, probeType, exampleAccessor, node, escapeCharacter));
177+
JoinType joinType = matcher.isAllMatching() ? JoinType.INNER : JoinType.LEFT;
178+
predicates.addAll(getPredicates(currentPath, cb, ((From<?, ?>) from).join(attribute.getName(), joinType),
179+
(ManagedType<?>) attribute.getType(), attributeValue, probeType, matcher, exampleAccessor, node,
180+
escapeCharacter));
176181

177182
continue;
178183
}

spring-data-jpa/src/test/java/org/springframework/data/jpa/convert/QueryByExamplePredicateBuilderUnitTests.java

+21
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@
1616
package org.springframework.data.jpa.convert;
1717

1818
import static org.assertj.core.api.Assertions.*;
19+
import static org.mockito.ArgumentMatchers.*;
1920
import static org.mockito.Mockito.*;
2021
import static org.springframework.data.domain.Example.*;
2122

2223
import jakarta.persistence.Id;
2324
import jakarta.persistence.criteria.CriteriaBuilder;
2425
import jakarta.persistence.criteria.Expression;
2526
import jakarta.persistence.criteria.Join;
27+
import jakarta.persistence.criteria.JoinType;
2628
import jakarta.persistence.criteria.Path;
2729
import jakarta.persistence.criteria.Predicate;
2830
import jakarta.persistence.criteria.Root;
@@ -39,6 +41,8 @@
3941
import org.junit.jupiter.api.BeforeEach;
4042
import org.junit.jupiter.api.Test;
4143
import org.junit.jupiter.api.extension.ExtendWith;
44+
import org.junit.jupiter.params.ParameterizedTest;
45+
import org.junit.jupiter.params.provider.CsvSource;
4246
import org.mockito.ArgumentMatchers;
4347
import org.mockito.Mock;
4448
import org.mockito.junit.jupiter.MockitoExtension;
@@ -47,6 +51,7 @@
4751
import org.springframework.data.domain.Example;
4852
import org.springframework.data.domain.ExampleMatcher;
4953
import org.springframework.data.domain.ExampleMatcher.GenericPropertyMatcher;
54+
import org.springframework.data.domain.ExampleMatcher.MatchMode;
5055
import org.springframework.data.jpa.repository.query.EscapeCharacter;
5156
import org.springframework.util.ObjectUtils;
5257

@@ -57,6 +62,7 @@
5762
* @author Mark Paluch
5863
* @author Oliver Gierke
5964
* @author Jens Schauder
65+
* @author Arnaud Lecointre
6066
*/
6167
@ExtendWith(MockitoExtension.class)
6268
@MockitoSettings(strictness = Strictness.LENIENT)
@@ -271,6 +277,21 @@ void likePatternsGetEscapedEnding() {
271277
verify(cb, times(1)).like(any(Expression.class), eq("%f\\\\o\\_o"), eq('\\'));
272278
}
273279

280+
@ParameterizedTest(name = "Matching {0} on association should join using JoinType.{1} ") // DATAJPA-3763
281+
@CsvSource({ "ALL, INNER", "ANY, LEFT" })
282+
void matchingAssociationShouldUseTheCorrectJoinType(MatchMode matchMode, JoinType expectedJoinType) {
283+
284+
Person person = new Person();
285+
person.father = new Person();
286+
287+
ExampleMatcher matcher = matchMode == MatchMode.ALL ? ExampleMatcher.matchingAll() : ExampleMatcher.matchingAny();
288+
Example<Person> example = of(person, matcher);
289+
290+
QueryByExamplePredicateBuilder.getPredicate(root, cb, example, EscapeCharacter.DEFAULT);
291+
292+
verify(root, times(1)).join("father", expectedJoinType);
293+
}
294+
274295
@SuppressWarnings("unused")
275296
static class Person {
276297

0 commit comments

Comments
 (0)