Skip to content

Commit a4cc5bb

Browse files
mp911dechristophstrobl
authored andcommitted
Add support for fluent QueryResultConverter.
Closes: #4949
1 parent 8f9daf8 commit a4cc5bb

20 files changed

+723
-110
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright 2025 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.mongodb.core;
17+
18+
import org.bson.Document;
19+
20+
enum EntityResultConverter implements QueryResultConverter<Object, Object> {
21+
22+
INSTANCE;
23+
24+
@Override
25+
public Object mapDocument(Document document, ConversionResultSupplier<Object> reader) {
26+
return reader.get();
27+
}
28+
29+
@Override
30+
public <V> QueryResultConverter<Object, V> andThen(QueryResultConverter<? super Object, ? extends V> after) {
31+
return (QueryResultConverter) after;
32+
}
33+
}

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

+15-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import org.springframework.data.mongodb.core.aggregation.Aggregation;
2121
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
22+
import org.springframework.lang.Contract;
2223

2324
/**
2425
* {@link ExecutableAggregationOperation} allows creation and execution of MongoDB aggregation operations in a fluent
@@ -45,7 +46,7 @@ public interface ExecutableAggregationOperation {
4546
/**
4647
* Start creating an aggregation operation that returns results mapped to the given domain type. <br />
4748
* Use {@link org.springframework.data.mongodb.core.aggregation.TypedAggregation} to specify a potentially different
48-
* input type for he aggregation.
49+
* input type for the aggregation.
4950
*
5051
* @param domainType must not be {@literal null}.
5152
* @return new instance of {@link ExecutableAggregation}.
@@ -76,10 +77,23 @@ interface AggregationWithCollection<T> {
7677
* Trigger execution by calling one of the terminating methods.
7778
*
7879
* @author Christoph Strobl
80+
* @author Mark Paluch
7981
* @since 2.0
8082
*/
8183
interface TerminatingAggregation<T> {
8284

85+
/**
86+
* Map the query result to a different type using {@link QueryResultConverter}.
87+
*
88+
* @param <R> {@link Class type} of the result.
89+
* @param converter the converter, must not be {@literal null}.
90+
* @return new instance of {@link TerminatingAggregation}.
91+
* @throws IllegalArgumentException if {@link QueryResultConverter converter} is {@literal null}.
92+
* @since x.y
93+
*/
94+
@Contract("_ -> new")
95+
<R> TerminatingAggregation<R> map(QueryResultConverter<? super T, ? extends R> converter);
96+
8397
/**
8498
* Apply pipeline operations as specified and get all matching elements.
8599
*

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

+20-8
Original file line numberDiff line numberDiff line change
@@ -44,25 +44,28 @@ public <T> ExecutableAggregation<T> aggregateAndReturn(Class<T> domainType) {
4444

4545
Assert.notNull(domainType, "DomainType must not be null");
4646

47-
return new ExecutableAggregationSupport<>(template, domainType, null, null);
47+
return new ExecutableAggregationSupport<>(template, domainType, QueryResultConverter.entity(), null, null);
4848
}
4949

5050
/**
5151
* @author Christoph Strobl
5252
* @since 2.0
5353
*/
54-
static class ExecutableAggregationSupport<T>
54+
static class ExecutableAggregationSupport<S, T>
5555
implements AggregationWithAggregation<T>, ExecutableAggregation<T>, TerminatingAggregation<T> {
5656

5757
private final MongoTemplate template;
58-
private final Class<T> domainType;
58+
private final Class<S> domainType;
59+
private final QueryResultConverter<? super S, ? extends T> resultConverter;
5960
private final @Nullable Aggregation aggregation;
6061
private final @Nullable String collection;
6162

62-
public ExecutableAggregationSupport(MongoTemplate template, Class<T> domainType, @Nullable Aggregation aggregation,
63+
public ExecutableAggregationSupport(MongoTemplate template, Class<S> domainType,
64+
QueryResultConverter<? super S, ? extends T> resultConverter, @Nullable Aggregation aggregation,
6365
@Nullable String collection) {
6466
this.template = template;
6567
this.domainType = domainType;
68+
this.resultConverter = resultConverter;
6669
this.aggregation = aggregation;
6770
this.collection = collection;
6871
}
@@ -72,29 +75,38 @@ public AggregationWithAggregation<T> inCollection(String collection) {
7275

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

75-
return new ExecutableAggregationSupport<>(template, domainType, aggregation, collection);
78+
return new ExecutableAggregationSupport<>(template, domainType, resultConverter, aggregation, collection);
7679
}
7780

7881
@Override
7982
public TerminatingAggregation<T> by(Aggregation aggregation) {
8083

8184
Assert.notNull(aggregation, "Aggregation must not be null");
8285

83-
return new ExecutableAggregationSupport<>(template, domainType, aggregation, collection);
86+
return new ExecutableAggregationSupport<>(template, domainType, resultConverter, aggregation, collection);
87+
}
88+
89+
@Override
90+
public <R> TerminatingAggregation<R> map(QueryResultConverter<? super T, ? extends R> converter) {
91+
92+
Assert.notNull(converter, "QueryResultConverter must not be null");
93+
94+
return new ExecutableAggregationSupport<>(template, domainType, this.resultConverter.andThen(converter),
95+
aggregation, collection);
8496
}
8597

8698
@Override
8799
public AggregationResults<T> all() {
88100

89101
Assert.notNull(aggregation, "Aggregation must be set first");
90-
return template.aggregate(aggregation, getCollectionName(aggregation), domainType);
102+
return template.doAggregate(aggregation, getCollectionName(aggregation), domainType, resultConverter);
91103
}
92104

93105
@Override
94106
public Stream<T> stream() {
95107

96108
Assert.notNull(aggregation, "Aggregation must be set first");
97-
return template.aggregateStream(aggregation, getCollectionName(aggregation), domainType);
109+
return template.doAggregateStream(aggregation, getCollectionName(aggregation), domainType, resultConverter, null);
98110
}
99111

100112
private String getCollectionName(@Nullable Aggregation aggregation) {

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

+51-2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
2929
import org.springframework.data.mongodb.core.query.NearQuery;
3030
import org.springframework.data.mongodb.core.query.Query;
31+
import org.springframework.lang.Contract;
3132

3233
import com.mongodb.client.MongoCollection;
3334

@@ -71,9 +72,33 @@ public interface ExecutableFindOperation {
7172
* Trigger find execution by calling one of the terminating methods.
7273
*
7374
* @author Christoph Strobl
75+
* @author Mark Paluch
7476
* @since 2.0
7577
*/
76-
interface TerminatingFind<T> {
78+
interface TerminatingFind<T> extends TerminatingResults<T>, TerminatingProjection {
79+
80+
}
81+
82+
/**
83+
* Trigger find execution by calling one of the terminating methods.
84+
*
85+
* @author Christoph Strobl
86+
* @author Mark Paluch
87+
* @since x.y
88+
*/
89+
interface TerminatingResults<T> {
90+
91+
/**
92+
* Map the query result to a different type using {@link QueryResultConverter}.
93+
*
94+
* @param <R> {@link Class type} of the result.
95+
* @param converter the converter, must not be {@literal null}.
96+
* @return new instance of {@link TerminatingResults}.
97+
* @throws IllegalArgumentException if {@link QueryResultConverter converter} is {@literal null}.
98+
* @since x.y
99+
*/
100+
@Contract("_ -> new")
101+
<R> TerminatingResults<R> map(QueryResultConverter<? super T, ? extends R> converter);
77102

78103
/**
79104
* Get exactly zero or one result.
@@ -142,6 +167,16 @@ default Optional<T> first() {
142167
*/
143168
Window<T> scroll(ScrollPosition scrollPosition);
144169

170+
}
171+
172+
/**
173+
* Trigger find execution by calling one of the terminating methods.
174+
*
175+
* @author Christoph Strobl
176+
* @since x.y
177+
*/
178+
interface TerminatingProjection {
179+
145180
/**
146181
* Get the number of matching elements. <br />
147182
* This method uses an
@@ -160,16 +195,30 @@ default Optional<T> first() {
160195
* @return {@literal true} if at least one matching element exists.
161196
*/
162197
boolean exists();
198+
163199
}
164200

165201
/**
166-
* Trigger geonear execution by calling one of the terminating methods.
202+
* Trigger {@code geoNear} execution by calling one of the terminating methods.
167203
*
168204
* @author Christoph Strobl
205+
* @author Mark Paluch
169206
* @since 2.0
170207
*/
171208
interface TerminatingFindNear<T> {
172209

210+
/**
211+
* Map the query result to a different type using {@link QueryResultConverter}.
212+
*
213+
* @param <R> {@link Class type} of the result.
214+
* @param converter the converter, must not be {@literal null}.
215+
* @return new instance of {@link TerminatingFindNear}.
216+
* @throws IllegalArgumentException if {@link QueryResultConverter converter} is {@literal null}.
217+
* @since x.y
218+
*/
219+
@Contract("_ -> new")
220+
<R> TerminatingFindNear<R> map(QueryResultConverter<? super T, ? extends R> converter);
221+
173222
/**
174223
* Find all matching elements and return them as {@link org.springframework.data.geo.GeoResult}.
175224
*

0 commit comments

Comments
 (0)