Skip to content

Commit a53f4ac

Browse files
committed
Add support for fluent Query by Example query definition.
Closes: #663
1 parent 944577d commit a53f4ac

File tree

4 files changed

+510
-0
lines changed

4 files changed

+510
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
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.r2dbc.repository.support;
17+
18+
import java.util.ArrayList;
19+
import java.util.Collection;
20+
import java.util.List;
21+
22+
import org.springframework.data.domain.Sort;
23+
import org.springframework.data.repository.query.FluentQuery;
24+
import org.springframework.util.Assert;
25+
26+
/**
27+
* Support class for {@link org.springframework.data.repository.query.FluentQuery.ReactiveFluentQuery} implementations.
28+
*
29+
* @author Mark Paluch
30+
* @since 1.4
31+
*/
32+
abstract class ReactiveFluentQuerySupport<P, T> implements FluentQuery.ReactiveFluentQuery<T> {
33+
34+
private final P predicate;
35+
private final Sort sort;
36+
private final Class<T> resultType;
37+
private final List<String> fieldsToInclude;
38+
39+
ReactiveFluentQuerySupport(P predicate, Sort sort, Class<T> resultType, List<String> fieldsToInclude) {
40+
this.predicate = predicate;
41+
this.sort = sort;
42+
this.resultType = resultType;
43+
this.fieldsToInclude = fieldsToInclude;
44+
}
45+
46+
/*
47+
* (non-Javadoc)
48+
* @see org.springframework.data.repository.query.FluentQuery.ReactiveFluentQuery#sortBy(org.springframework.data.domain.Sort)
49+
*/
50+
@Override
51+
public ReactiveFluentQuery<T> sortBy(Sort sort) {
52+
53+
Assert.notNull(sort, "Sort must not be null!");
54+
55+
return create(predicate, sort, resultType, fieldsToInclude);
56+
}
57+
58+
/*
59+
* (non-Javadoc)
60+
* @see org.springframework.data.repository.query.FluentQuery.ReactiveFluentQuery#as(java.lang.Class)
61+
*/
62+
@Override
63+
public <R> ReactiveFluentQuery<R> as(Class<R> projection) {
64+
65+
Assert.notNull(projection, "Projection target type must not be null!");
66+
67+
return create(predicate, sort, projection, fieldsToInclude);
68+
}
69+
70+
/*
71+
* (non-Javadoc)
72+
* @see org.springframework.data.repository.query.FluentQuery.ReactiveFluentQuery#project(java.util.Collection)
73+
*/
74+
@Override
75+
public ReactiveFluentQuery<T> project(Collection<String> properties) {
76+
77+
Assert.notNull(properties, "Projection properties must not be null!");
78+
79+
return create(predicate, sort, resultType, new ArrayList<>(properties));
80+
}
81+
82+
protected abstract <R> ReactiveFluentQuerySupport<P, R> create(P predicate, Sort sort, Class<R> resultType,
83+
List<String> fieldsToInclude);
84+
85+
P getPredicate() {
86+
return predicate;
87+
}
88+
89+
Sort getSort() {
90+
return sort;
91+
}
92+
93+
Class<T> getResultType() {
94+
return resultType;
95+
}
96+
97+
List<String> getFieldsToInclude() {
98+
return fieldsToInclude;
99+
}
100+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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.r2dbc.repository.support;
17+
18+
import reactor.core.publisher.Mono;
19+
20+
import java.util.List;
21+
22+
import org.springframework.data.domain.Page;
23+
import org.springframework.data.domain.PageImpl;
24+
import org.springframework.data.domain.Pageable;
25+
import org.springframework.util.Assert;
26+
27+
/**
28+
* Support for query execution using {@link Pageable}. Using {@link ReactivePageableExecutionUtils} assumes that data
29+
* queries are cheaper than {@code COUNT} queries and so some cases can take advantage of optimizations.
30+
*
31+
* @author Mark Paluch
32+
* @since 1.4
33+
*/
34+
abstract class ReactivePageableExecutionUtils {
35+
36+
private ReactivePageableExecutionUtils() {}
37+
38+
/**
39+
* Constructs a {@link Page} based on the given {@code content}, {@link Pageable} and {@link Mono} applying
40+
* optimizations. The construction of {@link Page} omits a count query if the total can be determined based on the
41+
* result size and {@link Pageable}.
42+
*
43+
* @param content must not be {@literal null}.
44+
* @param pageable must not be {@literal null}.
45+
* @param totalSupplier must not be {@literal null}.
46+
* @return the {@link Page}.
47+
*/
48+
public static <T> Mono<Page<T>> getPage(List<T> content, Pageable pageable, Mono<Long> totalSupplier) {
49+
50+
Assert.notNull(content, "Content must not be null!");
51+
Assert.notNull(pageable, "Pageable must not be null!");
52+
Assert.notNull(totalSupplier, "TotalSupplier must not be null!");
53+
54+
if (pageable.isUnpaged() || pageable.getOffset() == 0) {
55+
56+
if (pageable.isUnpaged() || pageable.getPageSize() > content.size()) {
57+
return Mono.just(new PageImpl<>(content, pageable, content.size()));
58+
}
59+
60+
return totalSupplier.map(total -> new PageImpl<>(content, pageable, total));
61+
}
62+
63+
if (content.size() != 0 && pageable.getPageSize() > content.size()) {
64+
return Mono.just(new PageImpl<>(content, pageable, pageable.getOffset() + content.size()));
65+
}
66+
67+
return totalSupplier.map(total -> new PageImpl<>(content, pageable, total));
68+
}
69+
}

src/main/java/org/springframework/data/r2dbc/repository/support/SimpleR2dbcRepository.java

+126
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,28 @@
1818
import reactor.core.publisher.Flux;
1919
import reactor.core.publisher.Mono;
2020

21+
import java.util.Collections;
2122
import java.util.List;
23+
import java.util.function.Function;
24+
import java.util.function.UnaryOperator;
2225

2326
import org.reactivestreams.Publisher;
2427
import org.springframework.data.domain.Example;
28+
import org.springframework.data.domain.Page;
29+
import org.springframework.data.domain.Pageable;
2530
import org.springframework.data.domain.Sort;
2631
import org.springframework.data.r2dbc.convert.R2dbcConverter;
2732
import org.springframework.data.r2dbc.core.R2dbcEntityOperations;
2833
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
2934
import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy;
35+
import org.springframework.data.r2dbc.core.ReactiveSelectOperation;
3036
import org.springframework.data.r2dbc.repository.R2dbcRepository;
3137
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
3238
import org.springframework.data.relational.core.query.Criteria;
3339
import org.springframework.data.relational.core.query.Query;
3440
import org.springframework.data.relational.repository.query.RelationalEntityInformation;
3541
import org.springframework.data.relational.repository.query.RelationalExampleMapper;
42+
import org.springframework.data.repository.query.FluentQuery;
3643
import org.springframework.data.repository.reactive.ReactiveSortingRepository;
3744
import org.springframework.data.util.Lazy;
3845
import org.springframework.data.util.Streamable;
@@ -432,11 +439,130 @@ public <S extends T> Mono<Boolean> exists(Example<S> example) {
432439
return this.entityOperations.exists(query, example.getProbeType());
433440
}
434441

442+
@Override
443+
public <S extends T, R, P extends Publisher<R>> P findBy(Example<S> example,
444+
Function<FluentQuery.ReactiveFluentQuery<S>, P> queryFunction) {
445+
446+
Assert.notNull(example, "Sample must not be null!");
447+
Assert.notNull(queryFunction, "Query function must not be null!");
448+
449+
return queryFunction.apply(new ReactiveFluentQueryByExample<>(example, example.getProbeType()));
450+
}
451+
435452
private RelationalPersistentProperty getIdProperty() {
436453
return this.idProperty.get();
437454
}
438455

439456
private Query getIdQuery(Object id) {
440457
return Query.query(Criteria.where(getIdProperty().getName()).is(id));
441458
}
459+
460+
/**
461+
* {@link org.springframework.data.repository.query.FluentQuery.ReactiveFluentQuery} using {@link Example}.
462+
*
463+
* @author Mark Paluch
464+
* @since 1.4
465+
*/
466+
class ReactiveFluentQueryByExample<S, T> extends ReactiveFluentQuerySupport<Example<S>, T> {
467+
468+
ReactiveFluentQueryByExample(Example<S> example, Class<T> resultType) {
469+
this(example, Sort.unsorted(), resultType, Collections.emptyList());
470+
}
471+
472+
ReactiveFluentQueryByExample(Example<S> example, Sort sort, Class<T> resultType, List<String> fieldsToInclude) {
473+
super(example, sort, resultType, fieldsToInclude);
474+
}
475+
476+
@Override
477+
protected <R> ReactiveFluentQueryByExample<S, R> create(Example<S> predicate, Sort sort, Class<R> resultType,
478+
List<String> fieldsToInclude) {
479+
return new ReactiveFluentQueryByExample<>(predicate, sort, resultType, fieldsToInclude);
480+
}
481+
482+
/*
483+
* (non-Javadoc)
484+
* @see org.springframework.data.repository.query.FluentQuery.ReactiveFluentQuery#one()
485+
*/
486+
@Override
487+
public Mono<T> one() {
488+
return createQuery().one();
489+
}
490+
491+
/*
492+
* (non-Javadoc)
493+
* @see org.springframework.data.repository.query.FluentQuery.ReactiveFluentQuery#first()
494+
*/
495+
@Override
496+
public Mono<T> first() {
497+
return createQuery().first();
498+
}
499+
500+
/*
501+
* (non-Javadoc)
502+
* @see org.springframework.data.repository.query.FluentQuery.ReactiveFluentQuery#all()
503+
*/
504+
@Override
505+
public Flux<T> all() {
506+
return createQuery().all();
507+
}
508+
509+
/*
510+
* (non-Javadoc)
511+
* @see org.springframework.data.repository.query.FluentQuery.ReactiveFluentQuery#page(org.springframework.data.domain.Pageable)
512+
*/
513+
@Override
514+
public Mono<Page<T>> page(Pageable pageable) {
515+
516+
Assert.notNull(pageable, "Pageable must not be null!");
517+
518+
Mono<List<T>> items = createQuery(q -> q.with(pageable)).all().collectList();
519+
520+
return items.flatMap(content -> ReactivePageableExecutionUtils.getPage(content, pageable, this.count()));
521+
}
522+
523+
/*
524+
* (non-Javadoc)
525+
* @see org.springframework.data.repository.query.FluentQuery.ReactiveFluentQuery#count()
526+
*/
527+
@Override
528+
public Mono<Long> count() {
529+
return createQuery().count();
530+
}
531+
532+
/*
533+
* (non-Javadoc)
534+
* @see org.springframework.data.repository.query.FluentQuery.ReactiveFluentQuery#exists()
535+
*/
536+
@Override
537+
public Mono<Boolean> exists() {
538+
return createQuery().exists();
539+
}
540+
541+
private ReactiveSelectOperation.TerminatingSelect<T> createQuery() {
542+
return createQuery(UnaryOperator.identity());
543+
}
544+
545+
@SuppressWarnings("unchecked")
546+
private ReactiveSelectOperation.TerminatingSelect<T> createQuery(UnaryOperator<Query> queryCustomizer) {
547+
548+
Query query = exampleMapper.getMappedExample(getPredicate());
549+
550+
if (getSort().isSorted()) {
551+
query = query.sort(getSort());
552+
}
553+
554+
if (!getFieldsToInclude().isEmpty()) {
555+
query = query.columns(getFieldsToInclude().toArray(new String[0]));
556+
}
557+
558+
query = queryCustomizer.apply(query);
559+
560+
ReactiveSelectOperation.ReactiveSelect<S> select = entityOperations.select(getPredicate().getProbeType());
561+
562+
if (getResultType() != getPredicate().getProbeType()) {
563+
return select.as(getResultType()).matching(query);
564+
}
565+
return (ReactiveSelectOperation.TerminatingSelect<T>) select.matching(query);
566+
}
567+
}
442568
}

0 commit comments

Comments
 (0)