Skip to content

Commit b497fca

Browse files
committed
Add support for fluent QueryResultConverter.
1 parent f6d4fda commit b497fca

20 files changed

+725
-112
lines changed
Lines changed: 33 additions & 0 deletions
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

Lines changed: 15 additions & 1 deletion
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

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,25 +43,28 @@ public <T> ExecutableAggregation<T> aggregateAndReturn(Class<T> domainType) {
4343

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

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

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

5656
private final MongoTemplate template;
57-
private final Class<T> domainType;
57+
private final Class<S> domainType;
58+
private final QueryResultConverter<? super S, ? extends T> resultConverter;
5859
private final Aggregation aggregation;
5960
private final String collection;
6061

61-
public ExecutableAggregationSupport(MongoTemplate template, Class<T> domainType, Aggregation aggregation,
62+
public ExecutableAggregationSupport(MongoTemplate template, Class<S> domainType,
63+
QueryResultConverter<? super S, ? extends T> resultConverter, Aggregation aggregation,
6264
String collection) {
6365
this.template = template;
6466
this.domainType = domainType;
67+
this.resultConverter = resultConverter;
6568
this.aggregation = aggregation;
6669
this.collection = collection;
6770
}
@@ -71,25 +74,34 @@ public AggregationWithAggregation<T> inCollection(String collection) {
7174

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

74-
return new ExecutableAggregationSupport<>(template, domainType, aggregation, collection);
77+
return new ExecutableAggregationSupport<>(template, domainType, resultConverter, aggregation, collection);
7578
}
7679

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

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

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

8597
@Override
8698
public AggregationResults<T> all() {
87-
return template.aggregate(aggregation, getCollectionName(aggregation), domainType);
99+
return template.doAggregate(aggregation, getCollectionName(aggregation), domainType, resultConverter);
88100
}
89101

90102
@Override
91103
public Stream<T> stream() {
92-
return template.aggregateStream(aggregation, getCollectionName(aggregation), domainType);
104+
return template.doAggregateStream(aggregation, getCollectionName(aggregation), domainType, resultConverter, null);
93105
}
94106

95107
private String getCollectionName(Aggregation aggregation) {

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

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
2828
import org.springframework.data.mongodb.core.query.NearQuery;
2929
import org.springframework.data.mongodb.core.query.Query;
30+
import org.springframework.lang.Contract;
3031
import org.springframework.lang.Nullable;
3132

3233
import com.mongodb.client.MongoCollection;
@@ -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)