Skip to content

Commit

Permalink
Use implicit join to create Predicate if possible
Browse files Browse the repository at this point in the history
  • Loading branch information
quaff committed Apr 23, 2024
1 parent f79f57d commit 98d7664
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
* @author Moritz Becker
* @author Andrey Kovalev
* @author Greg Turnquist
* @author Yanming Zhou
*/
public class JpaQueryCreator extends AbstractQueryCreator<CriteriaQuery<? extends Object>, Predicate> {

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

private <T> Expression<T> getTypedPath(Root<?> root, Part part) {
return toExpressionRecursively(root, part.getProperty());
return toExpressionRecursivelyForPredicate(root, part.getProperty());
}

private <T> Expression<T> traversePath(Path<?> root, PropertyPath path) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -761,6 +761,32 @@ private static jakarta.persistence.criteria.Order toJpaOrder(Order order, From<?
}
}

/**
* Creates an expression with joins by recursively navigating the path for constructing {@code Predicate},
* it will use implicit join if possible to eliminate unnecessary join.
*
* @param from the {@link From}
* @param property the property path
* @param <T> the type of the expression
* @return the expression
*/
@SuppressWarnings("unchecked")
static <T> Expression<T> 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<T>) path;
}
}

return toExpressionRecursively(from, property);
}

static <T> Expression<T> toExpressionRecursively(From<?, ?> from, PropertyPath property) {
return toExpressionRecursively(from, property, false);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<User, Integer> {
List<User> findByManagerId(Integer managerId);
}
}

0 comments on commit 98d7664

Please sign in to comment.