From 4b7c413f5bf98cc1a13bd032b0c9b1d6ffee14ff Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Tue, 24 Sep 2013 10:36:21 -0700 Subject: [PATCH] DATAJPA-403 - Improve predicate building by reusing existing joins if possible. QueryUtils now checks already existing joins and reuses them when building expressions. Inspired by the pull request #41 by Alexandre Payment but polished and added integration test to make sure the joins really get reused. --- .../data/jpa/repository/query/QueryUtils.java | 27 +++++++- .../query/QueryUtilsIntegrationTests.java | 63 +++++++++++++++++++ 2 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 src/test/java/org/springframework/data/jpa/repository/query/QueryUtilsIntegrationTests.java diff --git a/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java b/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java index a4d7a9372..a6fbf2e7e 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java @@ -439,12 +439,12 @@ public abstract class QueryUtils { */ propertyPathModel = (Bindable) ((ManagedType) from.getModel()).getAttribute(property.getSegment()); } else { - propertyPathModel = (Bindable) from.get(property.getSegment()).getModel(); + propertyPathModel = from.get(property.getSegment()).getModel(); } if (property.isCollection() || isEntityPath(propertyPathModel)) { - Join join = from.join(property.getSegment(), JoinType.LEFT); - return (Expression) (property.hasNext() ? toExpressionRecursively((From) join, property.next()) : join); + Join join = getOrCreateJoin(from, property.getSegment()); + return (Expression) (property.hasNext() ? toExpressionRecursively(join, property.next()) : join); } else { Path path = from.get(property.getSegment()); return (Expression) (property.hasNext() ? toExpressionRecursively(path, property.next()) : path); @@ -482,4 +482,25 @@ public abstract class QueryUtils { Path result = path.get(property.getSegment()); return property.hasNext() ? toExpressionRecursively(result, property.next()) : result; } + + /** + * Returns an existing join for the given attribute if one already exists or creates a new one if not. + * + * @param from the {@link From} to get the current joins from. + * @param attribute the {@link Attribute} to look for in the current joins. + * @return will never be {@literal null}. + */ + private static Join getOrCreateJoin(From from, String attribute) { + + for (Join join : from.getJoins()) { + + boolean sameName = join.getAttribute().getName().equals(attribute); + + if (sameName && join.getJoinType().equals(JoinType.LEFT)) { + return join; + } + } + + return from.join(attribute, JoinType.LEFT); + } } diff --git a/src/test/java/org/springframework/data/jpa/repository/query/QueryUtilsIntegrationTests.java b/src/test/java/org/springframework/data/jpa/repository/query/QueryUtilsIntegrationTests.java new file mode 100644 index 000000000..070efe7ed --- /dev/null +++ b/src/test/java/org/springframework/data/jpa/repository/query/QueryUtilsIntegrationTests.java @@ -0,0 +1,63 @@ +/* + * Copyright 2013 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 + * + * http://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 static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Root; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.data.jpa.domain.sample.User; +import org.springframework.data.mapping.PropertyPath; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * Integration tests for {@link QueryUtils}. + * + * @author Oliver Gierke + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration("classpath:infrastructure.xml") +public class QueryUtilsIntegrationTests { + + @PersistenceContext EntityManager em; + + /** + * @see DATAJPA-403 + */ + @Test + public void reusesExistingJoinForExpression() { + + CriteriaBuilder builder = em.getCriteriaBuilder(); + CriteriaQuery query = builder.createQuery(User.class); + Root from = query.from(User.class); + + PropertyPath managerFirstname = PropertyPath.from("manager.firstname", User.class); + PropertyPath managerLastname = PropertyPath.from("manager.lastname", User.class); + + QueryUtils.toExpressionRecursively(from, managerLastname); + QueryUtils.toExpressionRecursively(from, managerFirstname); + + assertThat(from.getJoins(), hasSize(1)); + } +}