Skip to content

DATAMONGO-2004 - Support lazy DBRef resolution through constructor creation of the enclosing entity. #571

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 3 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
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>2.1.0.BUILD-SNAPSHOT</version>
<version>2.1.0.DATAMONGO-2004-SNAPSHOT</version>
<packaging>pom</packaging>

<name>Spring Data MongoDB</name>
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>2.1.0.BUILD-SNAPSHOT</version>
<version>2.1.0.DATAMONGO-2004-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
4 changes: 2 additions & 2 deletions spring-data-mongodb-cross-store/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>2.1.0.BUILD-SNAPSHOT</version>
<version>2.1.0.DATAMONGO-2004-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down Expand Up @@ -50,7 +50,7 @@
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>2.1.0.BUILD-SNAPSHOT</version>
<version>2.1.0.DATAMONGO-2004-SNAPSHOT</version>
</dependency>

<!-- reactive -->
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 @@ -13,7 +13,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>2.1.0.BUILD-SNAPSHOT</version>
<version>2.1.0.DATAMONGO-2004-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>2.1.0.BUILD-SNAPSHOT</version>
<version>2.1.0.DATAMONGO-2004-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ public DocumentAccessor(Bson document) {
this.document = document;
}

/**
* @return the underlying {@link Bson document}.
* @since 2.1
*/
public Bson getDocument() {
return this.document;
}

/**
* Puts the given value into the backing {@link Document} based on the coordinates defined through the given
* {@link MongoPersistentProperty}. By default this will be the plain field name. But field names might also consist
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,8 @@ private <S extends Object> S read(TypeInformation<S> type, @Nullable Bson bson,
private ParameterValueProvider<MongoPersistentProperty> getParameterProvider(MongoPersistentEntity<?> entity,
Bson source, DefaultSpELExpressionEvaluator evaluator, ObjectPath path) {

MongoDbPropertyValueProvider provider = new MongoDbPropertyValueProvider(source, evaluator, path);
AssociationAwareMongoDbPropertyValueProvider provider = new AssociationAwareMongoDbPropertyValueProvider(source,
evaluator, path);
PersistentEntityParameterValueProvider<MongoPersistentProperty> parameterProvider = new PersistentEntityParameterValueProvider<>(
entity, provider, path.getCurrentObject());

Expand Down Expand Up @@ -659,7 +660,8 @@ protected Bson createMap(Map<Object, Object> map, MongoPersistentProperty proper
* @param sink the {@link Collection} to write to.
* @return
*/
private List<Object> writeCollectionInternal(Collection<?> source, @Nullable TypeInformation<?> type, Collection<?> sink) {
private List<Object> writeCollectionInternal(Collection<?> source, @Nullable TypeInformation<?> type,
Collection<?> sink) {

TypeInformation<?> componentType = null;

Expand Down Expand Up @@ -867,7 +869,7 @@ private Object getPotentiallyConvertedSimpleWrite(@Nullable Object value) {
*/
@Nullable
@SuppressWarnings({ "rawtypes", "unchecked" })
private Object getPotentiallyConvertedSimpleRead(@Nullable Object value, @Nullable Class<?> target) {
private Object getPotentiallyConvertedSimpleRead(@Nullable Object value, @Nullable Class<?> target) {

if (value == null || target == null || ClassUtils.isAssignableValue(target, value)) {
return value;
Expand Down Expand Up @@ -1270,12 +1272,14 @@ private Object removeTypeInfo(Object object, boolean recursively) {
* of the configured source {@link Document}.
*
* @author Oliver Gierke
* @author Mark Paluch
* @author Christoph Strobl
*/
class MongoDbPropertyValueProvider implements PropertyValueProvider<MongoPersistentProperty> {

private final DocumentAccessor source;
private final SpELExpressionEvaluator evaluator;
private final ObjectPath path;
final DocumentAccessor source;
final SpELExpressionEvaluator evaluator;
final ObjectPath path;

/**
* Creates a new {@link MongoDbPropertyValueProvider} for the given source, {@link SpELExpressionEvaluator} and
Expand All @@ -1285,15 +1289,8 @@ class MongoDbPropertyValueProvider implements PropertyValueProvider<MongoPersist
* @param evaluator must not be {@literal null}.
* @param path must not be {@literal null}.
*/
public MongoDbPropertyValueProvider(Bson source, SpELExpressionEvaluator evaluator, ObjectPath path) {

Assert.notNull(source, "Source document must no be null!");
Assert.notNull(evaluator, "SpELExpressionEvaluator must not be null!");
Assert.notNull(path, "ObjectPath must not be null!");

this.source = new DocumentAccessor(source);
this.evaluator = evaluator;
this.path = path;
MongoDbPropertyValueProvider(Bson source, SpELExpressionEvaluator evaluator, ObjectPath path) {
this(new DocumentAccessor(source), evaluator, path);
}

/**
Expand All @@ -1304,7 +1301,7 @@ public MongoDbPropertyValueProvider(Bson source, SpELExpressionEvaluator evaluat
* @param evaluator must not be {@literal null}.
* @param path must not be {@literal null}.
*/
public MongoDbPropertyValueProvider(DocumentAccessor accessor, SpELExpressionEvaluator evaluator, ObjectPath path) {
MongoDbPropertyValueProvider(DocumentAccessor accessor, SpELExpressionEvaluator evaluator, ObjectPath path) {

Assert.notNull(accessor, "DocumentAccessor must no be null!");
Assert.notNull(evaluator, "SpELExpressionEvaluator must not be null!");
Expand All @@ -1320,6 +1317,7 @@ public MongoDbPropertyValueProvider(DocumentAccessor accessor, SpELExpressionEva
* @see org.springframework.data.convert.PropertyValueProvider#getPropertyValue(org.springframework.data.mapping.PersistentProperty)
*/
@Nullable
@SuppressWarnings("unchecked")
public <T> T getPropertyValue(MongoPersistentProperty property) {

String expression = property.getSpelExpression();
Expand All @@ -1333,6 +1331,54 @@ public <T> T getPropertyValue(MongoPersistentProperty property) {
}
}

/**
* {@link PropertyValueProvider} that is aware of {@link MongoPersistentProperty#isAssociation()} and that delegates
* resolution to {@link DbRefResolver}.
*
* @author Mark Paluch
* @author Christoph Strobl
* @since 2.1
*/
class AssociationAwareMongoDbPropertyValueProvider extends MongoDbPropertyValueProvider {

/**
* Creates a new {@link AssociationAwareMongoDbPropertyValueProvider} for the given source,
* {@link SpELExpressionEvaluator} and {@link ObjectPath}.
*
* @param source must not be {@literal null}.
* @param evaluator must not be {@literal null}.
* @param path must not be {@literal null}.
*/
AssociationAwareMongoDbPropertyValueProvider(Bson source, SpELExpressionEvaluator evaluator, ObjectPath path) {
super(source, evaluator, path);
}

/*
* (non-Javadoc)
* @see org.springframework.data.convert.PropertyValueProvider#getPropertyValue(org.springframework.data.mapping.PersistentProperty)
*/
@Nullable
@SuppressWarnings("unchecked")
public <T> T getPropertyValue(MongoPersistentProperty property) {

if (property.isDbReference() && property.getDBRef().lazy()) {

Object rawRefValue = source.get(property);
if (rawRefValue == null) {
return null;
}

DbRefResolverCallback callback = new DefaultDbRefResolverCallback(source.getDocument(), path, evaluator,
MappingMongoConverter.this);

DBRef dbref = rawRefValue instanceof DBRef ? (DBRef) rawRefValue : null;
return (T) dbRefResolver.resolveDbRef(property, dbref, callback, dbRefProxyHandler);
}

return super.getPropertyValue(property);
}
}

/**
* Extension of {@link SpELExpressionParameterValueProvider} to recursively trigger value conversion on the raw
* resolved SpEL value.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3069,8 +3069,8 @@ public void resolvesCyclicDBRefCorrectly() {
assertThat(contentLoaded.dbrefMessage.id, is(messageLoaded.id));
}

@Test // DATAMONGO-1287
public void shouldReuseAlreadyResolvedLazyLoadedDBRefWhenUsedAsPersistenceConstrcutorArgument() {
@Test // DATAMONGO-1287, DATAMONGO-2004
public void shouldReuseAlreadyResolvedLazyLoadedDBRefWhenUsedAsPersistenceConstructorArgument() {

Document docInCtor = new Document();
docInCtor.id = "doc-in-ctor";
Expand All @@ -3083,7 +3083,7 @@ public void shouldReuseAlreadyResolvedLazyLoadedDBRefWhenUsedAsPersistenceConstr

DocumentWithLazyDBrefUsedInPresistenceConstructor loaded = template.findOne(query(where("id").is(source.id)),
DocumentWithLazyDBrefUsedInPresistenceConstructor.class);
assertThat(loaded.refToDocUsedInCtor, not(instanceOf(LazyLoadingProxy.class)));
assertThat(loaded.refToDocUsedInCtor, instanceOf(LazyLoadingProxy.class));
assertThat(loaded.refToDocNotUsedInCtor, nullValue());
}

Expand All @@ -3106,8 +3106,8 @@ public void shouldNotReuseLazyLoadedDBRefWhenTypeUsedInPersistenceConstrcutorBut
assertThat(loaded.refToDocUsedInCtor, nullValue());
}

@Test // DATAMONGO-1287
public void shouldRespectParamterValueWhenAttemptingToReuseLazyLoadedDBRefUsedInPersistenceConstrcutor() {
@Test // DATAMONGO-1287, DATAMONGO-2004
public void shouldRespectParameterValueWhenAttemptingToReuseLazyLoadedDBRefUsedInPersistenceConstructor() {

Document docInCtor = new Document();
docInCtor.id = "doc-in-ctor";
Expand All @@ -3125,7 +3125,7 @@ public void shouldRespectParamterValueWhenAttemptingToReuseLazyLoadedDBRefUsedIn

DocumentWithLazyDBrefUsedInPresistenceConstructor loaded = template.findOne(query(where("id").is(source.id)),
DocumentWithLazyDBrefUsedInPresistenceConstructor.class);
assertThat(loaded.refToDocUsedInCtor, not(instanceOf(LazyLoadingProxy.class)));
assertThat(loaded.refToDocUsedInCtor, instanceOf(LazyLoadingProxy.class));
assertThat(loaded.refToDocNotUsedInCtor, instanceOf(LazyLoadingProxy.class));
}

Expand Down Expand Up @@ -3384,6 +3384,73 @@ public void shouldFetchMapOfLazyReferencesCorrectly() {
assertThat(target.lazyDbRefAnnotatedMap.values(), contains(two, one));
}

@Test // DATAMONGO-2004
public void shouldFetchLazyReferenceWithConstructorCreationCorrectly() {

Sample one = new Sample("1", "jon snow");

template.save(one);

DocumentWithLazyDBRefsAndConstructorCreation source = new DocumentWithLazyDBRefsAndConstructorCreation(null, one,
null, null);

template.save(source);

DocumentWithLazyDBRefsAndConstructorCreation target = template.findOne(query(where("id").is(source.id)),
DocumentWithLazyDBRefsAndConstructorCreation.class);

assertThat(target.lazyDbRefProperty, instanceOf(LazyLoadingProxy.class));
assertThat(target.lazyDbRefProperty, is(one));
}

@Test // DATAMONGO-2004
public void shouldFetchMapOfLazyReferencesWithConstructorCreationCorrectly() {

Sample one = new Sample("1", "jon snow");
Sample two = new Sample("2", "tyrion lannister");

template.save(one);
template.save(two);

Map<String, Sample> map = new LinkedHashMap<>();
map.put("tyrion", two);
map.put("jon", one);

DocumentWithLazyDBRefsAndConstructorCreation source = new DocumentWithLazyDBRefsAndConstructorCreation(null, null,
null, map);

template.save(source);

DocumentWithLazyDBRefsAndConstructorCreation target = template.findOne(query(where("id").is(source.id)),
DocumentWithLazyDBRefsAndConstructorCreation.class);

assertThat(target.lazyDbRefAnnotatedMap, instanceOf(LazyLoadingProxy.class));
assertThat(target.lazyDbRefAnnotatedMap.values(), contains(two, one));
}

@Test // DATAMONGO-2004
public void shouldFetchListOfLazyReferencesWithConstructorCreationCorrectly() {

Sample one = new Sample("1", "jon snow");
Sample two = new Sample("2", "tyrion lannister");

template.save(one);
template.save(two);

List<Sample> list = Arrays.asList(two, one);

DocumentWithLazyDBRefsAndConstructorCreation source = new DocumentWithLazyDBRefsAndConstructorCreation(null, null,
list, null);

template.save(source);

DocumentWithLazyDBRefsAndConstructorCreation target = template.findOne(query(where("id").is(source.id)),
DocumentWithLazyDBRefsAndConstructorCreation.class);

assertThat(target.lazyDbRefAnnotatedList, instanceOf(LazyLoadingProxy.class));
assertThat(target.getLazyDbRefAnnotatedList(), contains(two, one));
}

@Test // DATAMONGO-1513
@DirtiesContext
public void populatesIdsAddedByEventListener() {
Expand Down Expand Up @@ -3590,6 +3657,29 @@ static class DocumentWithDBRefCollection {
@org.springframework.data.mongodb.core.mapping.DBRef(lazy = true) public Map<String, Sample> lazyDbRefAnnotatedMap;
}

@Data
static class DocumentWithLazyDBRefsAndConstructorCreation {

@Id public final String id;

public DocumentWithLazyDBRefsAndConstructorCreation(String id, Sample lazyDbRefProperty,
List<Sample> lazyDbRefAnnotatedList, Map<String, Sample> lazyDbRefAnnotatedMap) {
this.id = id;
this.lazyDbRefProperty = lazyDbRefProperty;
this.lazyDbRefAnnotatedList = lazyDbRefAnnotatedList;
this.lazyDbRefAnnotatedMap = lazyDbRefAnnotatedMap;
}

@org.springframework.data.mongodb.core.mapping.DBRef(lazy = true) //
public final Sample lazyDbRefProperty;

@Field("lazy_db_ref_list") @org.springframework.data.mongodb.core.mapping.DBRef(lazy = true) //
public final List<Sample> lazyDbRefAnnotatedList;

@Field("lazy_db_ref_map") @org.springframework.data.mongodb.core.mapping.DBRef(
lazy = true) public final Map<String, Sample> lazyDbRefAnnotatedMap;
}

@EqualsAndHashCode
static class DocumentWithCollection {

Expand Down
Loading