diff --git a/pom.xml b/pom.xml
index 27f5fb3f0e..4dc150d965 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.springframework.data
spring-data-jpa
- 2.6.0-SNAPSHOT
+ 2.6.0-2329-consider-property-paths-SNAPSHOT
Spring Data JPA
Spring Data module for JPA repositories.
diff --git a/src/main/java/org/springframework/data/jpa/repository/support/EntityGraphFactory.java b/src/main/java/org/springframework/data/jpa/repository/support/EntityGraphFactory.java
new file mode 100644
index 0000000000..a619916bf9
--- /dev/null
+++ b/src/main/java/org/springframework/data/jpa/repository/support/EntityGraphFactory.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2021 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.support;
+
+import java.util.Set;
+
+import javax.persistence.EntityGraph;
+import javax.persistence.EntityManager;
+import javax.persistence.Subgraph;
+
+import org.springframework.data.mapping.PropertyPath;
+
+/**
+ * Factory class to create an {@link EntityGraph} from a collection of property paths.
+ *
+ * @author Jens Schauder
+ * @since 2.6
+ */
+abstract class EntityGraphFactory {
+
+ public static final String HINT = "javax.persistence.fetchgraph";
+
+ /**
+ * Create an {@link EntityGraph} from a collection of properties.
+ *
+ * @param domainType
+ * @param properties
+ */
+ public static EntityGraph create(EntityManager entityManager, Class domainType, Set properties) {
+
+ EntityGraph entityGraph = entityManager.createEntityGraph(domainType);
+
+ for (String property : properties) {
+
+ Subgraph current = null;
+
+ for (PropertyPath path : PropertyPath.from(property, domainType)) {
+
+ if (path.hasNext()) {
+ current = current == null ? entityGraph.addSubgraph(path.getSegment())
+ : current.addSubgraph(path.getSegment());
+ continue;
+ }
+
+ if (current == null) {
+ entityGraph.addAttributeNodes(path.getSegment());
+ } else {
+ current.addAttributeNodes(path.getSegment());
+
+ }
+ }
+ }
+
+ return entityGraph;
+ }
+
+}
diff --git a/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryByExample.java b/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryByExample.java
index c187ccd884..cb2645bfc1 100644
--- a/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryByExample.java
+++ b/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryByExample.java
@@ -17,6 +17,7 @@
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Stream;
@@ -31,12 +32,8 @@
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.query.EscapeCharacter;
-import org.springframework.data.mapping.PersistentEntity;
-import org.springframework.data.mapping.PersistentProperty;
-import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery;
import org.springframework.data.support.PageableExecutionUtils;
-import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
@@ -47,9 +44,10 @@
* @param Result type
* @author Greg Turnquist
* @author Mark Paluch
+ * @author Jens Schauder
* @since 2.6
*/
-class FetchableFluentQueryByExample extends FluentQuerySupport implements FetchableFluentQuery {
+class FetchableFluentQueryByExample extends FluentQuerySupport implements FetchableFluentQuery {
private final Example example;
private final Function> finder;
@@ -60,19 +58,17 @@ class FetchableFluentQueryByExample extends FluentQuerySupport implemen
public FetchableFluentQueryByExample(Example example, Function> finder,
Function, Long> countOperation, Function, Boolean> existsOperation,
- MappingContext extends PersistentEntity, ?>, ? extends PersistentProperty>> context,
EntityManager entityManager, EscapeCharacter escapeCharacter) {
- this(example, (Class) example.getProbeType(), Sort.unsorted(), null, finder, countOperation, existsOperation,
- context, entityManager, escapeCharacter);
+ this(example, example.getProbeType(), (Class) example.getProbeType(), Sort.unsorted(), Collections.emptySet(),
+ finder, countOperation, existsOperation, entityManager, escapeCharacter);
}
- private FetchableFluentQueryByExample(Example example, Class returnType, Sort sort,
- @Nullable Collection properties, Function> finder,
- Function, Long> countOperation, Function, Boolean> existsOperation,
- MappingContext extends PersistentEntity, ?>, ? extends PersistentProperty>> context,
+ private FetchableFluentQueryByExample(Example example, Class entityType, Class returnType, Sort sort,
+ Collection properties, Function> finder, Function, Long> countOperation,
+ Function, Boolean> existsOperation,
EntityManager entityManager, EscapeCharacter escapeCharacter) {
- super(returnType, sort, properties, context);
+ super(returnType, sort, properties, entityType);
this.example = example;
this.finder = finder;
this.countOperation = countOperation;
@@ -81,7 +77,7 @@ private FetchableFluentQueryByExample(Example example, Class returnType, S
this.escapeCharacter = escapeCharacter;
}
- /*
+ /*
* (non-Javadoc)
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#sortBy(org.springframework.data.domain.Sort)
*/
@@ -90,11 +86,11 @@ public FetchableFluentQuery sortBy(Sort sort) {
Assert.notNull(sort, "Sort must not be null!");
- return new FetchableFluentQueryByExample<>(this.example, this.resultType, this.sort.and(sort), this.properties,
- this.finder, this.countOperation, this.existsOperation, this.context, this.entityManager, this.escapeCharacter);
+ return new FetchableFluentQueryByExample<>(example, entityType, resultType, sort.and(sort), properties, finder,
+ countOperation, existsOperation, entityManager, escapeCharacter);
}
- /*
+ /*
* (non-Javadoc)
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#as(java.lang.Class)
*/
@@ -106,29 +102,29 @@ public FetchableFluentQuery as(Class resultType) {
throw new UnsupportedOperationException("Class-based DTOs are not yet supported.");
}
- return new FetchableFluentQueryByExample<>(this.example, resultType, this.sort, this.properties, this.finder,
- this.countOperation, this.existsOperation, this.context, this.entityManager, this.escapeCharacter);
+ return new FetchableFluentQueryByExample<>(example, entityType, resultType, sort, properties, finder,
+ countOperation, existsOperation, entityManager, escapeCharacter);
}
- /*
+ /*
* (non-Javadoc)
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#project(java.util.Collection)
*/
@Override
public FetchableFluentQuery project(Collection properties) {
- return new FetchableFluentQueryByExample<>(this.example, this.resultType, this.sort, mergeProperties(properties),
- this.finder, this.countOperation, this.existsOperation, this.context, this.entityManager, this.escapeCharacter);
+ return new FetchableFluentQueryByExample<>(example, entityType, resultType, sort, mergeProperties(properties),
+ finder, countOperation, existsOperation, entityManager, escapeCharacter);
}
- /*
+ /*
* (non-Javadoc)
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#oneValue()
*/
@Override
public R oneValue() {
- TypedQuery limitedQuery = this.finder.apply(this.sort);
+ TypedQuery limitedQuery = createSortedAndProjectedQuery();
limitedQuery.setMaxResults(2); // Never need more than 2 values
List results = limitedQuery.getResultList();
@@ -140,14 +136,14 @@ public R oneValue() {
return results.isEmpty() ? null : getConversionFunction().apply(results.get(0));
}
- /*
+ /*
* (non-Javadoc)
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#firstValue()
*/
@Override
public R firstValue() {
- TypedQuery limitedQuery = this.finder.apply(this.sort);
+ TypedQuery limitedQuery = createSortedAndProjectedQuery();
limitedQuery.setMaxResults(1); // Never need more than 1 value
List results = limitedQuery.getResultList();
@@ -155,19 +151,19 @@ public R firstValue() {
return results.isEmpty() ? null : getConversionFunction().apply(results.get(0));
}
- /*
+ /*
* (non-Javadoc)
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#all()
*/
@Override
public List all() {
- List resultList = this.finder.apply(this.sort).getResultList();
+ List resultList = createSortedAndProjectedQuery().getResultList();
return convert(resultList);
}
- /*
+ /*
* (non-Javadoc)
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#page(org.springframework.data.domain.Pageable)
*/
@@ -176,39 +172,39 @@ public Page page(Pageable pageable) {
return pageable.isUnpaged() ? new PageImpl<>(all()) : readPage(pageable);
}
- /*
+ /*
* (non-Javadoc)
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#stream()
*/
@Override
public Stream stream() {
- return this.finder.apply(this.sort) //
+ return createSortedAndProjectedQuery() //
.getResultStream() //
.map(getConversionFunction());
}
- /*
+ /*
* (non-Javadoc)
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#count()
*/
@Override
public long count() {
- return this.countOperation.apply(example);
+ return countOperation.apply(example);
}
- /*
+ /*
* (non-Javadoc)
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#exists()
*/
@Override
public boolean exists() {
- return this.existsOperation.apply(example);
+ return existsOperation.apply(example);
}
private Page readPage(Pageable pageable) {
- TypedQuery pagedQuery = this.finder.apply(this.sort);
+ TypedQuery pagedQuery = createSortedAndProjectedQuery();
if (pageable.isPaged()) {
pagedQuery.setFirstResult((int) pageable.getOffset());
@@ -217,7 +213,18 @@ private Page readPage(Pageable pageable) {
List paginatedResults = convert(pagedQuery.getResultList());
- return PageableExecutionUtils.getPage(paginatedResults, pageable, () -> this.countOperation.apply(this.example));
+ return PageableExecutionUtils.getPage(paginatedResults, pageable, () -> countOperation.apply(example));
+ }
+
+ private TypedQuery createSortedAndProjectedQuery() {
+
+ TypedQuery query = finder.apply(sort);
+
+ if (!properties.isEmpty()) {
+ query.setHint(EntityGraphFactory.HINT, EntityGraphFactory.create(entityManager, entityType, properties));
+ }
+
+ return query;
}
private List convert(List resultList) {
@@ -232,7 +239,7 @@ private List convert(List resultList) {
}
private Function getConversionFunction() {
- return getConversionFunction(this.example.getProbeType(), this.resultType);
+ return getConversionFunction(example.getProbeType(), resultType);
}
}
diff --git a/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryByPredicate.java b/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryByPredicate.java
index a5890f568f..f71eeacc0b 100644
--- a/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryByPredicate.java
+++ b/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryByPredicate.java
@@ -17,26 +17,25 @@
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Stream;
+import javax.persistence.EntityManager;
+
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
-import org.springframework.data.mapping.PersistentEntity;
-import org.springframework.data.mapping.PersistentProperty;
-import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery;
import org.springframework.data.support.PageableExecutionUtils;
-import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import com.querydsl.core.types.Predicate;
-import com.querydsl.jpa.JPQLQuery;
+import com.querydsl.jpa.impl.AbstractJPAQuery;
/**
* Immutable implementation of {@link FetchableFluentQuery} based on a Querydsl {@link Predicate}. All methods that
@@ -46,41 +45,42 @@
* @param Result type
* @author Greg Turnquist
* @author Mark Paluch
+ * @author Jens Schauder
* @since 2.6
*/
-class FetchableFluentQueryByPredicate extends FluentQuerySupport implements FetchableFluentQuery {
+class FetchableFluentQueryByPredicate extends FluentQuerySupport implements FetchableFluentQuery {
private final Predicate predicate;
- private final Function> finder;
- private final BiFunction> pagedFinder;
+ private final Function> finder;
+ private final BiFunction> pagedFinder;
private final Function countOperation;
private final Function existsOperation;
- private final Class entityType;
-
- public FetchableFluentQueryByPredicate(Predicate predicate, Class resultType, Function> finder,
- BiFunction> pagedFinder, Function countOperation,
- Function existsOperation, Class entityType,
- MappingContext extends PersistentEntity, ?>, ? extends PersistentProperty>> context) {
- this(predicate, resultType, Sort.unsorted(), null, finder, pagedFinder, countOperation, existsOperation, entityType,
- context);
+ private final EntityManager entityManager;
+
+ public FetchableFluentQueryByPredicate(Predicate predicate, Class entityType,
+ Function> finder, BiFunction> pagedFinder,
+ Function countOperation, Function existsOperation,
+ EntityManager entityManager) {
+ this(predicate, entityType, (Class) entityType, Sort.unsorted(), Collections.emptySet(), finder, pagedFinder,
+ countOperation, existsOperation, entityManager);
}
- private FetchableFluentQueryByPredicate(Predicate predicate, Class resultType, Sort sort,
- @Nullable Collection properties, Function> finder,
- BiFunction> pagedFinder, Function countOperation,
- Function existsOperation, Class entityType,
- MappingContext extends PersistentEntity, ?>, ? extends PersistentProperty>> context) {
+ private FetchableFluentQueryByPredicate(Predicate predicate, Class entityType, Class resultType, Sort sort,
+ Collection properties, Function> finder,
+ BiFunction> pagedFinder, Function countOperation,
+ Function existsOperation,
+ EntityManager entityManager) {
- super(resultType, sort, properties, context);
+ super(resultType, sort, properties, entityType);
this.predicate = predicate;
this.finder = finder;
this.pagedFinder = pagedFinder;
this.countOperation = countOperation;
this.existsOperation = existsOperation;
- this.entityType = entityType;
+ this.entityManager = entityManager;
}
- /*
+ /*
* (non-Javadoc)
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#sortBy(org.springframework.data.domain.Sort)
*/
@@ -89,11 +89,11 @@ public FetchableFluentQuery sortBy(Sort sort) {
Assert.notNull(sort, "Sort must not be null!");
- return new FetchableFluentQueryByPredicate<>(this.predicate, this.resultType, this.sort.and(sort), this.properties,
- this.finder, this.pagedFinder, this.countOperation, this.existsOperation, this.entityType, this.context);
+ return new FetchableFluentQueryByPredicate<>(predicate, entityType, resultType, sort.and(sort), properties, finder,
+ pagedFinder, countOperation, existsOperation, entityManager);
}
- /*
+ /*
* (non-Javadoc)
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#as(java.lang.Class)
*/
@@ -101,34 +101,34 @@ public FetchableFluentQuery sortBy(Sort sort) {
public FetchableFluentQuery as(Class resultType) {
Assert.notNull(resultType, "Projection target type must not be null!");
+
if (!resultType.isInterface()) {
throw new UnsupportedOperationException("Class-based DTOs are not yet supported.");
}
- return new FetchableFluentQueryByPredicate<>(this.predicate, resultType, this.sort, this.properties, this.finder,
- this.pagedFinder, this.countOperation, this.existsOperation, this.entityType, this.context);
+ return new FetchableFluentQueryByPredicate<>(predicate, entityType, resultType, sort, properties, finder,
+ pagedFinder, countOperation, existsOperation, entityManager);
}
- /*
+ /*
* (non-Javadoc)
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#project(java.util.Collection)
*/
@Override
public FetchableFluentQuery project(Collection properties) {
- return new FetchableFluentQueryByPredicate<>(this.predicate, this.resultType, this.sort,
- mergeProperties(properties), this.finder, this.pagedFinder, this.countOperation, this.existsOperation,
- this.entityType, this.context);
+ return new FetchableFluentQueryByPredicate<>(predicate, entityType, resultType, sort, mergeProperties(properties),
+ finder, pagedFinder, countOperation, existsOperation, entityManager);
}
- /*
+ /*
* (non-Javadoc)
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#oneValue()
*/
@Override
public R oneValue() {
- List results = this.finder.apply(this.sort) //
+ List> results = createSortedAndProjectedQuery() //
.limit(2) // Never need more than 2 values
.fetch();
@@ -139,32 +139,30 @@ public R oneValue() {
return results.isEmpty() ? null : getConversionFunction().apply(results.get(0));
}
- /*
+ /*
* (non-Javadoc)
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#firstValue()
*/
@Override
public R firstValue() {
- List results = this.finder.apply(this.sort) //
+ List> results = createSortedAndProjectedQuery() //
.limit(1) // Never need more than 1 value
.fetch();
return results.isEmpty() ? null : getConversionFunction().apply(results.get(0));
}
- /*
+ /*
* (non-Javadoc)
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#all()
*/
@Override
public List all() {
-
- JPQLQuery query = this.finder.apply(this.sort);
- return convert(query.fetch());
+ return convert(createSortedAndProjectedQuery().fetch());
}
- /*
+ /*
* (non-Javadoc)
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#page(org.springframework.data.domain.Pageable)
*/
@@ -173,58 +171,69 @@ public Page page(Pageable pageable) {
return pageable.isUnpaged() ? new PageImpl<>(all()) : readPage(pageable);
}
- /*
+ /*
* (non-Javadoc)
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#stream()
*/
@Override
public Stream stream() {
- return this.finder.apply(this.sort) //
+ return createSortedAndProjectedQuery() //
.stream() //
.map(getConversionFunction());
}
- /*
+ /*
* (non-Javadoc)
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#count()
*/
@Override
public long count() {
- return this.countOperation.apply(this.predicate);
+ return countOperation.apply(predicate);
}
- /*
+ /*
* (non-Javadoc)
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#exists()
*/
@Override
public boolean exists() {
- return this.existsOperation.apply(this.predicate);
+ return existsOperation.apply(predicate);
+ }
+
+ private AbstractJPAQuery, ?> createSortedAndProjectedQuery() {
+
+ AbstractJPAQuery, ?> query = finder.apply(sort);
+
+ if (!properties.isEmpty()) {
+ query.setHint(EntityGraphFactory.HINT, EntityGraphFactory.create(entityManager, entityType, properties));
+ }
+
+ return query;
}
private Page readPage(Pageable pageable) {
- JPQLQuery pagedQuery = this.pagedFinder.apply(this.sort, pageable);
+ AbstractJPAQuery, ?> pagedQuery = pagedFinder.apply(sort, pageable);
List paginatedResults = convert(pagedQuery.fetch());
- return PageableExecutionUtils.getPage(paginatedResults, pageable, () -> this.countOperation.apply(this.predicate));
+ return PageableExecutionUtils.getPage(paginatedResults, pageable, () -> countOperation.apply(predicate));
}
- private List convert(List resultList) {
+ private List convert(List> resultList) {
Function conversionFunction = getConversionFunction();
List mapped = new ArrayList<>(resultList.size());
- for (S s : resultList) {
- mapped.add(conversionFunction.apply(s));
+ for (Object o : resultList) {
+ mapped.add(conversionFunction.apply(o));
}
return mapped;
}
private Function getConversionFunction() {
- return getConversionFunction(this.entityType, this.resultType);
+ return getConversionFunction(entityType, resultType);
}
}
diff --git a/src/main/java/org/springframework/data/jpa/repository/support/FluentQuerySupport.java b/src/main/java/org/springframework/data/jpa/repository/support/FluentQuerySupport.java
index d8cf794577..ab7d58ddc9 100644
--- a/src/main/java/org/springframework/data/jpa/repository/support/FluentQuerySupport.java
+++ b/src/main/java/org/springframework/data/jpa/repository/support/FluentQuerySupport.java
@@ -23,9 +23,6 @@
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.data.domain.Sort;
-import org.springframework.data.mapping.PersistentEntity;
-import org.springframework.data.mapping.PersistentProperty;
-import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.lang.Nullable;
@@ -34,19 +31,19 @@
*
* @param The resulting type of the query.
* @author Greg Turnquist
+ * @author Jens Schauder
* @since 2.6
*/
-abstract class FluentQuerySupport {
+abstract class FluentQuerySupport {
protected final Class resultType;
protected final Sort sort;
- protected final @Nullable Set properties;
- protected final MappingContext extends PersistentEntity, ?>, ? extends PersistentProperty>> context;
+ protected final Set properties;
+ protected final Class entityType;
private final SpelAwareProxyProjectionFactory projectionFactory = new SpelAwareProxyProjectionFactory();
- FluentQuerySupport(Class resultType, Sort sort, @Nullable Collection properties,
- MappingContext extends PersistentEntity, ?>, ? extends PersistentProperty>> context) {
+ FluentQuerySupport(Class resultType, Sort sort, @Nullable Collection properties, Class entityType) {
this.resultType = resultType;
this.sort = sort;
@@ -54,24 +51,22 @@ abstract class FluentQuerySupport {
if (properties != null) {
this.properties = new HashSet<>(properties);
} else {
- this.properties = null;
+ this.properties = Collections.emptySet();
}
- this.context = context;
+ this.entityType = entityType;
}
final Collection mergeProperties(Collection additionalProperties) {
Set newProperties = new HashSet<>();
- if (this.properties != null) {
- newProperties.addAll(this.properties);
- }
+ newProperties.addAll(properties);
newProperties.addAll(additionalProperties);
return Collections.unmodifiableCollection(newProperties);
}
@SuppressWarnings("unchecked")
- final Function getConversionFunction(Class inputType, Class targetType) {
+ final Function getConversionFunction(Class inputType, Class targetType) {
if (targetType.isAssignableFrom(inputType)) {
return (Function) Function.identity();
diff --git a/src/main/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutor.java b/src/main/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutor.java
index 19ecfcf79a..d748761ddb 100644
--- a/src/main/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutor.java
+++ b/src/main/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutor.java
@@ -15,7 +15,6 @@
*/
package org.springframework.data.jpa.repository.support;
-import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.BiFunction;
@@ -28,7 +27,6 @@
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
-import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.querydsl.EntityPathResolver;
import org.springframework.data.querydsl.QSort;
@@ -167,7 +165,7 @@ public Page findAll(Predicate predicate, Pageable pageable) {
return PageableExecutionUtils.getPage(query.fetch(), pageable, countQuery::fetchCount);
}
- /*
+ /*
* (non-Javadoc)
* @see org.springframework.data.querydsl.QuerydslPredicateExecutor#findBy(com.querydsl.core.types.Predicate, java.util.function.Function)
*/
@@ -178,31 +176,36 @@ public R findBy(Predicate predicate, Function> finder = sort -> {
- JPQLQuery select = createQuery(predicate).select(path);
+ Function> finder = sort -> {
+ AbstractJPAQuery, ?> select = (AbstractJPAQuery, ?>) createQuery(predicate).select(path);
if (sort != null) {
- select = querydsl.applySorting(sort, select);
+ select = (AbstractJPAQuery, ?>) querydsl.applySorting(sort, select);
}
return select;
};
- BiFunction> pagedFinder = (sort, pageable) -> {
+ BiFunction> pagedFinder = (sort, pageable) -> {
- JPQLQuery select = finder.apply(sort);
+ AbstractJPAQuery, ?> select = finder.apply(sort);
if (pageable.isPaged()) {
- select = querydsl.applyPagination(pageable, select);
+ select = (AbstractJPAQuery, ?>) querydsl.applyPagination(pageable, select);
}
return select;
};
- FetchableFluentQueryByPredicate fluentQuery = new FetchableFluentQueryByPredicate<>(predicate,
- entityInformation.getJavaType(), finder, pagedFinder, this::count, this::exists,
- this.entityInformation.getJavaType(),
- new JpaMetamodelMappingContext(Collections.singleton(this.entityManager.getMetamodel())));
+ FetchableFluentQueryByPredicate fluentQuery = new FetchableFluentQueryByPredicate<>( //
+ predicate, //
+ this.entityInformation.getJavaType(), //
+ finder, //
+ pagedFinder, //
+ this::count, //
+ this::exists, //
+ entityManager //
+ );
return queryFunction.apply((FetchableFluentQuery) fluentQuery);
}
@@ -231,7 +234,7 @@ public boolean exists(Predicate predicate) {
* @param predicate
* @return the Querydsl {@link JPQLQuery}.
*/
- protected JPQLQuery> createQuery(Predicate... predicate) {
+ protected AbstractJPAQuery, ?> createQuery(Predicate... predicate) {
Assert.notNull(predicate, "Predicate must not be null!");
diff --git a/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java b/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java
index b69d43667f..f5f2fe1ef8 100644
--- a/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java
+++ b/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java
@@ -48,15 +48,11 @@
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.convert.QueryByExamplePredicateBuilder;
import org.springframework.data.jpa.domain.Specification;
-import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext;
import org.springframework.data.jpa.provider.PersistenceProvider;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.query.EscapeCharacter;
import org.springframework.data.jpa.repository.query.QueryUtils;
import org.springframework.data.jpa.repository.support.QueryHints.NoHints;
-import org.springframework.data.mapping.PersistentEntity;
-import org.springframework.data.mapping.PersistentProperty;
-import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery;
import org.springframework.data.support.PageableExecutionUtils;
import org.springframework.data.util.ProxyUtils;
@@ -94,7 +90,6 @@ public class SimpleJpaRepository implements JpaRepositoryImplementation entityInformation;
private final EntityManager em;
private final PersistenceProvider provider;
- private final MappingContext extends PersistentEntity, ?>, ? extends PersistentProperty>> context;
private @Nullable CrudMethodMetadata metadata;
private EscapeCharacter escapeCharacter = EscapeCharacter.DEFAULT;
@@ -113,9 +108,6 @@ public SimpleJpaRepository(JpaEntityInformation entityInformation, EntityM
this.entityInformation = entityInformation;
this.em = entityManager;
this.provider = PersistenceProvider.fromEntityManager(entityManager);
- this.context = em.getMetamodel() != null //
- ? new JpaMetamodelMappingContext(Collections.singleton(em.getMetamodel())) //
- : null;
}
/**
@@ -595,7 +587,7 @@ public R findBy(Example example, Function fluentQuery = new FetchableFluentQueryByExample<>(example, finder, this::count,
- this::exists, this.context, this.em, this.escapeCharacter);
+ this::exists, this.em, this.escapeCharacter);
return queryFunction.apply(fluentQuery);
}
diff --git a/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java b/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java
index 29f03ef110..623bb41fa0 100644
--- a/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java
+++ b/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java
@@ -45,6 +45,7 @@
import javax.persistence.criteria.Root;
import org.assertj.core.api.SoftAssertions;
+import org.hibernate.LazyInitializationException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@@ -2138,6 +2139,84 @@ void findByFluentExampleWithInterfaceBasedProjection() {
.containsExactlyInAnyOrder(firstUser.getFirstname(), thirdUser.getFirstname(), fourthUser.getFirstname());
}
+ @Test // GH-2294
+ void findByFluentExampleWithSimplePropertyPathsDoesntLoadUnrequestedPaths() {
+
+ flushTestUsers();
+ // make sure we don't get preinitialized entities back:
+ em.clear();
+
+ User prototype = new User();
+ prototype.setFirstname("v");
+
+ List users = repository.findBy(
+ of(prototype,
+ matching().withIgnorePaths("age", "createdAt", "active").withMatcher("firstname",
+ GenericPropertyMatcher::contains)), //
+ q -> q.project("firstname").all());
+
+ // remove the entities, so lazy loading throws an exception
+ em.clear();
+
+ assertThat(users).extracting(User::getFirstname).containsExactlyInAnyOrder(firstUser.getFirstname(),
+ thirdUser.getFirstname(), fourthUser.getFirstname());
+
+ assertThatExceptionOfType(LazyInitializationException.class) //
+ .isThrownBy( //
+ () -> users.forEach(u -> u.getRoles().size()) // forces loading of roles
+ );
+ }
+
+ @Test // GH-2294
+ void findByFluentExampleWithCollectionPropertyPathsDoesntLoadUnrequestedPaths() {
+
+ flushTestUsers();
+ // make sure we don't get preinitialized entities back:
+ em.clear();
+
+ User prototype = new User();
+ prototype.setFirstname("v");
+
+ List users = repository.findBy(
+ of(prototype,
+ matching().withIgnorePaths("age", "createdAt", "active").withMatcher("firstname",
+ GenericPropertyMatcher::contains)), //
+ q -> q.project("firstname", "roles").all());
+
+ // remove the entities, so lazy loading throws an exception
+ em.clear();
+
+ assertThat(users).extracting(User::getFirstname).containsExactlyInAnyOrder(firstUser.getFirstname(),
+ thirdUser.getFirstname(), fourthUser.getFirstname());
+
+ assertThat(users).allMatch(u -> u.getRoles().isEmpty());
+ }
+
+ @Test // GH-2294
+ void findByFluentExampleWithComplexPropertyPathsDoesntLoadUnrequestedPaths() {
+
+ flushTestUsers();
+ // make sure we don't get preinitialized entities back:
+ em.clear();
+
+ User prototype = new User();
+ prototype.setFirstname("v");
+
+ List users = repository.findBy(
+ of(prototype,
+ matching().withIgnorePaths("age", "createdAt", "active").withMatcher("firstname",
+ GenericPropertyMatcher::contains)), //
+ q -> q.project("roles.name").all());
+
+ // remove the entities, so lazy loading throws an exception
+ em.clear();
+
+ assertThat(users).extracting(User::getFirstname).containsExactlyInAnyOrder(firstUser.getFirstname(),
+ thirdUser.getFirstname(), fourthUser.getFirstname());
+
+ assertThat(users).allMatch(u -> u.getRoles().isEmpty());
+ }
+
@Test // GH-2294
void findByFluentExampleWithSortedInterfaceBasedProjection() {
diff --git a/src/test/java/org/springframework/data/jpa/repository/support/EntityGraphFactoryUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/support/EntityGraphFactoryUnitTests.java
new file mode 100644
index 0000000000..e0ac1ce52c
--- /dev/null
+++ b/src/test/java/org/springframework/data/jpa/repository/support/EntityGraphFactoryUnitTests.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2021 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.support;
+
+import static java.util.Arrays.*;
+import static org.mockito.Mockito.*;
+
+import java.util.HashSet;
+
+import javax.persistence.EntityGraph;
+import javax.persistence.EntityManager;
+import javax.persistence.Subgraph;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for {@link EntityGraphFactory}.
+ *
+ * @author Jens Schauder
+ */
+@SuppressWarnings("rawtypes")
+class EntityGraphFactoryUnitTests {
+
+ EntityManager em = mock(EntityManager.class);
+ EntityGraph entityGraph;
+
+ @BeforeEach
+ void beforeEach() {
+
+ entityGraph = mock(EntityGraph.class, RETURNS_DEEP_STUBS);
+ when(em.createEntityGraph(DummyEntity.class)).thenReturn(entityGraph);
+ }
+
+ // GH-2329
+ @Test
+ void simpleSetOfPropertiesGetRegistered() {
+
+ HashSet properties = new HashSet<>(asList("one", "two"));
+
+ entityGraph = EntityGraphFactory.create(em, DummyEntity.class, properties);
+
+ verify(entityGraph).addAttributeNodes("one");
+ verify(entityGraph).addAttributeNodes("two");
+ }
+
+ // GH-2329
+ @Test
+ void setOfCompositePropertiesGetRegisteredPiecewise() {
+
+ HashSet properties = new HashSet<>(asList("one.two", "eins.zwei.drei"));
+
+ entityGraph = EntityGraphFactory.create(em, DummyEntity.class, properties);
+
+ verify(entityGraph).addSubgraph("one");
+ Subgraph> one = entityGraph.addSubgraph("one");
+ verify(one).addAttributeNodes("two");
+
+ verify(entityGraph).addSubgraph("eins");
+ Subgraph> eins = entityGraph.addSubgraph("eins");
+ verify(eins).addSubgraph("zwei");
+ Subgraph> zwei = eins.addSubgraph("zwei");
+ verify(zwei).addAttributeNodes("drei");
+ }
+
+ private static class DummyEntity {
+ DummyEntity one;
+ DummyEntity two;
+ DummyEntity eins;
+ DummyEntity zwei;
+ DummyEntity drei;
+ }
+}
diff --git a/src/test/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutorUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutorUnitTests.java
index 49ee607867..1df41b945f 100644
--- a/src/test/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutorUnitTests.java
+++ b/src/test/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutorUnitTests.java
@@ -22,11 +22,13 @@
import java.sql.Date;
import java.time.LocalDate;
import java.util.List;
+import java.util.Set;
import java.util.stream.Stream;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
+import org.hibernate.LazyInitializationException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -405,8 +407,12 @@ void findByFluentPredicateWithInterfaceBasedProjection() {
@Test // GH-2294
void findByFluentPredicateWithSortedInterfaceBasedProjection() {
- List userProjections = predicateExecutor.findBy(user.firstname.contains("v"),
- q -> q.as(UserProjectionInterfaceBased.class).sortBy(Sort.by("firstname")).all());
+ List userProjections = predicateExecutor.findBy( //
+ user.firstname.contains("v"), //
+ q -> q.as(UserProjectionInterfaceBased.class) //
+ .sortBy(Sort.by("firstname")) //
+ .all() //
+ );
assertThat(userProjections).extracting(UserProjectionInterfaceBased::getFirstname)
.containsExactly(dave.getFirstname(), oliver.getFirstname());
@@ -442,7 +448,79 @@ class UserDto {
.findBy(user.firstname.contains("v"), q -> q.as(UserDto.class).sortBy(Sort.by("firstname")).all()));
}
+ @Test // GH-2329
+ void findByFluentPredicateWithSimplePropertyPathsDoesntLoadUnrequestedPaths() {
+
+ // make sure the entities are actually written to the database:
+ em.flush();
+ // make sure we don't get preinitialized entities back:
+ em.clear();
+
+ List users = predicateExecutor.findBy(user.firstname.contains("v"),
+ q -> q.project("firstname", "lastname").all());
+
+ // remove the entities, so lazy loading throws an exception
+ em.clear();
+
+ assertThat(users).extracting(User::getFirstname) //
+ .containsExactlyInAnyOrder( //
+ dave.getFirstname(), //
+ oliver.getFirstname() //
+ );
+
+ assertThatExceptionOfType(LazyInitializationException.class) //
+ .isThrownBy( //
+ () -> users.forEach(u -> u.getRoles().size()) // forces loading of roles
+ );
+ }
+
+ @Test // GH-2329
+ void findByFluentPredicateWithCollectionPropertyPathsLoadsRequestedPaths() {
+
+ // make sure the entities are actually written to the database:
+ em.flush();
+ // make sure we don't get preinitialized entities back:
+ em.clear();
+
+ List users = predicateExecutor.findBy(user.firstname.contains("v"),
+ q -> q.project("firstname", "roles").all());
+
+ // remove the entities, so lazy loading throws an exception
+ em.clear();
+
+ assertThat(users).extracting(User::getFirstname).containsExactlyInAnyOrder( //
+ dave.getFirstname(), //
+ oliver.getFirstname() //
+ );
+
+ assertThat(users).allMatch(u -> u.getRoles().isEmpty());
+
+ }
+
+ @Test // GH-2329
+ void findByFluentPredicateWithComplexPropertyPathsDoesntLoadsRequestedPaths() {
+
+ // make sure the entities are actually written to the database:
+ em.flush();
+ // make sure we don't get preinitialized entities back:
+ em.clear();
+
+ List users = predicateExecutor.findBy(user.firstname.contains("v"), q -> q.project("roles.name").all());
+
+ // remove the entities, so lazy loading throws an exception
+ em.clear();
+
+ assertThat(users).extracting(User::getFirstname).containsExactlyInAnyOrder( //
+ dave.getFirstname(), //
+ oliver.getFirstname() //
+ );
+
+ assertThat(users).allMatch(u -> u.getRoles().isEmpty());
+ }
+
private interface UserProjectionInterfaceBased {
String getFirstname();
+
+ Set getRoles();
}
}