Skip to content

Commit 98d7664

Browse files
committed
Use implicit join to create Predicate if possible
Fix spring-projectsGH-3349
1 parent f79f57d commit 98d7664

File tree

3 files changed

+109
-1
lines changed

3 files changed

+109
-1
lines changed

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
* @author Moritz Becker
5757
* @author Andrey Kovalev
5858
* @author Greg Turnquist
59+
* @author Yanming Zhou
5960
*/
6061
public class JpaQueryCreator extends AbstractQueryCreator<CriteriaQuery<? extends Object>, Predicate> {
6162

@@ -384,7 +385,7 @@ private Expression<? extends Comparable> getComparablePath(Root<?> root, Part pa
384385
}
385386

386387
private <T> Expression<T> getTypedPath(Root<?> root, Part part) {
387-
return toExpressionRecursively(root, part.getProperty());
388+
return toExpressionRecursivelyForPredicate(root, part.getProperty());
388389
}
389390

390391
private <T> Expression<T> traversePath(Path<?> root, PropertyPath path) {

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java

+26
Original file line numberDiff line numberDiff line change
@@ -761,6 +761,32 @@ private static jakarta.persistence.criteria.Order toJpaOrder(Order order, From<?
761761
}
762762
}
763763

764+
/**
765+
* Creates an expression with joins by recursively navigating the path for constructing {@code Predicate},
766+
* it will use implicit join if possible to eliminate unnecessary join.
767+
*
768+
* @param from the {@link From}
769+
* @param property the property path
770+
* @param <T> the type of the expression
771+
* @return the expression
772+
*/
773+
@SuppressWarnings("unchecked")
774+
static <T> Expression<T> toExpressionRecursivelyForPredicate(From<?, ?> from, PropertyPath property) {
775+
776+
// see https://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#hql-implicit-join
777+
Path<?> path = from;
778+
while (!property.isCollection()) {
779+
path = path.get(property.getSegment());
780+
if (property.hasNext()) {
781+
property = Objects.requireNonNull(property.next(), "An element of the property path is null");
782+
} else {
783+
return (Expression<T>) path;
784+
}
785+
}
786+
787+
return toExpressionRecursively(from, property);
788+
}
789+
764790
static <T> Expression<T> toExpressionRecursively(From<?, ?> from, PropertyPath property) {
765791
return toExpressionRecursively(from, property, false);
766792
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright 2017-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.jpa.repository.query;
17+
18+
import jakarta.persistence.EntityManager;
19+
import jakarta.persistence.PersistenceContext;
20+
import jakarta.persistence.TypedQuery;
21+
import org.hibernate.query.spi.SqmQuery;
22+
import org.hibernate.query.sqm.tree.from.SqmRoot;
23+
import org.hibernate.query.sqm.tree.select.SqmQuerySpec;
24+
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
25+
import org.junit.jupiter.api.Test;
26+
import org.junit.jupiter.api.extension.ExtendWith;
27+
import org.springframework.data.jpa.domain.sample.User;
28+
import org.springframework.data.jpa.provider.PersistenceProvider;
29+
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
30+
import org.springframework.data.repository.Repository;
31+
import org.springframework.data.repository.core.support.AbstractRepositoryMetadata;
32+
import org.springframework.data.repository.query.parser.PartTree;
33+
import org.springframework.test.context.ContextConfiguration;
34+
import org.springframework.test.context.junit.jupiter.SpringExtension;
35+
36+
import java.lang.reflect.Method;
37+
import java.util.List;
38+
39+
import static org.assertj.core.api.Assertions.assertThat;
40+
41+
/**
42+
* Integration tests for {@link JpaQueryCreator}.
43+
*
44+
* @author Yanming Zhou
45+
*/
46+
@ExtendWith(SpringExtension.class)
47+
@ContextConfiguration("classpath:infrastructure.xml")
48+
class JpaQueryCreatorIntegrationTests {
49+
50+
@PersistenceContext
51+
EntityManager entityManager;
52+
53+
@Test // GH-3349
54+
void implicitJoin() throws Exception {
55+
56+
Method method = SomeRepository.class.getMethod("findByManagerId", Integer.class);
57+
58+
PersistenceProvider provider = PersistenceProvider.fromEntityManager(entityManager);
59+
JpaQueryMethod queryMethod = new JpaQueryMethod(method,
60+
AbstractRepositoryMetadata.getMetadata(SomeRepository.class), new SpelAwareProxyProjectionFactory(), provider);
61+
62+
PartTree tree = new PartTree("findByManagerId", User.class);
63+
ParameterMetadataProvider metadataProvider = new ParameterMetadataProvider(entityManager.getCriteriaBuilder(),
64+
queryMethod.getParameters(), EscapeCharacter.DEFAULT);
65+
66+
JpaQueryCreator creator = new JpaQueryCreator(tree, queryMethod.getResultProcessor().getReturnedType(),
67+
entityManager.getCriteriaBuilder(), metadataProvider);
68+
69+
TypedQuery<?> query = entityManager.createQuery(creator.createQuery());
70+
SqmQuery sqmQuery = ((SqmQuery) query);
71+
SqmSelectStatement<?> statement = (SqmSelectStatement<?>) sqmQuery.getSqmStatement();
72+
SqmQuerySpec<?> spec = (SqmQuerySpec<?>) statement.getQueryPart();
73+
SqmRoot<?> root = spec.getFromClause().getRoots().get(0);
74+
75+
assertThat(root.getJoins()).isEmpty();
76+
}
77+
78+
interface SomeRepository extends Repository<User, Integer> {
79+
List<User> findByManagerId(Integer managerId);
80+
}
81+
}

0 commit comments

Comments
 (0)