diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java index 255ac86dc3..8ef396ff7d 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java @@ -56,6 +56,7 @@ * @author Moritz Becker * @author Andrey Kovalev * @author Greg Turnquist + * @author Yanming Zhou */ public class JpaQueryCreator extends AbstractQueryCreator, Predicate> { @@ -384,7 +385,7 @@ private Expression getComparablePath(Root root, Part pa } private Expression getTypedPath(Root root, Part part) { - return toExpressionRecursively(root, part.getProperty()); + return toExpressionRecursivelyForPredicate(root, part.getProperty()); } private Expression traversePath(Path root, PropertyPath path) { diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java index 0265b5549d..11b923b2e5 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java @@ -761,6 +761,32 @@ private static jakarta.persistence.criteria.Order toJpaOrder(Order order, From the type of the expression + * @return the expression + */ + @SuppressWarnings("unchecked") + static Expression toExpressionRecursivelyForPredicate(From from, PropertyPath property) { + + // see https://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#hql-implicit-join + Path path = from; + while (!property.isCollection()) { + path = path.get(property.getSegment()); + if (property.hasNext()) { + property = Objects.requireNonNull(property.next(), "An element of the property path is null"); + } else { + return (Expression) path; + } + } + + return toExpressionRecursively(from, property); + } + static Expression toExpressionRecursively(From from, PropertyPath property) { return toExpressionRecursively(from, property, false); } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryCreatorIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryCreatorIntegrationTests.java new file mode 100644 index 0000000000..d42b054d75 --- /dev/null +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryCreatorIntegrationTests.java @@ -0,0 +1,81 @@ +/* + * Copyright 2017-2024 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.query; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.persistence.TypedQuery; +import org.hibernate.query.spi.SqmQuery; +import org.hibernate.query.sqm.tree.from.SqmRoot; +import org.hibernate.query.sqm.tree.select.SqmQuerySpec; +import org.hibernate.query.sqm.tree.select.SqmSelectStatement; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.data.jpa.domain.sample.User; +import org.springframework.data.jpa.provider.PersistenceProvider; +import org.springframework.data.projection.SpelAwareProxyProjectionFactory; +import org.springframework.data.repository.Repository; +import org.springframework.data.repository.core.support.AbstractRepositoryMetadata; +import org.springframework.data.repository.query.parser.PartTree; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.lang.reflect.Method; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link JpaQueryCreator}. + * + * @author Yanming Zhou + */ +@ExtendWith(SpringExtension.class) +@ContextConfiguration("classpath:infrastructure.xml") +class JpaQueryCreatorIntegrationTests { + + @PersistenceContext + EntityManager entityManager; + + @Test // GH-3349 + void implicitJoin() throws Exception { + + Method method = SomeRepository.class.getMethod("findByManagerId", Integer.class); + + PersistenceProvider provider = PersistenceProvider.fromEntityManager(entityManager); + JpaQueryMethod queryMethod = new JpaQueryMethod(method, + AbstractRepositoryMetadata.getMetadata(SomeRepository.class), new SpelAwareProxyProjectionFactory(), provider); + + PartTree tree = new PartTree("findByManagerId", User.class); + ParameterMetadataProvider metadataProvider = new ParameterMetadataProvider(entityManager.getCriteriaBuilder(), + queryMethod.getParameters(), EscapeCharacter.DEFAULT); + + JpaQueryCreator creator = new JpaQueryCreator(tree, queryMethod.getResultProcessor().getReturnedType(), + entityManager.getCriteriaBuilder(), metadataProvider); + + TypedQuery query = entityManager.createQuery(creator.createQuery()); + SqmQuery sqmQuery = ((SqmQuery) query); + SqmSelectStatement statement = (SqmSelectStatement) sqmQuery.getSqmStatement(); + SqmQuerySpec spec = (SqmQuerySpec) statement.getQueryPart(); + SqmRoot root = spec.getFromClause().getRoots().get(0); + + assertThat(root.getJoins()).isEmpty(); + } + + interface SomeRepository extends Repository { + List findByManagerId(Integer managerId); + } +}