Skip to content

Commit 7a100d2

Browse files
committed
Translate projected properties of the fluent query API into a fetchgraph.
When a property path based projection is specified we still return the root entity. But we do provide a fetchgraph. The JPA implementation will (should) load only the specified attributes eagerly. It most likely will also load all other attributes from all selected tables. Once we have infrastructure in place for for multilevel projections the same approach can and should be used for those. Currently this is not the case. Closes #2329 See spring-projects/spring-data-commons#2420
1 parent 02a9c63 commit 7a100d2

File tree

6 files changed

+336
-35
lines changed

6 files changed

+336
-35
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright 2021 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.support;
17+
18+
import java.util.Set;
19+
20+
import javax.persistence.EntityManager;
21+
import javax.persistence.Subgraph;
22+
23+
import org.springframework.lang.Nullable;
24+
25+
import com.querydsl.jpa.impl.AbstractJPAQuery;
26+
27+
/**
28+
* Realizes projection by property path as fetchgraph hints.
29+
*
30+
* @author Jens Schauder
31+
*
32+
* @since 2.6
33+
*/
34+
class DefaultProjector implements Projector {
35+
36+
private final EntityManager entityManager;
37+
38+
DefaultProjector(EntityManager entityManager) {
39+
this.entityManager = entityManager;
40+
}
41+
42+
@Override
43+
public AbstractJPAQuery<?, ?> apply(Class<?> domainType, AbstractJPAQuery<?, ?> query,
44+
@Nullable Set<String> properties) {
45+
46+
if (!(properties == null || properties.isEmpty())) {
47+
48+
final javax.persistence.EntityGraph<?> entityGraph = entityManager.createEntityGraph(domainType);
49+
50+
for (String property : properties) {
51+
52+
final String[] split = property.split("\\.");
53+
Subgraph<Object> subgraph = null;
54+
55+
for (int i = 0; i < split.length; i++) {
56+
57+
if (i < split.length - 1) {
58+
subgraph = subgraph == null ? entityGraph.addSubgraph(split[i]) : subgraph.addSubgraph(split[i]);
59+
} else {
60+
61+
if (subgraph == null) {
62+
entityGraph.addAttributeNodes(split[i]);
63+
} else {
64+
subgraph.addAttributeNodes(split[i]);
65+
}
66+
67+
}
68+
}
69+
}
70+
71+
query.setHint("javax.persistence.fetchgraph", entityGraph);
72+
}
73+
74+
return query;
75+
}
76+
}

src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryByPredicate.java

+32-24
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
import org.springframework.util.Assert;
3737

3838
import com.querydsl.core.types.Predicate;
39-
import com.querydsl.jpa.JPQLQuery;
39+
import com.querydsl.jpa.impl.AbstractJPAQuery;
4040

4141
/**
4242
* Immutable implementation of {@link FetchableFluentQuery} based on a Querydsl {@link Predicate}. All methods that
@@ -46,30 +46,32 @@
4646
* @param <R> Result type
4747
* @author Greg Turnquist
4848
* @author Mark Paluch
49+
* @author Jens Schauder
4950
* @since 2.6
5051
*/
5152
class FetchableFluentQueryByPredicate<S, R> extends FluentQuerySupport<R> implements FetchableFluentQuery<R> {
5253

5354
private final Predicate predicate;
54-
private final Function<Sort, JPQLQuery<S>> finder;
55-
private final BiFunction<Sort, Pageable, JPQLQuery<S>> pagedFinder;
55+
private final Function<Sort, AbstractJPAQuery<?, ?>> finder;
56+
private final BiFunction<Sort, Pageable, AbstractJPAQuery<?, ?>> pagedFinder;
5657
private final Function<Predicate, Long> countOperation;
5758
private final Function<Predicate, Boolean> existsOperation;
5859
private final Class<S> entityType;
60+
private final Projector projector;
5961

60-
public FetchableFluentQueryByPredicate(Predicate predicate, Class<R> resultType, Function<Sort, JPQLQuery<S>> finder,
61-
BiFunction<Sort, Pageable, JPQLQuery<S>> pagedFinder, Function<Predicate, Long> countOperation,
62-
Function<Predicate, Boolean> existsOperation, Class<S> entityType,
63-
MappingContext<? extends PersistentEntity<?, ?>, ? extends PersistentProperty<?>> context) {
62+
public FetchableFluentQueryByPredicate(Predicate predicate, Class<R> resultType,
63+
Function<Sort, AbstractJPAQuery<?, ?>> finder, BiFunction<Sort, Pageable, AbstractJPAQuery<?, ?>> pagedFinder,
64+
Function<Predicate, Long> countOperation, Function<Predicate, Boolean> existsOperation, Class<S> entityType,
65+
MappingContext<? extends PersistentEntity<?, ?>, ? extends PersistentProperty<?>> context, Projector projector) {
6466
this(predicate, resultType, Sort.unsorted(), null, finder, pagedFinder, countOperation, existsOperation, entityType,
65-
context);
67+
context, projector);
6668
}
6769

6870
private FetchableFluentQueryByPredicate(Predicate predicate, Class<R> resultType, Sort sort,
69-
@Nullable Collection<String> properties, Function<Sort, JPQLQuery<S>> finder,
70-
BiFunction<Sort, Pageable, JPQLQuery<S>> pagedFinder, Function<Predicate, Long> countOperation,
71+
@Nullable Collection<String> properties, Function<Sort, AbstractJPAQuery<?, ?>> finder,
72+
BiFunction<Sort, Pageable, AbstractJPAQuery<?, ?>> pagedFinder, Function<Predicate, Long> countOperation,
7173
Function<Predicate, Boolean> existsOperation, Class<S> entityType,
72-
MappingContext<? extends PersistentEntity<?, ?>, ? extends PersistentProperty<?>> context) {
74+
MappingContext<? extends PersistentEntity<?, ?>, ? extends PersistentProperty<?>> context, Projector projector) {
7375

7476
super(resultType, sort, properties, context);
7577
this.predicate = predicate;
@@ -78,6 +80,7 @@ private FetchableFluentQueryByPredicate(Predicate predicate, Class<R> resultType
7880
this.countOperation = countOperation;
7981
this.existsOperation = existsOperation;
8082
this.entityType = entityType;
83+
this.projector = projector;
8184
}
8285

8386
/*
@@ -90,7 +93,8 @@ public FetchableFluentQuery<R> sortBy(Sort sort) {
9093
Assert.notNull(sort, "Sort must not be null!");
9194

9295
return new FetchableFluentQueryByPredicate<>(this.predicate, this.resultType, this.sort.and(sort), this.properties,
93-
this.finder, this.pagedFinder, this.countOperation, this.existsOperation, this.entityType, this.context);
96+
this.finder, this.pagedFinder, this.countOperation, this.existsOperation, this.entityType, this.context,
97+
this.projector);
9498
}
9599

96100
/*
@@ -106,7 +110,7 @@ public <NR> FetchableFluentQuery<NR> as(Class<NR> resultType) {
106110
}
107111

108112
return new FetchableFluentQueryByPredicate<>(this.predicate, resultType, this.sort, this.properties, this.finder,
109-
this.pagedFinder, this.countOperation, this.existsOperation, this.entityType, this.context);
113+
this.pagedFinder, this.countOperation, this.existsOperation, this.entityType, this.context, this.projector);
110114
}
111115

112116
/*
@@ -118,7 +122,7 @@ public FetchableFluentQuery<R> project(Collection<String> properties) {
118122

119123
return new FetchableFluentQueryByPredicate<>(this.predicate, this.resultType, this.sort,
120124
mergeProperties(properties), this.finder, this.pagedFinder, this.countOperation, this.existsOperation,
121-
this.entityType, this.context);
125+
this.entityType, this.context, this.projector);
122126
}
123127

124128
/*
@@ -128,7 +132,7 @@ public FetchableFluentQuery<R> project(Collection<String> properties) {
128132
@Override
129133
public R oneValue() {
130134

131-
List<S> results = this.finder.apply(this.sort) //
135+
List<?> results = createSortedAndProjectedQuery() //
132136
.limit(2) // Never need more than 2 values
133137
.fetch();
134138

@@ -146,7 +150,7 @@ public R oneValue() {
146150
@Override
147151
public R firstValue() {
148152

149-
List<S> results = this.finder.apply(this.sort) //
153+
List<?> results = createSortedAndProjectedQuery() //
150154
.limit(1) // Never need more than 1 value
151155
.fetch();
152156

@@ -159,9 +163,7 @@ public R firstValue() {
159163
*/
160164
@Override
161165
public List<R> all() {
162-
163-
JPQLQuery<S> query = this.finder.apply(this.sort);
164-
return convert(query.fetch());
166+
return convert(createSortedAndProjectedQuery().fetch());
165167
}
166168

167169
/*
@@ -180,11 +182,17 @@ public Page<R> page(Pageable pageable) {
180182
@Override
181183
public Stream<R> stream() {
182184

183-
return this.finder.apply(this.sort) //
185+
return createSortedAndProjectedQuery() //
184186
.stream() //
185187
.map(getConversionFunction());
186188
}
187189

190+
private AbstractJPAQuery<?, ?> createSortedAndProjectedQuery() {
191+
192+
final AbstractJPAQuery<?, ?> query = this.finder.apply(this.sort);
193+
return projector.apply(entityType, query, properties);
194+
}
195+
188196
/*
189197
* (non-Javadoc)
190198
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#count()
@@ -205,19 +213,19 @@ public boolean exists() {
205213

206214
private Page<R> readPage(Pageable pageable) {
207215

208-
JPQLQuery<S> pagedQuery = this.pagedFinder.apply(this.sort, pageable);
216+
AbstractJPAQuery<?, ?> pagedQuery = this.pagedFinder.apply(this.sort, pageable);
209217
List<R> paginatedResults = convert(pagedQuery.fetch());
210218

211219
return PageableExecutionUtils.getPage(paginatedResults, pageable, () -> this.countOperation.apply(this.predicate));
212220
}
213221

214-
private List<R> convert(List<S> resultList) {
222+
private List<R> convert(List<?> resultList) {
215223

216224
Function<Object, R> conversionFunction = getConversionFunction();
217225
List<R> mapped = new ArrayList<>(resultList.size());
218226

219-
for (S s : resultList) {
220-
mapped.add(conversionFunction.apply(s));
227+
for (Object o : resultList) {
228+
mapped.add(conversionFunction.apply(o));
221229
}
222230

223231
return mapped;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright 2021 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.support;
17+
18+
import com.querydsl.jpa.impl.AbstractJPAQuery;
19+
import org.springframework.lang.Nullable;
20+
21+
import java.util.Set;
22+
23+
/**
24+
* Changes a query to produce the requested projection or something that can be mapped to it.
25+
*
26+
* @author Jens Schauder
27+
*
28+
* @since 2.6
29+
*/
30+
public interface Projector {
31+
32+
AbstractJPAQuery<?, ?> apply(Class<?> domainType, AbstractJPAQuery<?, ?> query, @Nullable Set<String> properties);
33+
}

src/main/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutor.java

+13-9
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public class QuerydslJpaPredicateExecutor<T> implements QuerydslPredicateExecuto
6969
/**
7070
* Creates a new {@link QuerydslJpaPredicateExecutor} from the given domain class and {@link EntityManager} and uses
7171
* the given {@link EntityPathResolver} to translate the domain class into an {@link EntityPath}.
72-
*
72+
*
7373
* @param entityInformation must not be {@literal null}.
7474
* @param entityManager must not be {@literal null}.
7575
* @param resolver must not be {@literal null}.
@@ -178,27 +178,29 @@ public <S extends T, R> R findBy(Predicate predicate, Function<FetchableFluentQu
178178
Assert.notNull(predicate, "Predicate must not be null!");
179179
Assert.notNull(queryFunction, "Query function must not be null!");
180180

181-
Function<Sort, JPQLQuery<T>> finder = sort -> {
182-
JPQLQuery<T> select = createQuery(predicate).select(path);
181+
Function<Sort, AbstractJPAQuery<?, ?>> finder = sort -> {
182+
AbstractJPAQuery<?, ?> select = (AbstractJPAQuery<?, ?>) createQuery(predicate).select(path);
183183

184184
if (sort != null) {
185-
select = querydsl.applySorting(sort, select);
185+
select = (AbstractJPAQuery<?, ?>) querydsl.applySorting(sort, select);
186186
}
187187

188188
return select;
189189
};
190190

191-
BiFunction<Sort, Pageable, JPQLQuery<T>> pagedFinder = (sort, pageable) -> {
191+
BiFunction<Sort, Pageable, AbstractJPAQuery<?, ?>> pagedFinder = (sort, pageable) -> {
192192

193-
JPQLQuery<T> select = finder.apply(sort);
193+
AbstractJPAQuery<?, ?> select = finder.apply(sort);
194194

195195
if (pageable.isPaged()) {
196-
select = querydsl.applyPagination(pageable, select);
196+
select = (AbstractJPAQuery<?, ?>) querydsl.applyPagination(pageable, select);
197197
}
198198

199199
return select;
200200
};
201201

202+
Projector projector = new DefaultProjector(entityManager);
203+
202204
FetchableFluentQueryByPredicate<T, T> fluentQuery = new FetchableFluentQueryByPredicate<>( //
203205
predicate, //
204206
entityInformation.getJavaType(), //
@@ -207,7 +209,8 @@ public <S extends T, R> R findBy(Predicate predicate, Function<FetchableFluentQu
207209
this::count, //
208210
this::exists, //
209211
this.entityInformation.getJavaType(), //
210-
new JpaMetamodelMappingContext(Collections.singleton(this.entityManager.getMetamodel())) //
212+
new JpaMetamodelMappingContext(Collections.singleton(this.entityManager.getMetamodel())), //
213+
projector //
211214
);
212215

213216
return queryFunction.apply((FetchableFluentQuery<S>) fluentQuery);
@@ -237,7 +240,7 @@ public boolean exists(Predicate predicate) {
237240
* @param predicate
238241
* @return the Querydsl {@link JPQLQuery}.
239242
*/
240-
protected JPQLQuery<?> createQuery(Predicate... predicate) {
243+
protected AbstractJPAQuery<?, ?> createQuery(Predicate... predicate) {
241244

242245
Assert.notNull(predicate, "Predicate must not be null!");
243246

@@ -322,4 +325,5 @@ private List<T> executeSorted(JPQLQuery<T> query, OrderSpecifier<?>... orders) {
322325
private List<T> executeSorted(JPQLQuery<T> query, Sort sort) {
323326
return querydsl.applySorting(sort, query).fetch();
324327
}
328+
325329
}

0 commit comments

Comments
 (0)