Skip to content

Commit d9e4948

Browse files
Support QueryResultConverter for reactive delete, replace and update operations.
Original Pull Request: #4949
1 parent 94f3207 commit d9e4948

File tree

10 files changed

+225
-52
lines changed

10 files changed

+225
-52
lines changed

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperation.java

+4
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ public interface ExecutableRemoveOperation {
5555
*/
5656
<T> ExecutableRemove<T> remove(Class<T> domainType);
5757

58+
/**
59+
* @author Christoph Strobl
60+
* @since 5.0
61+
*/
5862
interface TerminatingResults<T> {
5963

6064
/**

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationSupport.java

+1
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ public List<T> findAndRemove() {
109109
}
110110

111111
@Override
112+
@SuppressWarnings({"unchecked", "rawtypes"})
112113
public <R> TerminatingResults<R> map(QueryResultConverter<? super T, ? extends R> converter) {
113114
return new ExecutableRemoveSupport<>(template, (Class) domainType, query, collection, converter);
114115
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java

+73-17
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@
4747
import org.jspecify.annotations.Nullable;
4848
import org.reactivestreams.Publisher;
4949
import org.reactivestreams.Subscriber;
50-
5150
import org.springframework.beans.BeansException;
5251
import org.springframework.context.ApplicationContext;
5352
import org.springframework.context.ApplicationContextAware;
@@ -114,6 +113,7 @@
114113
import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions;
115114
import org.springframework.data.mongodb.core.query.BasicQuery;
116115
import org.springframework.data.mongodb.core.query.Collation;
116+
import org.springframework.data.mongodb.core.query.Criteria;
117117
import org.springframework.data.mongodb.core.query.Meta;
118118
import org.springframework.data.mongodb.core.query.NearQuery;
119119
import org.springframework.data.mongodb.core.query.Query;
@@ -1137,9 +1137,8 @@ public <T> Mono<T> findAndModify(Query query, UpdateDefinition update, FindAndMo
11371137
return findAndModify(query, update, options, entityClass, getCollectionName(entityClass));
11381138
}
11391139

1140-
@Override
1141-
public <T> Mono<T> findAndModify(Query query, UpdateDefinition update, FindAndModifyOptions options,
1142-
Class<T> entityClass, String collectionName) {
1140+
public <S, T> Mono<T> findAndModify(Query query, UpdateDefinition update, FindAndModifyOptions options,
1141+
Class<S> entityClass, String collectionName, QueryResultConverter<? super S, ? extends T> resultConverter) {
11431142

11441143
Assert.notNull(options, "Options must not be null ");
11451144
Assert.notNull(entityClass, "Entity class must not be null");
@@ -1156,13 +1155,27 @@ public <T> Mono<T> findAndModify(Query query, UpdateDefinition update, FindAndMo
11561155
}
11571156

11581157
return doFindAndModify(collectionName, ReactiveCollectionPreparerDelegate.of(query), query.getQueryObject(),
1159-
query.getFieldsObject(), getMappedSortObject(query, entityClass), entityClass, update, optionsToUse);
1158+
query.getFieldsObject(), getMappedSortObject(query, entityClass), entityClass, update, optionsToUse,
1159+
resultConverter);
1160+
}
1161+
1162+
@Override
1163+
public <T> Mono<T> findAndModify(Query query, UpdateDefinition update, FindAndModifyOptions options,
1164+
Class<T> entityClass, String collectionName) {
1165+
return findAndModify(query, update, options, entityClass, collectionName, QueryResultConverter.entity());
11601166
}
11611167

11621168
@Override
1163-
@SuppressWarnings("NullAway")
11641169
public <S, T> Mono<T> findAndReplace(Query query, S replacement, FindAndReplaceOptions options, Class<S> entityType,
11651170
String collectionName, Class<T> resultType) {
1171+
return findAndReplace(query, replacement, options, entityType, collectionName, resultType,
1172+
QueryResultConverter.entity());
1173+
}
1174+
1175+
@SuppressWarnings("NullAway")
1176+
public <S, T, R> Mono<R> findAndReplace(Query query, S replacement, FindAndReplaceOptions options,
1177+
Class<S> entityType, String collectionName, Class<T> resultType,
1178+
QueryResultConverter<? super T, ? extends R> resultConverter) {
11661179

11671180
Assert.notNull(query, "Query must not be null");
11681181
Assert.notNull(replacement, "Replacement must not be null");
@@ -1199,9 +1212,9 @@ public <S, T> Mono<T> findAndReplace(Query query, S replacement, FindAndReplaceO
11991212
mapped.getCollection()));
12001213
}).flatMap(it -> {
12011214

1202-
Mono<T> afterFindAndReplace = doFindAndReplace(it.getCollection(), collectionPreparer, mappedQuery,
1215+
Mono<R> afterFindAndReplace = doFindAndReplace(it.getCollection(), collectionPreparer, mappedQuery,
12031216
mappedFields, mappedSort, queryContext.getCollation(entityType).orElse(null), entityType, it.getTarget(),
1204-
options, projection);
1217+
options, projection, resultConverter);
12051218
return afterFindAndReplace.flatMap(saved -> {
12061219
maybeEmitEvent(new AfterSaveEvent<>(saved, it.getTarget(), it.getCollection()));
12071220
return maybeCallAfterSave(saved, it.getTarget(), it.getCollection());
@@ -2280,6 +2293,43 @@ protected <T> Flux<T> doFindAndDelete(String collectionName, Query query, Class<
22802293
.flatMapSequential(deleteResult -> Flux.fromIterable(list)));
22812294
}
22822295

2296+
<S, T> Flux<T> doFindAndDelete(String collectionName, Query query, Class<S> entityClass,
2297+
QueryResultConverter<? super S, ? extends T> resultConverter) {
2298+
2299+
List<Object> ids = new ArrayList<>();
2300+
ProjectingReadCallback readCallback = new ProjectingReadCallback(getConverter(),
2301+
EntityProjection.nonProjecting(entityClass), collectionName);
2302+
2303+
QueryResultConverterCallback<S, T> callback = new QueryResultConverterCallback<>(resultConverter, readCallback) {
2304+
2305+
@Override
2306+
public Mono<T> doWith(Document object) {
2307+
ids.add(object.get("_id"));
2308+
return super.doWith(object);
2309+
}
2310+
};
2311+
2312+
Flux<T> flux = doFind(collectionName, ReactiveCollectionPreparerDelegate.of(query), query.getQueryObject(),
2313+
query.getFieldsObject(), entityClass,
2314+
new QueryFindPublisherPreparer(query, query.getSortObject(), query.getLimit(), query.getSkip(), entityClass),
2315+
callback);
2316+
2317+
return Flux.from(flux).collectList().filter(it -> !it.isEmpty()).flatMapMany(list -> {
2318+
2319+
Criteria[] criterias = ids.stream() //
2320+
.map(it -> Criteria.where("_id").is(it)) //
2321+
.toArray(Criteria[]::new);
2322+
2323+
Query removeQuery = new Query(criterias.length == 1 ? criterias[0] : new Criteria().orOperator(criterias));
2324+
if (query.hasReadPreference()) {
2325+
removeQuery.withReadPreference(query.getReadPreference());
2326+
}
2327+
2328+
return Flux.from(remove(removeQuery, entityClass, collectionName))
2329+
.flatMapSequential(deleteResult -> Flux.fromIterable(list));
2330+
});
2331+
}
2332+
22832333
/**
22842334
* Create the specified collection using the provided options
22852335
*
@@ -2487,9 +2537,10 @@ protected <T> Mono<T> doFindAndRemove(String collectionName,
24872537
new ReadDocumentCallback<>(this.mongoConverter, entityClass, collectionName), collectionName);
24882538
}
24892539

2490-
protected <T> Mono<T> doFindAndModify(String collectionName,
2540+
<S, T> Mono<T> doFindAndModify(String collectionName,
24912541
CollectionPreparer<MongoCollection<Document>> collectionPreparer, Document query, Document fields,
2492-
@Nullable Document sort, Class<T> entityClass, UpdateDefinition update, FindAndModifyOptions options) {
2542+
@Nullable Document sort, Class<S> entityClass, UpdateDefinition update, FindAndModifyOptions options,
2543+
QueryResultConverter<? super S, ? extends T> resultConverter) {
24932544

24942545
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
24952546
UpdateContext updateContext = queryOperations.updateSingleContext(update, query, false);
@@ -2508,10 +2559,13 @@ protected <T> Mono<T> doFindAndModify(String collectionName,
25082559
serializeToJsonSafely(mappedUpdate), collectionName));
25092560
}
25102561

2562+
EntityProjection<S, ?> projection = EntityProjection.nonProjecting(entityClass);
2563+
DocumentCallback<T> callback = getResultReader(projection, collectionName, resultConverter);
2564+
25112565
return executeFindOneInternal(
25122566
new FindAndModifyCallback(collectionPreparer, mappedQuery, fields, sort, mappedUpdate,
25132567
update.getArrayFilters().stream().map(ArrayFilter::asDocument).collect(Collectors.toList()), options),
2514-
new ReadDocumentCallback<>(this.mongoConverter, entityClass, collectionName), collectionName);
2568+
callback, collectionName);
25152569
});
25162570
}
25172571

@@ -2540,7 +2594,7 @@ protected <T> Mono<T> doFindAndReplace(String collectionName,
25402594
EntityProjection<T, ?> projection = operations.introspectProjection(resultType, entityType);
25412595

25422596
return doFindAndReplace(collectionName, collectionPreparer, mappedQuery, mappedFields, mappedSort, collation,
2543-
entityType, replacement, options, projection);
2597+
entityType, replacement, options, projection, QueryResultConverter.entity());
25442598
}
25452599

25462600
/**
@@ -2560,10 +2614,11 @@ protected <T> Mono<T> doFindAndReplace(String collectionName,
25602614
* {@literal false} and {@link FindAndReplaceOptions#isUpsert() upsert} is {@literal false}.
25612615
* @since 3.4
25622616
*/
2563-
private <T> Mono<T> doFindAndReplace(String collectionName,
2617+
private <S, T> Mono<T> doFindAndReplace(String collectionName,
25642618
CollectionPreparer<MongoCollection<Document>> collectionPreparer, Document mappedQuery, Document mappedFields,
25652619
Document mappedSort, com.mongodb.client.model.Collation collation, Class<?> entityType, Document replacement,
2566-
FindAndReplaceOptions options, EntityProjection<T, ?> projection) {
2620+
FindAndReplaceOptions options, EntityProjection<S, ?> projection,
2621+
QueryResultConverter<? super S, ? extends T> resultConverter) {
25672622

25682623
return Mono.defer(() -> {
25692624

@@ -2575,9 +2630,10 @@ private <T> Mono<T> doFindAndReplace(String collectionName,
25752630
serializeToJsonSafely(replacement), collectionName));
25762631
}
25772632

2633+
DocumentCallback<T> resultReader = getResultReader(projection, collectionName, resultConverter);
2634+
25782635
return executeFindOneInternal(new FindAndReplaceCallback(collectionPreparer, mappedQuery, mappedFields,
2579-
mappedSort, replacement, collation, options),
2580-
new ProjectingReadCallback<>(this.mongoConverter, projection, collectionName), collectionName);
2636+
mappedSort, replacement, collation, options), resultReader, collectionName);
25812637

25822638
});
25832639
}
@@ -3124,7 +3180,7 @@ interface ReactiveCollectionQueryCallback<T> extends ReactiveCollectionCallback<
31243180
FindPublisher<T> doInCollection(MongoCollection<Document> collection) throws MongoException, DataAccessException;
31253181
}
31263182

3127-
static final class QueryResultConverterCallback<T, R> implements DocumentCallback<R> {
3183+
static class QueryResultConverterCallback<T, R> implements DocumentCallback<R> {
31283184

31293185
private final QueryResultConverter<? super T, ? extends R> converter;
31303186
private final DocumentCallback<T> delegate;

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveRemoveOperation.java

+26-5
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
2222
import org.springframework.data.mongodb.core.query.Query;
23+
import org.springframework.lang.Contract;
2324

2425
import com.mongodb.client.result.DeleteResult;
2526

@@ -56,16 +57,22 @@ public interface ReactiveRemoveOperation {
5657
<T> ReactiveRemove<T> remove(Class<T> domainType);
5758

5859
/**
59-
* Compose remove execution by calling one of the terminating methods.
60+
* @author Christoph Strobl
61+
* @since 5.0
6062
*/
61-
interface TerminatingRemove<T> {
63+
interface TerminatingResults<T> {
6264

6365
/**
64-
* Remove all documents matching.
66+
* Map the query result to a different type using {@link QueryResultConverter}.
6567
*
66-
* @return {@link Mono} emitting the {@link DeleteResult}. Never {@literal null}.
68+
* @param <R> {@link Class type} of the result.
69+
* @param converter the converter, must not be {@literal null}.
70+
* @return new instance of {@link ExecutableFindOperation.TerminatingResults}.
71+
* @throws IllegalArgumentException if {@link QueryResultConverter converter} is {@literal null}.
72+
* @since 5.0
6773
*/
68-
Mono<DeleteResult> all();
74+
@Contract("_ -> new")
75+
<R> TerminatingResults<R> map(QueryResultConverter<? super T, ? extends R> converter);
6976

7077
/**
7178
* Remove and return all matching documents. <br/>
@@ -78,6 +85,20 @@ interface TerminatingRemove<T> {
7885
Flux<T> findAndRemove();
7986
}
8087

88+
/**
89+
* Compose remove execution by calling one of the terminating methods.
90+
*/
91+
interface TerminatingRemove<T> extends TerminatingResults<T> {
92+
93+
/**
94+
* Remove all documents matching.
95+
*
96+
* @return {@link Mono} emitting the {@link DeleteResult}. Never {@literal null}.
97+
*/
98+
Mono<DeleteResult> all();
99+
100+
}
101+
81102
/**
82103
* Collection override (optional).
83104
*/

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveRemoveOperationSupport.java

+17-8
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@
1515
*/
1616
package org.springframework.data.mongodb.core;
1717

18-
import org.jspecify.annotations.Nullable;
1918
import reactor.core.publisher.Flux;
2019
import reactor.core.publisher.Mono;
2120

21+
import org.jspecify.annotations.Nullable;
2222
import org.springframework.data.mongodb.core.query.Query;
2323
import org.springframework.util.Assert;
2424
import org.springframework.util.StringUtils;
@@ -47,38 +47,41 @@ public <T> ReactiveRemove<T> remove(Class<T> domainType) {
4747

4848
Assert.notNull(domainType, "DomainType must not be null");
4949

50-
return new ReactiveRemoveSupport<>(template, domainType, ALL_QUERY, null);
50+
return new ReactiveRemoveSupport<>(template, domainType, ALL_QUERY, null, QueryResultConverter.entity());
5151
}
5252

53-
static class ReactiveRemoveSupport<T> implements ReactiveRemove<T>, RemoveWithCollection<T> {
53+
static class ReactiveRemoveSupport<S, T> implements ReactiveRemove<T>, RemoveWithCollection<T> {
5454

5555
private final ReactiveMongoTemplate template;
56-
private final Class<T> domainType;
56+
private final Class<S> domainType;
5757
private final Query query;
5858
private final @Nullable String collection;
59+
private final QueryResultConverter<? super S, ? extends T> resultConverter;
5960

60-
ReactiveRemoveSupport(ReactiveMongoTemplate template, Class<T> domainType, Query query, @Nullable String collection) {
61+
ReactiveRemoveSupport(ReactiveMongoTemplate template, Class<S> domainType, Query query, @Nullable String collection,
62+
QueryResultConverter<? super S, ? extends T> resultConverter) {
6163

6264
this.template = template;
6365
this.domainType = domainType;
6466
this.query = query;
6567
this.collection = collection;
68+
this.resultConverter = resultConverter;
6669
}
6770

6871
@Override
6972
public RemoveWithQuery<T> inCollection(String collection) {
7073

7174
Assert.hasText(collection, "Collection must not be null nor empty");
7275

73-
return new ReactiveRemoveSupport<>(template, domainType, query, collection);
76+
return new ReactiveRemoveSupport<>(template, domainType, query, collection, resultConverter);
7477
}
7578

7679
@Override
7780
public TerminatingRemove<T> matching(Query query) {
7881

7982
Assert.notNull(query, "Query must not be null");
8083

81-
return new ReactiveRemoveSupport<>(template, domainType, query, collection);
84+
return new ReactiveRemoveSupport<>(template, domainType, query, collection, resultConverter);
8285
}
8386

8487
@Override
@@ -94,7 +97,13 @@ public Flux<T> findAndRemove() {
9497

9598
String collectionName = getCollectionName();
9699

97-
return template.doFindAndDelete(collectionName, query, domainType);
100+
return template.doFindAndDelete(collectionName, query, domainType, resultConverter);
101+
}
102+
103+
@Override
104+
@SuppressWarnings({ "unchecked", "rawtypes" })
105+
public <R> TerminatingResults<R> map(QueryResultConverter<? super T, ? extends R> converter) {
106+
return new ReactiveRemoveSupport<>(template, (Class) domainType, query, collection, converter);
98107
}
99108

100109
private String getCollectionName() {

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveUpdateOperation.java

+25
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.springframework.data.mongodb.core;
1717

18+
import org.jetbrains.annotations.Contract;
1819
import reactor.core.publisher.Mono;
1920

2021
import org.springframework.data.mongodb.core.aggregation.AggregationUpdate;
@@ -64,6 +65,18 @@ public interface ReactiveUpdateOperation {
6465
*/
6566
interface TerminatingFindAndModify<T> {
6667

68+
/**
69+
* Map the query result to a different type using {@link QueryResultConverter}.
70+
*
71+
* @param <R> {@link Class type} of the result.
72+
* @param converter the converter, must not be {@literal null}.
73+
* @return new instance of {@link TerminatingFindAndModify}.
74+
* @throws IllegalArgumentException if {@link QueryResultConverter converter} is {@literal null}.
75+
* @since 5.0
76+
*/
77+
@Contract("_ -> new")
78+
<R> TerminatingFindAndModify<R> map(QueryResultConverter<? super T, ? extends R> converter);
79+
6780
/**
6881
* Find, modify and return the first matching document.
6982
*
@@ -97,6 +110,18 @@ interface TerminatingReplace {
97110
*/
98111
interface TerminatingFindAndReplace<T> extends TerminatingReplace {
99112

113+
/**
114+
* Map the query result to a different type using {@link QueryResultConverter}.
115+
*
116+
* @param <R> {@link Class type} of the result.
117+
* @param converter the converter, must not be {@literal null}.
118+
* @return new instance of {@link TerminatingFindAndModify}.
119+
* @throws IllegalArgumentException if {@link QueryResultConverter converter} is {@literal null}.
120+
* @since 5.0
121+
*/
122+
@Contract("_ -> new")
123+
<R> TerminatingFindAndReplace<R> map(QueryResultConverter<? super T, ? extends R> converter);
124+
100125
/**
101126
* Find, replace and return the first matching document.
102127
*

0 commit comments

Comments
 (0)