Skip to content

Commit

Permalink
Document that fluent findBy(…) queries must return a result.
Browse files Browse the repository at this point in the history
Closes #3294
  • Loading branch information
mp911de committed Jan 22, 2025
1 parent 74af5c1 commit 729b00d
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.Optional;
import java.util.function.Function;

import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
Expand Down Expand Up @@ -190,6 +191,10 @@ default long delete(PredicateSpecification<T> spec) {
/**
* Returns entities matching the given {@link Specification} applying the {@code queryFunction} that defines the query
* and its result type.
* <p>
* The query object used with {@code queryFunction} is only valid inside the {@code findBy(…)} method call. This
* requires the query function to return a query result and not the {@link FluentQuery} object itself to ensure the
* query is executed inside the {@code findBy(…)} method.
*
* @param spec must not be null.
* @param queryFunction the query function defining projection, sorting, and the result type
Expand All @@ -204,11 +209,16 @@ default <S extends T, R> R findBy(PredicateSpecification<T> spec,
/**
* Returns entities matching the given {@link Specification} applying the {@code queryFunction} that defines the query
* and its result type.
* <p>
* The query object used with {@code queryFunction} is only valid inside the {@code findBy(…)} method call. This
* requires the query function to return a query result and not the {@link FluentQuery} object itself to ensure the
* query is executed inside the {@code findBy(…)} method.
*
* @param spec must not be null.
* @param queryFunction the query function defining projection, sorting, and the result type
* @return all entities matching the given Example.
* @return all entities matching the given specification.
* @since 3.0
* @throws InvalidDataAccessApiUsageException if the query function returns the {@link FluentQuery} instance.
*/
<S extends T, R> R findBy(Specification<T> spec, Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.util.function.Function;

import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.domain.KeysetScrollPosition;
import org.springframework.data.domain.OffsetScrollPosition;
import org.springframework.data.domain.Page;
Expand All @@ -40,6 +41,7 @@
import org.springframework.data.querydsl.EntityPathResolver;
import org.springframework.data.querydsl.QSort;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.repository.query.FluentQuery;
import org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery;
import org.springframework.data.support.PageableExecutionUtils;
import org.springframework.lang.Nullable;
Expand Down Expand Up @@ -235,8 +237,7 @@ public <S extends T, R> R findBy(Predicate predicate, Function<FetchableFluentQu
};

FetchableFluentQueryByPredicate<T, T> fluentQuery = new FetchableFluentQueryByPredicate<>( //
path,
predicate, //
path, predicate, //
this.entityInformation, //
finder, //
scroll, //
Expand All @@ -246,7 +247,14 @@ public <S extends T, R> R findBy(Predicate predicate, Function<FetchableFluentQu
entityManager, //
getProjectionFactory());

return queryFunction.apply((FetchableFluentQuery<S>) fluentQuery);
R result = queryFunction.apply((FetchableFluentQuery<S>) fluentQuery);

if (result instanceof FluentQuery<?>) {
throw new InvalidDataAccessApiUsageException(
"findBy(…) queries must result a query result and not the FluentQuery object to ensure that queries are executed within the scope of the findBy(…) method");
}

return result;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import java.util.function.BiConsumer;
import java.util.function.Function;

import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.KeysetScrollPosition;
import org.springframework.data.domain.OffsetScrollPosition;
Expand All @@ -65,6 +66,7 @@
import org.springframework.data.mapping.PropertyPath;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.repository.query.FluentQuery;
import org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery;
import org.springframework.data.repository.query.ReturnedType;
import org.springframework.data.support.PageableExecutionUtils;
Expand Down Expand Up @@ -542,7 +544,14 @@ private <S extends T, R> R doFindBy(Specification<T> spec, Class<T> domainClass,
FetchableFluentQueryBySpecification<?, T> fluentQuery = new FetchableFluentQueryBySpecification<>(spec, domainClass,
finder, scrollDelegate, this::count, this::exists, this.entityManager, getProjectionFactory());

return queryFunction.apply((FetchableFluentQuery<S>) fluentQuery);
R result = queryFunction.apply((FetchableFluentQuery<S>) fluentQuery);

if (result instanceof FluentQuery<?>) {
throw new InvalidDataAccessApiUsageException(
"findBy(…) queries must result a query result and not the FluentQuery object to ensure that queries are executed within the scope of the findBy(…) method");
}

return result;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.IntStream;
import java.util.stream.Stream;

Expand Down Expand Up @@ -2427,6 +2428,14 @@ void findByFluentExampleWithSorting() {
assertThat(users).containsExactly(thirdUser, firstUser, fourthUser);
}

@Test // GH-3294
void findByFluentFailsReturningFluentQuery() {

User prototype = new User();
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
.isThrownBy(() -> repository.findBy(of(prototype), Function.identity()));
}

@Test // GH-2294
void findByFluentExampleFirstValue() {

Expand Down

0 comments on commit 729b00d

Please sign in to comment.