Skip to content

Add support for direct and nested projections #3894

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

Closed
wants to merge 8 commits into from
Closed
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
4 changes: 2 additions & 2 deletions 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>3.4.0-SNAPSHOT</version>
<version>3.4.0-2860-SNAPSHOT</version>
<packaging>pom</packaging>

<name>Spring Data MongoDB</name>
Expand All @@ -26,7 +26,7 @@
<properties>
<project.type>multi</project.type>
<dist.id>spring-data-mongodb</dist.id>
<springdata.commons>2.7.0-SNAPSHOT</springdata.commons>
<springdata.commons>2.7.0-GH-2420-SNAPSHOT</springdata.commons>
<mongo>4.4.0</mongo>
<mongo.reactivestreams>${mongo}</mongo.reactivestreams>
<jmh.version>1.19</jmh.version>
Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb-benchmarks/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>3.4.0-SNAPSHOT</version>
<version>3.4.0-2860-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

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 @@ -14,7 +14,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>3.4.0-SNAPSHOT</version>
<version>3.4.0-2860-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 @@ -11,7 +11,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>3.4.0-SNAPSHOT</version>
<version>3.4.0-2860-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,17 @@
import org.bson.Document;
import org.springframework.core.convert.ConversionService;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.mapping.IdentifierAccessor;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.context.EntityProjection;
import org.springframework.data.mapping.context.EntityProjectionIntrospector;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
import org.springframework.data.mongodb.core.CollectionOptions.TimeSeriesOptions;
import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.data.mongodb.core.convert.MongoWriter;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
Expand All @@ -39,6 +43,7 @@
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.timeseries.Granularity;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
Expand All @@ -63,8 +68,19 @@ class EntityOperations {

private final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> context;

EntityOperations(MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> context) {
private final EntityProjectionIntrospector introspector;

EntityOperations(MongoConverter converter) {
this(converter.getMappingContext(), converter.getCustomConversions(), converter.getProjectionFactory());
}

EntityOperations(MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> context,
CustomConversions conversions, ProjectionFactory projectionFactory) {
this.context = context;
this.introspector = EntityProjectionIntrospector.create(projectionFactory,
EntityProjectionIntrospector.ProjectionPredicate.typeHierarchy()
.and(((target, underlyingType) -> !conversions.isSimpleType(target))),
context);
}

/**
Expand Down Expand Up @@ -229,6 +245,11 @@ public <T> TypedOperations<T> forType(@Nullable Class<T> entityClass) {
return UntypedOperations.instance();
}

public <M, D> EntityProjection<M, D> introspectProjection(Class<M> resultType,
Class<D> entityType) {
return introspector.introspect(resultType, entityType);
}

/**
* A representation of information about an entity.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
import org.springframework.data.geo.Metric;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.callback.EntityCallbacks;
import org.springframework.data.mapping.context.EntityProjection;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.MongoDatabaseUtils;
Expand Down Expand Up @@ -102,7 +103,6 @@
import org.springframework.data.mongodb.core.timeseries.Granularity;
import org.springframework.data.mongodb.core.validation.Validator;
import org.springframework.data.mongodb.util.BsonUtils;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.util.CloseableIterator;
import org.springframework.data.util.Optionals;
import org.springframework.lang.Nullable;
Expand Down Expand Up @@ -173,7 +173,6 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
private final QueryMapper queryMapper;
private final UpdateMapper updateMapper;
private final JsonSchemaMapper schemaMapper;
private final SpelAwareProxyProjectionFactory projectionFactory;
private final EntityOperations operations;
private final PropertyOperations propertyOperations;
private final QueryOperations queryOperations;
Expand Down Expand Up @@ -225,8 +224,7 @@ public MongoTemplate(MongoDatabaseFactory mongoDbFactory, @Nullable MongoConvert
this.queryMapper = new QueryMapper(this.mongoConverter);
this.updateMapper = new UpdateMapper(this.mongoConverter);
this.schemaMapper = new MongoJsonSchemaMapper(this.mongoConverter);
this.projectionFactory = new SpelAwareProxyProjectionFactory();
this.operations = new EntityOperations(this.mongoConverter.getMappingContext());
this.operations = new EntityOperations(this.mongoConverter);
this.propertyOperations = new PropertyOperations(this.mongoConverter.getMappingContext());
this.queryOperations = new QueryOperations(queryMapper, updateMapper, operations, propertyOperations,
mongoDbFactory);
Expand Down Expand Up @@ -264,7 +262,6 @@ private MongoTemplate(MongoDatabaseFactory dbFactory, MongoTemplate that) {
this.queryMapper = that.queryMapper;
this.updateMapper = that.updateMapper;
this.schemaMapper = that.schemaMapper;
this.projectionFactory = that.projectionFactory;
this.mappingContext = that.mappingContext;
this.operations = that.operations;
this.propertyOperations = that.propertyOperations;
Expand Down Expand Up @@ -330,9 +327,6 @@ public void setApplicationContext(ApplicationContext applicationContext) throws
}

resourceLoader = applicationContext;

projectionFactory.setBeanFactory(applicationContext);
projectionFactory.setBeanClassLoader(applicationContext.getClassLoader());
}

/**
Expand Down Expand Up @@ -416,15 +410,17 @@ protected <T> CloseableIterator<T> doStream(Query query, Class<?> entityType, St
MongoPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(entityType);

QueryContext queryContext = queryOperations.createQueryContext(query);
EntityProjection<T, ?> projection = operations.introspectProjection(returnType,
entityType);

Document mappedQuery = queryContext.getMappedQuery(persistentEntity);
Document mappedFields = queryContext.getMappedFields(persistentEntity, returnType, projectionFactory);
Document mappedFields = queryContext.getMappedFields(persistentEntity, projection);

FindIterable<Document> cursor = new QueryCursorPreparer(query, entityType).initiateFind(collection,
col -> col.find(mappedQuery, Document.class).projection(mappedFields));

return new CloseableIterableCursorAdapter<>(cursor, exceptionTranslator,
new ProjectingReadCallback<>(mongoConverter, entityType, returnType, collectionName));
new ProjectingReadCallback<>(mongoConverter, projection, collectionName));
});
}

Expand Down Expand Up @@ -964,9 +960,11 @@ public <T> GeoResults<T> geoNear(NearQuery near, Class<?> domainType, String col
.withOptions(AggregationOptions.builder().collation(near.getCollation()).build());

AggregationResults<Document> results = aggregate($geoNear, collection, Document.class);
EntityProjection<T, ?> projection = operations.introspectProjection(returnType,
domainType);

DocumentCallback<GeoResult<T>> callback = new GeoNearResultDocumentCallback<>(distanceField,
new ProjectingReadCallback<>(mongoConverter, domainType, returnType, collection), near.getMetric());
new ProjectingReadCallback<>(mongoConverter, projection, collection), near.getMetric());

List<GeoResult<T>> result = new ArrayList<>();

Expand Down Expand Up @@ -1050,8 +1048,10 @@ public <S, T> T findAndReplace(Query query, S replacement, FindAndReplaceOptions
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityType);
QueryContext queryContext = queryOperations.createQueryContext(query);

EntityProjection<T, S> projection = operations.introspectProjection(resultType,
entityType);
Document mappedQuery = queryContext.getMappedQuery(entity);
Document mappedFields = queryContext.getMappedFields(entity, resultType, projectionFactory);
Document mappedFields = queryContext.getMappedFields(entity, projection);
Document mappedSort = queryContext.getMappedSort(entity);

replacement = maybeCallBeforeConvert(replacement, collectionName);
Expand All @@ -1061,7 +1061,8 @@ public <S, T> T findAndReplace(Query query, S replacement, FindAndReplaceOptions
maybeCallBeforeSave(replacement, mappedReplacement, collectionName);

T saved = doFindAndReplace(collectionName, mappedQuery, mappedFields, mappedSort,
queryContext.getCollation(entityType).orElse(null), entityType, mappedReplacement, options, resultType);
queryContext.getCollation(entityType).orElse(null), entityType, mappedReplacement, options,
projection);

if (saved != null) {
maybeEmitEvent(new AfterSaveEvent<>(saved, mappedReplacement, collectionName));
Expand Down Expand Up @@ -2499,7 +2500,8 @@ protected <T> T doFindOne(String collectionName, Document query, Document fields
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);

QueryContext queryContext = queryOperations.createQueryContext(new BasicQuery(query, fields));
Document mappedFields = queryContext.getMappedFields(entity, entityClass, projectionFactory);
Document mappedFields = queryContext.getMappedFields(entity,
EntityProjection.nonProjecting(entityClass));
Document mappedQuery = queryContext.getMappedQuery(entity);

if (LOGGER.isDebugEnabled()) {
Expand Down Expand Up @@ -2551,7 +2553,8 @@ protected <S, T> List<T> doFind(String collectionName, Document query, Document
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);

QueryContext queryContext = queryOperations.createQueryContext(new BasicQuery(query, fields));
Document mappedFields = queryContext.getMappedFields(entity, entityClass, projectionFactory);
Document mappedFields = queryContext.getMappedFields(entity,
EntityProjection.nonProjecting(entityClass));
Document mappedQuery = queryContext.getMappedQuery(entity);

if (LOGGER.isDebugEnabled()) {
Expand All @@ -2573,9 +2576,11 @@ <S, T> List<T> doFind(String collectionName, Document query, Document fields, Cl
Class<T> targetClass, CursorPreparer preparer) {

MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(sourceClass);
EntityProjection<T, S> projection = operations.introspectProjection(targetClass,
sourceClass);

QueryContext queryContext = queryOperations.createQueryContext(new BasicQuery(query, fields));
Document mappedFields = queryContext.getMappedFields(entity, targetClass, projectionFactory);
Document mappedFields = queryContext.getMappedFields(entity, projection);
Document mappedQuery = queryContext.getMappedQuery(entity);

if (LOGGER.isDebugEnabled()) {
Expand All @@ -2584,9 +2589,10 @@ <S, T> List<T> doFind(String collectionName, Document query, Document fields, Cl
}

return executeFindMultiInternal(new FindCallback(mappedQuery, mappedFields, null), preparer,
new ProjectingReadCallback<>(mongoConverter, sourceClass, targetClass, collectionName), collectionName);
new ProjectingReadCallback<>(mongoConverter, projection, collectionName), collectionName);
}


/**
* Convert given {@link CollectionOptions} to a document and take the domain type information into account when
* creating a mapped schema for validation. <br />
Expand Down Expand Up @@ -2745,6 +2751,35 @@ protected <T> T doFindAndReplace(String collectionName, Document mappedQuery, Do
Document mappedSort, @Nullable com.mongodb.client.model.Collation collation, Class<?> entityType,
Document replacement, FindAndReplaceOptions options, Class<T> resultType) {

EntityProjection<T, ?> projection = operations.introspectProjection(resultType,
entityType);

return doFindAndReplace(collectionName, mappedQuery, mappedFields, mappedSort, collation, entityType, replacement,
options, projection);
}

/**
* Customize this part for findAndReplace.
*
* @param collectionName The name of the collection to perform the operation in.
* @param mappedQuery the query to look up documents.
* @param mappedFields the fields to project the result to.
* @param mappedSort the sort to be applied when executing the query.
* @param collation collation settings for the query. Can be {@literal null}.
* @param entityType the source domain type.
* @param replacement the replacement {@link Document}.
* @param options applicable options.
* @param projection the projection descriptor.
* @return {@literal null} if object does not exist, {@link FindAndReplaceOptions#isReturnNew() return new} is
* {@literal false} and {@link FindAndReplaceOptions#isUpsert() upsert} is {@literal false}.
* @since 3.4
*/
@Nullable
private <T> T doFindAndReplace(String collectionName, Document mappedQuery, Document mappedFields,
Document mappedSort, @Nullable com.mongodb.client.model.Collation collation, Class<?> entityType,
Document replacement, FindAndReplaceOptions options,
EntityProjection<T, ?> projection) {

if (LOGGER.isDebugEnabled()) {
LOGGER.debug(String.format(
"findAndReplace using query: %s fields: %s sort: %s for class: %s and replacement: %s " + "in collection: %s",
Expand All @@ -2754,7 +2789,7 @@ protected <T> T doFindAndReplace(String collectionName, Document mappedQuery, Do

return executeFindOneInternal(
new FindAndReplaceCallback(mappedQuery, mappedFields, mappedSort, replacement, collation, options),
new ProjectingReadCallback<>(mongoConverter, entityType, resultType, collectionName), collectionName);
new ProjectingReadCallback<>(mongoConverter, projection, collectionName), collectionName);
}

/**
Expand Down Expand Up @@ -3205,17 +3240,15 @@ public T doWith(Document document) {
*/
private class ProjectingReadCallback<S, T> implements DocumentCallback<T> {

private final EntityReader<Object, Bson> reader;
private final Class<S> entityType;
private final Class<T> targetType;
private final MongoConverter reader;
private final EntityProjection<T, S> projection;
private final String collectionName;

ProjectingReadCallback(EntityReader<Object, Bson> reader, Class<S> entityType, Class<T> targetType,
ProjectingReadCallback(MongoConverter reader, EntityProjection<T, S> projection,
String collectionName) {

this.reader = reader;
this.entityType = entityType;
this.targetType = targetType;
this.projection = projection;
this.collectionName = collectionName;
}

Expand All @@ -3230,21 +3263,16 @@ public T doWith(Document document) {
return null;
}

Class<?> typeToRead = targetType.isInterface() || targetType.isAssignableFrom(entityType) ? entityType
: targetType;
maybeEmitEvent(new AfterLoadEvent<>(document, projection.getMappedType().getType(), collectionName));

maybeEmitEvent(new AfterLoadEvent<>(document, targetType, collectionName));

Object entity = reader.read(typeToRead, document);
Object entity = reader.project(projection, document);

if (entity == null) {
throw new MappingException(String.format("EntityReader %s returned null", reader));
}

Object result = targetType.isInterface() ? projectionFactory.createProjection(targetType, entity) : entity;

maybeEmitEvent(new AfterConvertEvent<>(document, result, collectionName));
return (T) maybeCallAfterConvert(result, document, collectionName);
maybeEmitEvent(new AfterConvertEvent<>(document, entity, collectionName));
return (T) maybeCallAfterConvert(entity, document, collectionName);
}
}

Expand Down
Loading