Skip to content

Commit 9672b30

Browse files
committed
Document that fluent findBy(…) queries must return a result.
Closes #3294
1 parent f0f957b commit 9672b30

File tree

4 files changed

+41
-8
lines changed

4 files changed

+41
-8
lines changed

Diff for: spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/JpaSpecificationExecutor.java

+7-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.Optional;
2424
import java.util.function.Function;
2525

26+
import org.springframework.dao.InvalidDataAccessApiUsageException;
2627
import org.springframework.data.domain.Page;
2728
import org.springframework.data.domain.Pageable;
2829
import org.springframework.data.domain.Sort;
@@ -122,11 +123,16 @@ public interface JpaSpecificationExecutor<T> {
122123
/**
123124
* Returns entities matching the given {@link Specification} applying the {@code queryFunction} that defines the query
124125
* and its result type.
126+
* <p>
127+
* The query object used with {@code queryFunction} is only valid inside the {@code findBy(…)} method call. This
128+
* requires the query function to return a query result and not the {@link FluentQuery} object itself to ensure the
129+
* query is executed inside the {@code findBy(…)} method.
125130
*
126131
* @param spec must not be null.
127132
* @param queryFunction the query function defining projection, sorting, and the result type
128-
* @return all entities matching the given Example.
133+
* @return all entities matching the given specification.
129134
* @since 3.0
135+
* @throws InvalidDataAccessApiUsageException if the query function returns the {@link FluentQuery} instance.
130136
*/
131137
<S extends T, R> R findBy(Specification<T> spec, Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction);
132138

Diff for: spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutor.java

+10-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.function.Function;
2525

2626
import org.springframework.dao.IncorrectResultSizeDataAccessException;
27+
import org.springframework.dao.InvalidDataAccessApiUsageException;
2728
import org.springframework.data.domain.KeysetScrollPosition;
2829
import org.springframework.data.domain.OffsetScrollPosition;
2930
import org.springframework.data.domain.Page;
@@ -41,6 +42,7 @@
4142
import org.springframework.data.querydsl.EntityPathResolver;
4243
import org.springframework.data.querydsl.QSort;
4344
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
45+
import org.springframework.data.repository.query.FluentQuery;
4446
import org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery;
4547
import org.springframework.data.support.PageableExecutionUtils;
4648
import org.springframework.lang.Nullable;
@@ -245,7 +247,14 @@ public <S extends T, R> R findBy(Predicate predicate, Function<FetchableFluentQu
245247
entityManager, //
246248
getProjectionFactory());
247249

248-
return queryFunction.apply((FetchableFluentQuery<S>) fluentQuery);
250+
R result = queryFunction.apply((FetchableFluentQuery<S>) fluentQuery);
251+
252+
if (result instanceof FluentQuery<?>) {
253+
throw new InvalidDataAccessApiUsageException(
254+
"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");
255+
}
256+
257+
return result;
249258
}
250259

251260
@Override

Diff for: spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java

+10-1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import java.util.function.BiConsumer;
4343
import java.util.function.Function;
4444

45+
import org.springframework.dao.InvalidDataAccessApiUsageException;
4546
import org.springframework.data.domain.Example;
4647
import org.springframework.data.domain.KeysetScrollPosition;
4748
import org.springframework.data.domain.OffsetScrollPosition;
@@ -62,6 +63,7 @@
6263
import org.springframework.data.jpa.support.PageableUtils;
6364
import org.springframework.data.projection.ProjectionFactory;
6465
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
66+
import org.springframework.data.repository.query.FluentQuery;
6567
import org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery;
6668
import org.springframework.data.support.PageableExecutionUtils;
6769
import org.springframework.data.util.ProxyUtils;
@@ -539,7 +541,14 @@ private <S extends T, R> R doFindBy(Specification<T> spec, Class<T> domainClass,
539541
FetchableFluentQueryBySpecification<?, T> fluentQuery = new FetchableFluentQueryBySpecification<>(spec, domainClass,
540542
finder, scrollDelegate, this::count, this::exists, this.entityManager, getProjectionFactory());
541543

542-
return queryFunction.apply((FetchableFluentQuery<S>) fluentQuery);
544+
R result = queryFunction.apply((FetchableFluentQuery<S>) fluentQuery);
545+
546+
if (result instanceof FluentQuery<?>) {
547+
throw new InvalidDataAccessApiUsageException(
548+
"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");
549+
}
550+
551+
return result;
543552
}
544553

545554
@Override

Diff for: spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java

+14-5
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import java.util.Map;
4242
import java.util.Optional;
4343
import java.util.Set;
44+
import java.util.function.Function;
4445
import java.util.stream.IntStream;
4546
import java.util.stream.Stream;
4647

@@ -2352,6 +2353,14 @@ void findByFluentExampleWithSorting() {
23522353
assertThat(users).containsExactly(thirdUser, firstUser, fourthUser);
23532354
}
23542355

2356+
@Test // GH-3294
2357+
void findByFluentFailsReturningFluentQuery() {
2358+
2359+
User prototype = new User();
2360+
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
2361+
.isThrownBy(() -> repository.findBy(of(prototype), Function.identity()));
2362+
}
2363+
23552364
@Test // GH-2294
23562365
void findByFluentExampleFirstValue() {
23572366

@@ -2449,13 +2458,13 @@ void findByFluentExampleWithInterfaceBasedProjectionUsingSpEL() {
24492458
prototype.setFirstname("v");
24502459

24512460
List<UserProjectionUsingSpEL> users = repository.findBy(
2452-
of(prototype,
2453-
matching().withIgnorePaths("age", "createdAt", "active").withMatcher("firstname",
2454-
GenericPropertyMatcher::contains)), //
2455-
q -> q.as(UserProjectionUsingSpEL.class).all());
2461+
of(prototype,
2462+
matching().withIgnorePaths("age", "createdAt", "active").withMatcher("firstname",
2463+
GenericPropertyMatcher::contains)), //
2464+
q -> q.as(UserProjectionUsingSpEL.class).all());
24562465

24572466
assertThat(users).extracting(UserProjectionUsingSpEL::hello)
2458-
.contains(new GreetingsFrom().groot(firstUser.getFirstname()));
2467+
.contains(new GreetingsFrom().groot(firstUser.getFirstname()));
24592468
}
24602469

24612470
@Test // GH-2294

0 commit comments

Comments
 (0)