Skip to content

Commit

Permalink
Translate projected properties of the fluent query API into a fetchgr…
Browse files Browse the repository at this point in the history
…aph.

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
Original pull request: #2345.
  • Loading branch information
schauder authored and mp911de committed Oct 29, 2021
1 parent 02a9c63 commit a39cb24
Show file tree
Hide file tree
Showing 10 changed files with 513 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -36,7 +37,6 @@
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;

/**
Expand All @@ -47,38 +47,42 @@
* @param <R> Result type
* @author Greg Turnquist
* @author Mark Paluch
* @author Jens Schauder
* @since 2.6
*/
class FetchableFluentQueryByExample<S, R> extends FluentQuerySupport<R> implements FetchableFluentQuery<R> {
class FetchableFluentQueryByExample<S, R> extends FluentQuerySupport<S, R> implements FetchableFluentQuery<R> {

private final Example<S> example;
private final Function<Sort, TypedQuery<S>> finder;
private final Function<Example<S>, Long> countOperation;
private final Function<Example<S>, Boolean> existsOperation;
private final EntityManager entityManager;
private final EscapeCharacter escapeCharacter;
private final Projector<TypedQuery<?>> projector;

public FetchableFluentQueryByExample(Example<S> example, Function<Sort, TypedQuery<S>> finder,
Function<Example<S>, Long> countOperation, Function<Example<S>, Boolean> existsOperation,
MappingContext<? extends PersistentEntity<?, ?>, ? extends PersistentProperty<?>> context,
EntityManager entityManager, EscapeCharacter escapeCharacter) {
this(example, (Class<R>) example.getProbeType(), Sort.unsorted(), null, finder, countOperation, existsOperation,
context, entityManager, escapeCharacter);
this(example, example.getProbeType(), (Class<R>) example.getProbeType(), Sort.unsorted(), Collections.emptySet(),
finder, countOperation, existsOperation, context, entityManager, escapeCharacter,
new TypedQueryProjector(entityManager));
}

private FetchableFluentQueryByExample(Example<S> example, Class<R> returnType, Sort sort,
@Nullable Collection<String> properties, Function<Sort, TypedQuery<S>> finder,
Function<Example<S>, Long> countOperation, Function<Example<S>, Boolean> existsOperation,
private FetchableFluentQueryByExample(Example<S> example, Class<S> entityType, Class<R> returnType, Sort sort,
Collection<String> properties, Function<Sort, TypedQuery<S>> finder, Function<Example<S>, Long> countOperation,
Function<Example<S>, Boolean> existsOperation,
MappingContext<? extends PersistentEntity<?, ?>, ? extends PersistentProperty<?>> context,
EntityManager entityManager, EscapeCharacter escapeCharacter) {
EntityManager entityManager, EscapeCharacter escapeCharacter, Projector<TypedQuery<?>> projector) {

super(returnType, sort, properties, context);
super(returnType, sort, properties, context, entityType);
this.example = example;
this.finder = finder;
this.countOperation = countOperation;
this.existsOperation = existsOperation;
this.entityManager = entityManager;
this.escapeCharacter = escapeCharacter;
this.projector = projector;
}

/*
Expand All @@ -90,8 +94,9 @@ public FetchableFluentQuery<R> 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, context, entityManager, escapeCharacter,
new TypedQueryProjector(entityManager));
}

/*
Expand All @@ -106,8 +111,9 @@ public <NR> FetchableFluentQuery<NR> as(Class<NR> 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, context, entityManager, escapeCharacter,
new TypedQueryProjector(entityManager));
}

/*
Expand All @@ -117,8 +123,9 @@ public <NR> FetchableFluentQuery<NR> as(Class<NR> resultType) {
@Override
public FetchableFluentQuery<R> project(Collection<String> 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, context, entityManager, escapeCharacter,
new TypedQueryProjector(entityManager));
}

/*
Expand All @@ -128,7 +135,7 @@ public FetchableFluentQuery<R> project(Collection<String> properties) {
@Override
public R oneValue() {

TypedQuery<S> limitedQuery = this.finder.apply(this.sort);
TypedQuery<S> limitedQuery = createSortedAndProjectedQuery();
limitedQuery.setMaxResults(2); // Never need more than 2 values

List<S> results = limitedQuery.getResultList();
Expand All @@ -147,7 +154,7 @@ public R oneValue() {
@Override
public R firstValue() {

TypedQuery<S> limitedQuery = this.finder.apply(this.sort);
TypedQuery<S> limitedQuery = createSortedAndProjectedQuery();
limitedQuery.setMaxResults(1); // Never need more than 1 value

List<S> results = limitedQuery.getResultList();
Expand All @@ -162,7 +169,7 @@ public R firstValue() {
@Override
public List<R> all() {

List<S> resultList = this.finder.apply(this.sort).getResultList();
List<S> resultList = createSortedAndProjectedQuery().getResultList();

return convert(resultList);
}
Expand All @@ -183,7 +190,7 @@ public Page<R> page(Pageable pageable) {
@Override
public Stream<R> stream() {

return this.finder.apply(this.sort) //
return createSortedAndProjectedQuery() //
.getResultStream() //
.map(getConversionFunction());
}
Expand All @@ -194,7 +201,7 @@ public Stream<R> stream() {
*/
@Override
public long count() {
return this.countOperation.apply(example);
return countOperation.apply(example);
}

/*
Expand All @@ -203,12 +210,12 @@ public long count() {
*/
@Override
public boolean exists() {
return this.existsOperation.apply(example);
return existsOperation.apply(example);
}

private Page<R> readPage(Pageable pageable) {

TypedQuery<S> pagedQuery = this.finder.apply(this.sort);
TypedQuery<S> pagedQuery = createSortedAndProjectedQuery();

if (pageable.isPaged()) {
pagedQuery.setFirstResult((int) pageable.getOffset());
Expand All @@ -217,7 +224,15 @@ private Page<R> readPage(Pageable pageable) {

List<R> paginatedResults = convert(pagedQuery.getResultList());

return PageableExecutionUtils.getPage(paginatedResults, pageable, () -> this.countOperation.apply(this.example));
return PageableExecutionUtils.getPage(paginatedResults, pageable, () -> countOperation.apply(example));
}

private TypedQuery<S> createSortedAndProjectedQuery() {

TypedQuery<S> query = finder.apply(sort);
projector.apply(entityType, query, properties);

return query;
}

private List<R> convert(List<S> resultList) {
Expand All @@ -232,7 +247,7 @@ private List<R> convert(List<S> resultList) {
}

private Function<Object, R> getConversionFunction() {
return getConversionFunction(this.example.getProbeType(), this.resultType);
return getConversionFunction(example.getProbeType(), resultType);
}

}
Loading

0 comments on commit a39cb24

Please sign in to comment.