Skip to content

Commit 0297d95

Browse files
Support QueryResultConverter for delete, replace and update operations.
Original Pull Request: #4949
1 parent d76017f commit 0297d95

9 files changed

+295
-61
lines changed

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

+27-12
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
2121
import org.springframework.data.mongodb.core.query.Query;
22+
import org.springframework.lang.Contract;
2223

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

@@ -54,11 +55,36 @@ public interface ExecutableRemoveOperation {
5455
*/
5556
<T> ExecutableRemove<T> remove(Class<T> domainType);
5657

58+
interface TerminatingResults<T> {
59+
60+
/**
61+
* Map the query result to a different type using {@link QueryResultConverter}.
62+
*
63+
* @param <R> {@link Class type} of the result.
64+
* @param converter the converter, must not be {@literal null}.
65+
* @return new instance of {@link ExecutableFindOperation.TerminatingResults}.
66+
* @throws IllegalArgumentException if {@link QueryResultConverter converter} is {@literal null}.
67+
* @since x.y
68+
*/
69+
@Contract("_ -> new")
70+
<R> TerminatingResults<R> map(QueryResultConverter<? super T, ? extends R> converter);
71+
72+
/**
73+
* Remove and return all matching documents. <br/>
74+
* <strong>NOTE:</strong> The entire list of documents will be fetched before sending the actual delete commands.
75+
* Also, {@link org.springframework.context.ApplicationEvent}s will be published for each and every delete
76+
* operation.
77+
*
78+
* @return empty {@link List} if no match found. Never {@literal null}.
79+
*/
80+
List<T> findAndRemove();
81+
}
82+
5783
/**
5884
* @author Christoph Strobl
5985
* @since 2.0
6086
*/
61-
interface TerminatingRemove<T> {
87+
interface TerminatingRemove<T> extends TerminatingResults<T> {
6288

6389
/**
6490
* Remove all documents matching.
@@ -73,16 +99,6 @@ interface TerminatingRemove<T> {
7399
* @return the {@link DeleteResult}. Never {@literal null}.
74100
*/
75101
DeleteResult one();
76-
77-
/**
78-
* Remove and return all matching documents. <br/>
79-
* <strong>NOTE:</strong> The entire list of documents will be fetched before sending the actual delete commands.
80-
* Also, {@link org.springframework.context.ApplicationEvent}s will be published for each and every delete
81-
* operation.
82-
*
83-
* @return empty {@link List} if no match found. Never {@literal null}.
84-
*/
85-
List<T> findAndRemove();
86102
}
87103

88104
/**
@@ -105,7 +121,6 @@ interface RemoveWithCollection<T> extends RemoveWithQuery<T> {
105121
RemoveWithQuery<T> inCollection(String collection);
106122
}
107123

108-
109124
/**
110125
* @author Christoph Strobl
111126
* @since 2.0

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

+15-8
Original file line numberDiff line numberDiff line change
@@ -48,26 +48,28 @@ public <T> ExecutableRemove<T> remove(Class<T> domainType) {
4848

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

51-
return new ExecutableRemoveSupport<>(tempate, domainType, ALL_QUERY, null);
51+
return new ExecutableRemoveSupport<>(tempate, domainType, ALL_QUERY, null, QueryResultConverter.entity());
5252
}
5353

5454
/**
5555
* @author Christoph Strobl
5656
* @since 2.0
5757
*/
58-
static class ExecutableRemoveSupport<T> implements ExecutableRemove<T>, RemoveWithCollection<T> {
58+
static class ExecutableRemoveSupport<S, T> implements ExecutableRemove<T>, RemoveWithCollection<T> {
5959

6060
private final MongoTemplate template;
61-
private final Class<T> domainType;
61+
private final Class<S> domainType;
6262
private final Query query;
6363
@Nullable private final String collection;
64+
private final QueryResultConverter<? super S, ? extends T> resultConverter;
6465

65-
public ExecutableRemoveSupport(MongoTemplate template, Class<T> domainType, Query query,
66-
@Nullable String collection) {
66+
public ExecutableRemoveSupport(MongoTemplate template, Class<S> domainType, Query query,
67+
@Nullable String collection, QueryResultConverter<? super S, ? extends T> resultConverter) {
6768
this.template = template;
6869
this.domainType = domainType;
6970
this.query = query;
7071
this.collection = collection;
72+
this.resultConverter = resultConverter;
7173
}
7274

7375
@Override
@@ -76,7 +78,7 @@ public RemoveWithQuery<T> inCollection(String collection) {
7678

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

79-
return new ExecutableRemoveSupport<>(template, domainType, query, collection);
81+
return new ExecutableRemoveSupport<>(template, domainType, query, collection, resultConverter);
8082
}
8183

8284
@Override
@@ -85,7 +87,7 @@ public TerminatingRemove<T> matching(Query query) {
8587

8688
Assert.notNull(query, "Query must not be null");
8789

88-
return new ExecutableRemoveSupport<>(template, domainType, query, collection);
90+
return new ExecutableRemoveSupport<>(template, domainType, query, collection, resultConverter);
8991
}
9092

9193
@Override
@@ -103,7 +105,12 @@ public List<T> findAndRemove() {
103105

104106
String collectionName = getCollectionName();
105107

106-
return template.doFindAndDelete(collectionName, query, domainType);
108+
return template.doFindAndDelete(collectionName, query, domainType, resultConverter);
109+
}
110+
111+
@Override
112+
public <R> TerminatingResults<R> map(QueryResultConverter<? super T, ? extends R> converter) {
113+
return new ExecutableRemoveSupport<>(template, (Class) domainType, query, collection, converter);
107114
}
108115

109116
private String getCollectionName() {

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

+27
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@
1818
import java.util.Optional;
1919

2020
import org.jspecify.annotations.Nullable;
21+
import org.springframework.data.mongodb.core.ExecutableFindOperation.TerminatingResults;
2122
import org.springframework.data.mongodb.core.aggregation.AggregationUpdate;
2223
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
2324
import org.springframework.data.mongodb.core.query.Query;
2425
import org.springframework.data.mongodb.core.query.Update;
2526
import org.springframework.data.mongodb.core.query.UpdateDefinition;
2627

2728
import com.mongodb.client.result.UpdateResult;
29+
import org.springframework.lang.Contract;
2830

2931
/**
3032
* {@link ExecutableUpdateOperation} allows creation and execution of MongoDB update / findAndModify / findAndReplace
@@ -69,6 +71,19 @@ public interface ExecutableUpdateOperation {
6971
*/
7072
interface TerminatingFindAndModify<T> {
7173

74+
75+
/**
76+
* Map the query result to a different type using {@link QueryResultConverter}.
77+
*
78+
* @param <R> {@link Class type} of the result.
79+
* @param converter the converter, must not be {@literal null}.
80+
* @return new instance of {@link TerminatingFindAndModify}.
81+
* @throws IllegalArgumentException if {@link QueryResultConverter converter} is {@literal null}.
82+
* @since x.y
83+
*/
84+
@Contract("_ -> new")
85+
<R> TerminatingFindAndModify<R> map(QueryResultConverter<? super T, ? extends R> converter);
86+
7287
/**
7388
* Find, modify and return the first matching document.
7489
*
@@ -130,6 +145,18 @@ default Optional<T> findAndReplace() {
130145
*/
131146
@Nullable
132147
T findAndReplaceValue();
148+
149+
/**
150+
* Map the query result to a different type using {@link QueryResultConverter}.
151+
*
152+
* @param <R> {@link Class type} of the result.
153+
* @param converter the converter, must not be {@literal null}.
154+
* @return new instance of {@link TerminatingFindAndModify}.
155+
* @throws IllegalArgumentException if {@link QueryResultConverter converter} is {@literal null}.
156+
* @since x.y
157+
*/
158+
@Contract("_ -> new")
159+
<R> TerminatingFindAndReplace<R> mapResult(QueryResultConverter<? super T, ? extends R> converter);
133160
}
134161

135162
/**

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

+33-17
Original file line numberDiff line numberDiff line change
@@ -47,31 +47,33 @@ public <T> ExecutableUpdate<T> update(Class<T> domainType) {
4747

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

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

5353
/**
5454
* @author Christoph Strobl
5555
* @since 2.0
5656
*/
5757
@SuppressWarnings("rawtypes")
58-
static class ExecutableUpdateSupport<T>
58+
static class ExecutableUpdateSupport<S, T>
5959
implements ExecutableUpdate<T>, UpdateWithCollection<T>, UpdateWithQuery<T>, TerminatingUpdate<T>,
6060
FindAndReplaceWithOptions<T>, TerminatingFindAndReplace<T>, FindAndReplaceWithProjection<T> {
6161

6262
private final MongoTemplate template;
63-
private final Class domainType;
63+
private final Class<?> domainType;
6464
private final Query query;
6565
@Nullable private final UpdateDefinition update;
6666
@Nullable private final String collection;
6767
@Nullable private final FindAndModifyOptions findAndModifyOptions;
6868
@Nullable private final FindAndReplaceOptions findAndReplaceOptions;
6969
@Nullable private final Object replacement;
70-
private final Class<T> targetType;
70+
private final QueryResultConverter<? super S, ? extends T> resultConverter;
71+
private final Class<S> targetType;
7172

72-
ExecutableUpdateSupport(MongoTemplate template, Class domainType, Query query, @Nullable UpdateDefinition update,
73+
ExecutableUpdateSupport(MongoTemplate template, Class<?> domainType, Query query, @Nullable UpdateDefinition update,
7374
@Nullable String collection, @Nullable FindAndModifyOptions findAndModifyOptions,
74-
@Nullable FindAndReplaceOptions findAndReplaceOptions, @Nullable Object replacement, Class<T> targetType) {
75+
@Nullable FindAndReplaceOptions findAndReplaceOptions, @Nullable Object replacement, Class<S> targetType,
76+
QueryResultConverter<? super S, ? extends T> resultConverter) {
7577

7678
this.template = template;
7779
this.domainType = domainType;
@@ -82,6 +84,7 @@ static class ExecutableUpdateSupport<T>
8284
this.findAndReplaceOptions = findAndReplaceOptions;
8385
this.replacement = replacement;
8486
this.targetType = targetType;
87+
this.resultConverter = resultConverter;
8588
}
8689

8790
@Override
@@ -91,7 +94,7 @@ public TerminatingUpdate<T> apply(UpdateDefinition update) {
9194
Assert.notNull(update, "Update must not be null");
9295

9396
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
94-
findAndReplaceOptions, replacement, targetType);
97+
findAndReplaceOptions, replacement, targetType, resultConverter);
9598
}
9699

97100
@Override
@@ -101,7 +104,7 @@ public UpdateWithQuery<T> inCollection(String collection) {
101104
Assert.hasText(collection, "Collection must not be null nor empty");
102105

103106
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
104-
findAndReplaceOptions, replacement, targetType);
107+
findAndReplaceOptions, replacement, targetType, resultConverter);
105108
}
106109

107110
@Override
@@ -111,7 +114,7 @@ public TerminatingFindAndModify<T> withOptions(FindAndModifyOptions options) {
111114
Assert.notNull(options, "Options must not be null");
112115

113116
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, options,
114-
findAndReplaceOptions, replacement, targetType);
117+
findAndReplaceOptions, replacement, targetType, resultConverter);
115118
}
116119

117120
@Override
@@ -121,7 +124,7 @@ public FindAndReplaceWithProjection<T> replaceWith(T replacement) {
121124
Assert.notNull(replacement, "Replacement must not be null");
122125

123126
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
124-
findAndReplaceOptions, replacement, targetType);
127+
findAndReplaceOptions, replacement, targetType, resultConverter);
125128
}
126129

127130
@Override
@@ -131,7 +134,7 @@ public FindAndReplaceWithProjection<T> withOptions(FindAndReplaceOptions options
131134
Assert.notNull(options, "Options must not be null");
132135

133136
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
134-
options, replacement, targetType);
137+
options, replacement, targetType, resultConverter);
135138
}
136139

137140
@Override
@@ -143,7 +146,7 @@ public TerminatingReplace withOptions(ReplaceOptions options) {
143146
target.upsert();
144147
}
145148
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
146-
target, replacement, targetType);
149+
target, replacement, targetType, resultConverter);
147150
}
148151

149152
@Override
@@ -153,7 +156,7 @@ public UpdateWithUpdate<T> matching(Query query) {
153156
Assert.notNull(query, "Query must not be null");
154157

155158
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
156-
findAndReplaceOptions, replacement, targetType);
159+
findAndReplaceOptions, replacement, targetType, resultConverter);
157160
}
158161

159162
@Override
@@ -163,7 +166,7 @@ public <R> FindAndReplaceWithOptions<R> as(Class<R> resultType) {
163166
Assert.notNull(resultType, "ResultType must not be null");
164167

165168
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
166-
findAndReplaceOptions, replacement, resultType);
169+
findAndReplaceOptions, replacement, resultType, QueryResultConverter.entity());
167170
}
168171

169172
@Override
@@ -181,22 +184,35 @@ public UpdateResult upsert() {
181184
return doUpdate(true, true);
182185
}
183186

187+
@Override
188+
public <R> TerminatingFindAndModify<R> map(QueryResultConverter<? super T, ? extends R> converter) {
189+
190+
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
191+
findAndReplaceOptions, replacement, targetType, this.resultConverter.andThen(converter));
192+
}
193+
194+
@Override
195+
public <R> TerminatingFindAndReplace<R> mapResult(QueryResultConverter<? super T, ? extends R> converter) {
196+
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
197+
findAndReplaceOptions, replacement, targetType, this.resultConverter.andThen(converter));
198+
}
199+
184200
@Override
185201
@SuppressWarnings("NullAway")
186202
public @Nullable T findAndModifyValue() {
187203

188204
return template.findAndModify(query, update,
189205
findAndModifyOptions != null ? findAndModifyOptions : new FindAndModifyOptions(), targetType,
190-
getCollectionName());
206+
getCollectionName(), resultConverter);
191207
}
192208

193209
@Override
194210
@SuppressWarnings({ "unchecked", "NullAway" })
195211
public @Nullable T findAndReplaceValue() {
196212

197213
return (T) template.findAndReplace(query, replacement,
198-
findAndReplaceOptions != null ? findAndReplaceOptions : FindAndReplaceOptions.empty(), domainType,
199-
getCollectionName(), targetType);
214+
findAndReplaceOptions != null ? findAndReplaceOptions : FindAndReplaceOptions.empty(), (Class) domainType,
215+
getCollectionName(), targetType, (QueryResultConverter) resultConverter);
200216
}
201217

202218
@Override

0 commit comments

Comments
 (0)