Skip to content

Add support for fluent QueryResultConverter #4949

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.5.0-SNAPSHOT</version>
<version>4.5.0-QRC-SNAPSHOT</version>
<packaging>pom</packaging>

<name>Spring Data MongoDB</name>
Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb-distribution/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.5.0-SNAPSHOT</version>
<version>4.5.0-QRC-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.5.0-SNAPSHOT</version>
<version>4.5.0-QRC-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright 2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core;

import org.bson.Document;

enum EntityResultConverter implements QueryResultConverter<Object, Object> {

INSTANCE;

@Override
public Object mapDocument(Document document, ConversionResultSupplier<Object> reader) {
return reader.get();
}

@Override
public <V> QueryResultConverter<Object, V> andThen(QueryResultConverter<? super Object, ? extends V> after) {
return (QueryResultConverter) after;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.lang.Contract;

/**
* {@link ExecutableAggregationOperation} allows creation and execution of MongoDB aggregation operations in a fluent
Expand All @@ -45,7 +46,7 @@ public interface ExecutableAggregationOperation {
/**
* Start creating an aggregation operation that returns results mapped to the given domain type. <br />
* Use {@link org.springframework.data.mongodb.core.aggregation.TypedAggregation} to specify a potentially different
* input type for he aggregation.
* input type for the aggregation.
*
* @param domainType must not be {@literal null}.
* @return new instance of {@link ExecutableAggregation}.
Expand Down Expand Up @@ -76,10 +77,23 @@ interface AggregationWithCollection<T> {
* Trigger execution by calling one of the terminating methods.
*
* @author Christoph Strobl
* @author Mark Paluch
* @since 2.0
*/
interface TerminatingAggregation<T> {

/**
* Map the query result to a different type using {@link QueryResultConverter}.
*
* @param <R> {@link Class type} of the result.
* @param converter the converter, must not be {@literal null}.
* @return new instance of {@link TerminatingAggregation}.
* @throws IllegalArgumentException if {@link QueryResultConverter converter} is {@literal null}.
* @since x.y
*/
@Contract("_ -> new")
<R> TerminatingAggregation<R> map(QueryResultConverter<? super T, ? extends R> converter);

/**
* Apply pipeline operations as specified and get all matching elements.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,25 +43,28 @@ public <T> ExecutableAggregation<T> aggregateAndReturn(Class<T> domainType) {

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

return new ExecutableAggregationSupport<>(template, domainType, null, null);
return new ExecutableAggregationSupport<>(template, domainType, QueryResultConverter.entity(), null, null);
}

/**
* @author Christoph Strobl
* @since 2.0
*/
static class ExecutableAggregationSupport<T>
static class ExecutableAggregationSupport<S, T>
implements AggregationWithAggregation<T>, ExecutableAggregation<T>, TerminatingAggregation<T> {

private final MongoTemplate template;
private final Class<T> domainType;
private final Class<S> domainType;
private final QueryResultConverter<? super S, ? extends T> resultConverter;
private final Aggregation aggregation;
private final String collection;

public ExecutableAggregationSupport(MongoTemplate template, Class<T> domainType, Aggregation aggregation,
public ExecutableAggregationSupport(MongoTemplate template, Class<S> domainType,
QueryResultConverter<? super S, ? extends T> resultConverter, Aggregation aggregation,
String collection) {
this.template = template;
this.domainType = domainType;
this.resultConverter = resultConverter;
this.aggregation = aggregation;
this.collection = collection;
}
Expand All @@ -71,25 +74,34 @@ public AggregationWithAggregation<T> inCollection(String collection) {

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

return new ExecutableAggregationSupport<>(template, domainType, aggregation, collection);
return new ExecutableAggregationSupport<>(template, domainType, resultConverter, aggregation, collection);
}

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

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

return new ExecutableAggregationSupport<>(template, domainType, aggregation, collection);
return new ExecutableAggregationSupport<>(template, domainType, resultConverter, aggregation, collection);
}

@Override
public <R> TerminatingAggregation<R> map(QueryResultConverter<? super T, ? extends R> converter) {

Assert.notNull(converter, "QueryResultConverter must not be null");

return new ExecutableAggregationSupport<>(template, domainType, this.resultConverter.andThen(converter),
aggregation, collection);
}

@Override
public AggregationResults<T> all() {
return template.aggregate(aggregation, getCollectionName(aggregation), domainType);
return template.doAggregate(aggregation, getCollectionName(aggregation), domainType, resultConverter);
}

@Override
public Stream<T> stream() {
return template.aggregateStream(aggregation, getCollectionName(aggregation), domainType);
return template.doAggregateStream(aggregation, getCollectionName(aggregation), domainType, resultConverter, null);
}

private String getCollectionName(Aggregation aggregation) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.lang.Contract;
import org.springframework.lang.Nullable;

import com.mongodb.client.MongoCollection;
Expand Down Expand Up @@ -71,9 +72,33 @@ public interface ExecutableFindOperation {
* Trigger find execution by calling one of the terminating methods.
*
* @author Christoph Strobl
* @author Mark Paluch
* @since 2.0
*/
interface TerminatingFind<T> {
interface TerminatingFind<T> extends TerminatingResults<T>, TerminatingProjection {

}

/**
* Trigger find execution by calling one of the terminating methods.
*
* @author Christoph Strobl
* @author Mark Paluch
* @since x.y
*/
interface TerminatingResults<T> {

/**
* Map the query result to a different type using {@link QueryResultConverter}.
*
* @param <R> {@link Class type} of the result.
* @param converter the converter, must not be {@literal null}.
* @return new instance of {@link TerminatingResults}.
* @throws IllegalArgumentException if {@link QueryResultConverter converter} is {@literal null}.
* @since x.y
*/
@Contract("_ -> new")
<R> TerminatingResults<R> map(QueryResultConverter<? super T, ? extends R> converter);

/**
* Get exactly zero or one result.
Expand Down Expand Up @@ -142,6 +167,16 @@ default Optional<T> first() {
*/
Window<T> scroll(ScrollPosition scrollPosition);

}

/**
* Trigger find execution by calling one of the terminating methods.
*
* @author Christoph Strobl
* @since x.y
*/
interface TerminatingProjection {

/**
* Get the number of matching elements. <br />
* This method uses an
Expand All @@ -160,16 +195,30 @@ default Optional<T> first() {
* @return {@literal true} if at least one matching element exists.
*/
boolean exists();

}

/**
* Trigger geonear execution by calling one of the terminating methods.
* Trigger {@code geoNear} execution by calling one of the terminating methods.
*
* @author Christoph Strobl
* @author Mark Paluch
* @since 2.0
*/
interface TerminatingFindNear<T> {

/**
* Map the query result to a different type using {@link QueryResultConverter}.
*
* @param <R> {@link Class type} of the result.
* @param converter the converter, must not be {@literal null}.
* @return new instance of {@link TerminatingFindNear}.
* @throws IllegalArgumentException if {@link QueryResultConverter converter} is {@literal null}.
* @since x.y
*/
@Contract("_ -> new")
<R> TerminatingFindNear<R> map(QueryResultConverter<? super T, ? extends R> converter);

/**
* Find all matching elements and return them as {@link org.springframework.data.geo.GeoResult}.
*
Expand Down
Loading