From 390a82bf21642a8bbfb1c2bc183ffb45d3b5beb3 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 28 Jul 2021 10:19:40 +0200 Subject: [PATCH] Add FluentQuery support to QueryByExampleExecutor and ReactiveQueryByExampleExecutor. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FluentQuery allows extending a query specification initially defined by a Example probe or a Querydsl Predicate and fetching the actual result through a functional programming model: interface PersonRepository extends QuerydslPredicateExecutor { R findBy(Predicate predicate, Function, R> queryFunction); } PersonRepository repo = …; List result = repo.findBy(QPerson.person.name.eq("Walter"), q -> q.sort(Sort.by("lastname")).as(PersonProjection.class).all()); --- .../querydsl/QuerydslPredicateExecutor.java | 13 + .../ReactiveQuerydslPredicateExecutor.java | 17 ++ .../data/repository/query/FluentQuery.java | 224 ++++++++++++++++++ .../query/QueryByExampleExecutor.java | 12 + .../query/ReactiveQueryByExampleExecutor.java | 16 ++ 5 files changed, 282 insertions(+) create mode 100644 src/main/java/org/springframework/data/repository/query/FluentQuery.java diff --git a/src/main/java/org/springframework/data/querydsl/QuerydslPredicateExecutor.java b/src/main/java/org/springframework/data/querydsl/QuerydslPredicateExecutor.java index e63170a10c..c9f1311dc7 100644 --- a/src/main/java/org/springframework/data/querydsl/QuerydslPredicateExecutor.java +++ b/src/main/java/org/springframework/data/querydsl/QuerydslPredicateExecutor.java @@ -16,10 +16,12 @@ package org.springframework.data.querydsl; import java.util.Optional; +import java.util.function.Function; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; +import org.springframework.data.repository.query.FluentQuery; import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.Predicate; @@ -108,4 +110,15 @@ public interface QuerydslPredicateExecutor { * @return {@literal true} if the data store contains elements that match the given {@link Predicate}. */ boolean exists(Predicate predicate); + + /** + * Returns entities matching the given {@link Predicate} applying the {@link Function queryFunction} that defines the + * query and its result type. + * + * @param predicate must not be {@literal null}. + * @param queryFunction the query function defining projection, sorting, and the result type + * @return all entities matching the given {@link Predicate}. + * @since 2.6 + */ + R findBy(Predicate predicate, Function, R> queryFunction); } diff --git a/src/main/java/org/springframework/data/querydsl/ReactiveQuerydslPredicateExecutor.java b/src/main/java/org/springframework/data/querydsl/ReactiveQuerydslPredicateExecutor.java index 42e375a46c..bc654a1fe2 100644 --- a/src/main/java/org/springframework/data/querydsl/ReactiveQuerydslPredicateExecutor.java +++ b/src/main/java/org/springframework/data/querydsl/ReactiveQuerydslPredicateExecutor.java @@ -18,7 +18,12 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import java.util.function.Function; + +import org.reactivestreams.Publisher; + import org.springframework.data.domain.Sort; +import org.springframework.data.repository.query.FluentQuery; import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.Predicate; @@ -127,4 +132,16 @@ public interface ReactiveQuerydslPredicateExecutor { * @throws IllegalArgumentException if the required parameter is {@literal null}. */ Mono exists(Predicate predicate); + + /** + * Returns entities matching the given {@link Predicate} applying the {@link Function queryFunction} that defines the + * query and its result type. + * + * @param predicate must not be {@literal null}. + * @param queryFunction the query function defining projection, sorting, and the result type + * @return all entities matching the given {@link Predicate}. + * @since 2.6 + */ + > P findBy(Predicate predicate, + Function, P> queryFunction); } diff --git a/src/main/java/org/springframework/data/repository/query/FluentQuery.java b/src/main/java/org/springframework/data/repository/query/FluentQuery.java new file mode 100644 index 0000000000..5eafb3f51e --- /dev/null +++ b/src/main/java/org/springframework/data/repository/query/FluentQuery.java @@ -0,0 +1,224 @@ +/* + * 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.repository.query; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.stream.Stream; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.lang.Nullable; + +/** + * Fluent interface to define a query along with projection and sorting. + * + * @author Mark Paluch + * @since 2.6 + */ +public interface FluentQuery { + + /** + * Define the sort order. + * + * @param sort must not be {@code null}. + * @return a new instance of {@link FluentQuery}. + * @throws IllegalArgumentException if resultType is {@code null}. + */ + FluentQuery sortBy(Sort sort); + + /** + * Define the target type the result should be mapped to. Skip this step if you are only interested in the original + * domain type. + * + * @param resultType must not be {@code null}. + * @param result type. + * @return a new instance of {@link FluentQuery}. + * @throws IllegalArgumentException if resultType is {@code null}. + */ + FluentQuery as(Class resultType); + + /** + * Define which properties or property paths to include in the query. + * + * @param properties must not be {@code null}. + * @return a new instance of {@link FluentQuery}. + * @throws IllegalArgumentException if fields is {@code null}. + */ + default FluentQuery project(String... properties) { + return project(Arrays.asList(properties)); + } + + /** + * Define which properties or property paths to include in the query. + * + * @param properties must not be {@code null}. + * @return a new instance of {@link FluentQuery}. + * @throws IllegalArgumentException if fields is {@code null}. + */ + FluentQuery project(Collection properties); + + /** + * Fetchable extension {@link FluentQuery} allowing to materialize results from the underlying query. + * + * @author Mark Paluch + * @since 2.6 + */ + interface FetchableFluentQuery extends FluentQuery { + + @Override + FetchableFluentQuery sortBy(Sort sort); + + @Override + FetchableFluentQuery as(Class resultType); + + @Override + default FetchableFluentQuery project(String... properties) { + return project(Arrays.asList(properties)); + } + + @Override + FetchableFluentQuery project(Collection properties); + + /** + * Get exactly zero or one result. + * + * @return {@code null} if no match found. + * @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one match found. + */ + @Nullable + T one(); + + /** + * Get the first or no result. + * + * @return {@code null} if no match found. + */ + @Nullable + T first(); + + /** + * Get all matching elements. + * + * @return + */ + List all(); + + /** + * Get a page of matching elements for {@link Pageable}. + * + * @param pageable must not be {@code null}. The given {@link Pageable} will override any previously specified + * {@link Sort sort} if the {@link Sort} object is not {@link Sort#isUnsorted()}. + * @return + */ + Page page(Pageable pageable); + + /** + * Stream all matching elements. + * + * @return a {@link Stream} wrapping cursors that need to be closed. + */ + Stream stream(); + + /** + * Get the number of matching elements. + * + * @return total number of matching elements. + */ + long count(); + + /** + * Check for the presence of matching elements. + * + * @return {@literal true} if at least one matching element exists. + */ + boolean exists(); + } + + /** + * Reactive extension {@link FluentQuery} allowing to materialize results from the underlying query. + * + * @author Mark Paluch + * @since 2.6 + */ + interface ReactiveFluentQuery extends FluentQuery { + + @Override + ReactiveFluentQuery sortBy(Sort sort); + + @Override + ReactiveFluentQuery as(Class resultType); + + @Override + default ReactiveFluentQuery project(String... properties) { + return project(Arrays.asList(properties)); + } + + @Override + ReactiveFluentQuery project(Collection properties); + + /** + * Get exactly zero or one result. + * + * @return {@code null} if no match found. + * @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one match found. + */ + Mono one(); + + /** + * Get the first or no result. + * + * @return {@code null} if no match found. + */ + Mono first(); + + /** + * Get all matching elements. + * + * @return + */ + Flux all(); + + /** + * Get a page of matching elements for {@link Pageable}. + * + * @param pageable must not be {@code null}. The given {@link Pageable} will override any previously specified + * {@link Sort sort} if the {@link Sort} object is not {@link Sort#isUnsorted()}. + * @return + */ + Mono> page(Pageable pageable); + + /** + * Get the number of matching elements. + * + * @return total number of matching elements. + */ + Mono count(); + + /** + * Check for the presence of matching elements. + * + * @return {@literal true} if at least one matching element exists. + */ + Mono exists(); + + } +} diff --git a/src/main/java/org/springframework/data/repository/query/QueryByExampleExecutor.java b/src/main/java/org/springframework/data/repository/query/QueryByExampleExecutor.java index 64b95c7306..71a8831742 100644 --- a/src/main/java/org/springframework/data/repository/query/QueryByExampleExecutor.java +++ b/src/main/java/org/springframework/data/repository/query/QueryByExampleExecutor.java @@ -16,6 +16,7 @@ package org.springframework.data.repository.query; import java.util.Optional; +import java.util.function.Function; import org.springframework.data.domain.Example; import org.springframework.data.domain.Page; @@ -86,4 +87,15 @@ public interface QueryByExampleExecutor { * @return {@literal true} if the data store contains elements that match the given {@link Example}. */ boolean exists(Example example); + + /** + * Returns entities matching the given {@link Example} applying the {@link Function queryFunction} that defines the + * query and its result type. + * + * @param example must not be {@literal null}. + * @param queryFunction the query function defining projection, sorting, and the result type + * @return all entities matching the given {@link Example}. + * @since 2.6 + */ + R findBy(Example example, Function, R> queryFunction); } diff --git a/src/main/java/org/springframework/data/repository/query/ReactiveQueryByExampleExecutor.java b/src/main/java/org/springframework/data/repository/query/ReactiveQueryByExampleExecutor.java index 44518a5aea..e69c0611c5 100644 --- a/src/main/java/org/springframework/data/repository/query/ReactiveQueryByExampleExecutor.java +++ b/src/main/java/org/springframework/data/repository/query/ReactiveQueryByExampleExecutor.java @@ -18,6 +18,10 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import java.util.function.Function; + +import org.reactivestreams.Publisher; + import org.springframework.data.domain.Example; import org.springframework.data.domain.Sort; @@ -75,4 +79,16 @@ public interface ReactiveQueryByExampleExecutor { * @return {@literal true} if the data store contains elements that match the given {@link Example}. */ Mono exists(Example example); + + /** + * Returns entities matching the given {@link Example} applying the {@link Function queryFunction} that defines the + * query and its result type. + * + * @param example must not be {@literal null}. + * @param queryFunction the query function defining projection, sorting, and the result type + * @return all entities matching the given {@link Example}. + * @since 2.6 + */ + > P findBy(Example example, + Function, P> queryFunction); }