Skip to content

Commit 2230b51

Browse files
christophstroblodrotbohm
authored andcommitted
DATAMONGO-1733 - Added support for projections on FluentMongoOperations.
Interfaces based projections handed to queries built using the FluentMongoOperations APIs now get projected as expected and also apply querying optimizations so that only fields needed in the projection are read in the first place. Original pull request: spring-projects#486.
1 parent 7258cb8 commit 2230b51

File tree

4 files changed

+375
-36
lines changed

4 files changed

+375
-36
lines changed

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

Lines changed: 92 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import lombok.AllArgsConstructor;
2323
import lombok.RequiredArgsConstructor;
2424

25+
import java.beans.PropertyDescriptor;
2526
import java.io.IOException;
2627
import java.util.ArrayList;
2728
import java.util.Collection;
@@ -112,11 +113,14 @@
112113
import org.springframework.data.mongodb.core.query.Query;
113114
import org.springframework.data.mongodb.core.query.Update;
114115
import org.springframework.data.mongodb.util.MongoClientVersion;
116+
import org.springframework.data.projection.ProjectionInformation;
117+
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
115118
import org.springframework.data.util.CloseableIterator;
116119
import org.springframework.data.util.Optionals;
117120
import org.springframework.data.util.Pair;
118121
import org.springframework.jca.cci.core.ConnectionCallback;
119122
import org.springframework.util.Assert;
123+
import org.springframework.util.ClassUtils;
120124
import org.springframework.util.CollectionUtils;
121125
import org.springframework.util.ObjectUtils;
122126
import org.springframework.util.ResourceUtils;
@@ -176,6 +180,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
176180
private static final String ID_FIELD = "_id";
177181
private static final WriteResultChecking DEFAULT_WRITE_RESULT_CHECKING = WriteResultChecking.NONE;
178182
private static final Collection<String> ITERABLE_CLASSES;
183+
public static final SpelAwareProxyProjectionFactory PROJECTION_FACTORY = new SpelAwareProxyProjectionFactory();
179184

180185
static {
181186

@@ -372,14 +377,14 @@ public CloseableIterator<T> doInCollection(MongoCollection<Document> collection)
372377

373378
MongoPersistentEntity<?> persistentEntity = mappingContext.getRequiredPersistentEntity(entityType);
374379

375-
Document mappedFields = queryMapper.getMappedFields(query.getFieldsObject(), persistentEntity);
380+
Document mappedFields = getMappedFieldsObject(query.getFieldsObject(), persistentEntity, returnType);
376381
Document mappedQuery = queryMapper.getMappedObject(query.getQueryObject(), persistentEntity);
377382

378383
FindIterable<Document> cursor = new QueryCursorPreparer(query, entityType)
379384
.prepare(collection.find(mappedQuery).projection(mappedFields));
380385

381386
return new CloseableIterableCursorAdapter<T>(cursor, exceptionTranslator,
382-
new ReadDocumentCallback<T>(mongoConverter, returnType, collectionName));
387+
new ProjectingReadCallback<>(mongoConverter, entityType, returnType, collectionName));
383388
}
384389
});
385390
}
@@ -694,7 +699,7 @@ public <T> GeoResults<T> geoNear(NearQuery near, Class<?> domainType, String col
694699
results = results == null ? Collections.emptyList() : results;
695700

696701
DocumentCallback<GeoResult<T>> callback = new GeoNearResultDocumentCallback<T>(
697-
new ReadDocumentCallback<T>(mongoConverter, returnType, collectionName), near.getMetric());
702+
new ProjectingReadCallback<>(mongoConverter, domainType, returnType, collectionName), near.getMetric());
698703
List<GeoResult<T>> result = new ArrayList<GeoResult<T>>(results.size());
699704

700705
int index = 0;
@@ -2037,7 +2042,7 @@ <S, T> List<T> doFind(String collectionName, Document query, Document fields, Cl
20372042

20382043
MongoPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(sourceClass);
20392044

2040-
Document mappedFields = queryMapper.getMappedFields(fields, entity);
2045+
Document mappedFields = getMappedFieldsObject(fields, entity, targetClass);
20412046
Document mappedQuery = queryMapper.getMappedObject(query, entity);
20422047

20432048
if (LOGGER.isDebugEnabled()) {
@@ -2046,7 +2051,7 @@ <S, T> List<T> doFind(String collectionName, Document query, Document fields, Cl
20462051
}
20472052

20482053
return executeFindMultiInternal(new FindCallback(mappedQuery, mappedFields), preparer,
2049-
new ReadDocumentCallback<T>(mongoConverter, targetClass, collectionName), collectionName);
2054+
new ProjectingReadCallback<>(mongoConverter, sourceClass, targetClass, collectionName), collectionName);
20502055
}
20512056

20522057
protected Document convertToDocument(CollectionOptions collectionOptions) {
@@ -2331,6 +2336,37 @@ private Document getMappedSortObject(Query query, Class<?> type) {
23312336
return queryMapper.getMappedSort(query.getSortObject(), mappingContext.getPersistentEntity(type));
23322337
}
23332338

2339+
private Document getMappedFieldsObject(Document fields, MongoPersistentEntity<?> entity, Class<?> targetType) {
2340+
return queryMapper.getMappedFields(addFieldsForProjection(fields, entity.getType(), targetType), entity);
2341+
}
2342+
2343+
/**
2344+
* For cases where {@code fields} is {@literal null} or {@literal empty} add fields required for creating the
2345+
* projection (target) type if the {@code targetType} is a {@literal closed interface projection}.
2346+
*
2347+
* @param fields can be {@literal null}.
2348+
* @param domainType must not be {@literal null}.
2349+
* @param targetType must not be {@literal null}.
2350+
* @return {@link Document} with fields to be included.
2351+
*/
2352+
private Document addFieldsForProjection(Document fields, Class<?> domainType, Class<?> targetType) {
2353+
2354+
if ((fields != null && !fields.isEmpty()) || !targetType.isInterface()
2355+
|| ClassUtils.isAssignable(domainType, targetType)) {
2356+
return fields;
2357+
}
2358+
2359+
ProjectionInformation projectionInformation = PROJECTION_FACTORY.getProjectionInformation(targetType);
2360+
if (projectionInformation.isClosed()) {
2361+
2362+
for (PropertyDescriptor descriptor : projectionInformation.getInputProperties()) {
2363+
fields.append(descriptor.getName(), 1);
2364+
}
2365+
}
2366+
2367+
return fields;
2368+
}
2369+
23342370
/**
23352371
* Tries to convert the given {@link RuntimeException} into a {@link DataAccessException} but returns the original
23362372
* exception if the conversation failed. Thus allows safe re-throwing of the return value.
@@ -2568,6 +2604,57 @@ public T doWith(Document object) {
25682604
}
25692605
}
25702606

2607+
/**
2608+
* {@link DocumentCallback} transforming {@link Document} into the given {@code targetType} or decorating the
2609+
* {@code sourceType} with a {@literal projection} in case the {@code targetType} is an {@litera interface}.
2610+
*
2611+
* @param <S>
2612+
* @param <T>
2613+
* @since 2.0
2614+
*/
2615+
class ProjectingReadCallback<S, T> implements DocumentCallback<T> {
2616+
2617+
private final Class<S> entityType;
2618+
private final Class<T> targetType;
2619+
private final String collectionName;
2620+
private final EntityReader<Object, Bson> reader;
2621+
2622+
ProjectingReadCallback(EntityReader<Object, Bson> reader, Class<S> entityType, Class<T> targetType,
2623+
String collectionName) {
2624+
2625+
this.reader = reader;
2626+
this.entityType = entityType;
2627+
this.targetType = targetType;
2628+
this.collectionName = collectionName;
2629+
}
2630+
2631+
public T doWith(Document object) {
2632+
2633+
if (null != object) {
2634+
maybeEmitEvent(new AfterLoadEvent<>(object, targetType, collectionName));
2635+
}
2636+
2637+
T target = doRead(object, entityType, targetType);
2638+
2639+
if (null != target) {
2640+
maybeEmitEvent(new AfterConvertEvent<>(object, target, collectionName));
2641+
}
2642+
2643+
return target;
2644+
}
2645+
2646+
private T doRead(Document source, Class entityType, Class targetType) {
2647+
2648+
if (targetType != entityType && targetType.isInterface()) {
2649+
2650+
S target = (S) reader.read(entityType, source);
2651+
return (T) PROJECTION_FACTORY.createProjection(targetType, target);
2652+
}
2653+
2654+
return (T) reader.read(targetType, source);
2655+
}
2656+
}
2657+
25712658
class UnwrapAndReadDocumentCallback<T> extends ReadDocumentCallback<T> {
25722659

25732660
public UnwrapAndReadDocumentCallback(EntityReader<? super T, Bson> reader, Class<T> type, String collectionName) {

0 commit comments

Comments
 (0)