Skip to content

Commit 5ba5cc3

Browse files
committed
DATAMONGO-2004 - Support lazy DBRef resolution through constructor creation of the enclosing entity.
We now respect eager/lazy loading preferences of the annotated association property when the enclosing entity is created through its constructor and the reference is passed as constructor argument. Previously, we eagerly resolved DBRefs and passed the resolved value to the constructor.
1 parent bf13735 commit 5ba5cc3

File tree

3 files changed

+154
-10
lines changed

3 files changed

+154
-10
lines changed

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@ public DocumentAccessor(Bson document) {
5858
this.document = document;
5959
}
6060

61+
/**
62+
* @return the underlying {@link Bson document}.
63+
* @since 2.1
64+
*/
65+
public Bson getDocument() {
66+
return this.document;
67+
}
68+
6169
/**
6270
* Puts the given value into the backing {@link Document} based on the coordinates defined through the given
6371
* {@link MongoPersistentProperty}. By default this will be the plain field name. But field names might also consist

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

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,8 @@ private <S extends Object> S read(TypeInformation<S> type, @Nullable Bson bson,
255255
private ParameterValueProvider<MongoPersistentProperty> getParameterProvider(MongoPersistentEntity<?> entity,
256256
Bson source, DefaultSpELExpressionEvaluator evaluator, ObjectPath path) {
257257

258-
MongoDbPropertyValueProvider provider = new MongoDbPropertyValueProvider(source, evaluator, path);
258+
AssociationAwareMongoDbPropertyValueProvider provider = new AssociationAwareMongoDbPropertyValueProvider(source,
259+
evaluator, path);
259260
PersistentEntityParameterValueProvider<MongoPersistentProperty> parameterProvider = new PersistentEntityParameterValueProvider<>(
260261
entity, provider, path.getCurrentObject());
261262

@@ -1273,9 +1274,9 @@ private Object removeTypeInfo(Object object, boolean recursively) {
12731274
*/
12741275
class MongoDbPropertyValueProvider implements PropertyValueProvider<MongoPersistentProperty> {
12751276

1276-
private final DocumentAccessor source;
1277-
private final SpELExpressionEvaluator evaluator;
1278-
private final ObjectPath path;
1277+
final DocumentAccessor source;
1278+
final SpELExpressionEvaluator evaluator;
1279+
final ObjectPath path;
12791280

12801281
/**
12811282
* Creates a new {@link MongoDbPropertyValueProvider} for the given source, {@link SpELExpressionEvaluator} and
@@ -1320,6 +1321,7 @@ public MongoDbPropertyValueProvider(DocumentAccessor accessor, SpELExpressionEva
13201321
* @see org.springframework.data.convert.PropertyValueProvider#getPropertyValue(org.springframework.data.mapping.PersistentProperty)
13211322
*/
13221323
@Nullable
1324+
@SuppressWarnings("unchecked")
13231325
public <T> T getPropertyValue(MongoPersistentProperty property) {
13241326

13251327
String expression = property.getSpelExpression();
@@ -1333,6 +1335,50 @@ public <T> T getPropertyValue(MongoPersistentProperty property) {
13331335
}
13341336
}
13351337

1338+
/**
1339+
* {@link PropertyValueProvider} that is aware of {@link MongoPersistentProperty#isAssociation()} and that delegates
1340+
* resolution to {@link DbRefResolver}.
1341+
*
1342+
* @author Mark Paluch
1343+
* @since 2.1
1344+
*/
1345+
class AssociationAwareMongoDbPropertyValueProvider extends MongoDbPropertyValueProvider {
1346+
1347+
/**
1348+
* Creates a new {@link AssociationAwareMongoDbPropertyValueProvider} for the given source,
1349+
* {@link SpELExpressionEvaluator} and {@link ObjectPath}.
1350+
*
1351+
* @param source must not be {@literal null}.
1352+
* @param evaluator must not be {@literal null}.
1353+
* @param path must not be {@literal null}.
1354+
*/
1355+
public AssociationAwareMongoDbPropertyValueProvider(Bson source, SpELExpressionEvaluator evaluator,
1356+
ObjectPath path) {
1357+
super(source, evaluator, path);
1358+
}
1359+
1360+
/*
1361+
* (non-Javadoc)
1362+
* @see org.springframework.data.convert.PropertyValueProvider#getPropertyValue(org.springframework.data.mapping.PersistentProperty)
1363+
*/
1364+
@Nullable
1365+
@SuppressWarnings("unchecked")
1366+
public <T> T getPropertyValue(MongoPersistentProperty property) {
1367+
1368+
T value = super.getPropertyValue(property);
1369+
1370+
if (value == null || !property.isAssociation()) {
1371+
return value;
1372+
}
1373+
1374+
DbRefResolverCallback callback = new DefaultDbRefResolverCallback(source.getDocument(), path, evaluator,
1375+
MappingMongoConverter.this);
1376+
DBRef dbref = value instanceof DBRef ? (DBRef) value : null;
1377+
1378+
return (T) dbRefResolver.resolveDbRef(property, dbref, callback, dbRefProxyHandler);
1379+
}
1380+
}
1381+
13361382
/**
13371383
* Extension of {@link SpELExpressionParameterValueProvider} to recursively trigger value conversion on the raw
13381384
* resolved SpEL value.

spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java

Lines changed: 96 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2935,8 +2935,8 @@ public void resolvesCyclicDBRefCorrectly() {
29352935
assertThat(contentLoaded.dbrefMessage.id, is(messageLoaded.id));
29362936
}
29372937

2938-
@Test // DATAMONGO-1287
2939-
public void shouldReuseAlreadyResolvedLazyLoadedDBRefWhenUsedAsPersistenceConstrcutorArgument() {
2938+
@Test // DATAMONGO-1287, DATAMONGO-2004
2939+
public void shouldReuseAlreadyResolvedLazyLoadedDBRefWhenUsedAsPersistenceConstructorArgument() {
29402940

29412941
Document docInCtor = new Document();
29422942
docInCtor.id = "doc-in-ctor";
@@ -2949,7 +2949,7 @@ public void shouldReuseAlreadyResolvedLazyLoadedDBRefWhenUsedAsPersistenceConstr
29492949

29502950
DocumentWithLazyDBrefUsedInPresistenceConstructor loaded = template.findOne(query(where("id").is(source.id)),
29512951
DocumentWithLazyDBrefUsedInPresistenceConstructor.class);
2952-
assertThat(loaded.refToDocUsedInCtor, not(instanceOf(LazyLoadingProxy.class)));
2952+
assertThat(loaded.refToDocUsedInCtor, instanceOf(LazyLoadingProxy.class));
29532953
assertThat(loaded.refToDocNotUsedInCtor, nullValue());
29542954
}
29552955

@@ -2972,8 +2972,8 @@ public void shouldNotReuseLazyLoadedDBRefWhenTypeUsedInPersistenceConstrcutorBut
29722972
assertThat(loaded.refToDocUsedInCtor, nullValue());
29732973
}
29742974

2975-
@Test // DATAMONGO-1287
2976-
public void shouldRespectParamterValueWhenAttemptingToReuseLazyLoadedDBRefUsedInPersistenceConstrcutor() {
2975+
@Test // DATAMONGO-1287, DATAMONGO-2004
2976+
public void shouldRespectParameterValueWhenAttemptingToReuseLazyLoadedDBRefUsedInPersistenceConstructor() {
29772977

29782978
Document docInCtor = new Document();
29792979
docInCtor.id = "doc-in-ctor";
@@ -2991,7 +2991,7 @@ public void shouldRespectParamterValueWhenAttemptingToReuseLazyLoadedDBRefUsedIn
29912991

29922992
DocumentWithLazyDBrefUsedInPresistenceConstructor loaded = template.findOne(query(where("id").is(source.id)),
29932993
DocumentWithLazyDBrefUsedInPresistenceConstructor.class);
2994-
assertThat(loaded.refToDocUsedInCtor, not(instanceOf(LazyLoadingProxy.class)));
2994+
assertThat(loaded.refToDocUsedInCtor, instanceOf(LazyLoadingProxy.class));
29952995
assertThat(loaded.refToDocNotUsedInCtor, instanceOf(LazyLoadingProxy.class));
29962996
}
29972997

@@ -3250,6 +3250,73 @@ public void shouldFetchMapOfLazyReferencesCorrectly() {
32503250
assertThat(target.lazyDbRefAnnotatedMap.values(), contains(two, one));
32513251
}
32523252

3253+
@Test // DATAMONGO-2004
3254+
public void shouldFetchLazyReferenceWithConstructorCreationCorrectly() {
3255+
3256+
Sample one = new Sample("1", "jon snow");
3257+
3258+
template.save(one);
3259+
3260+
DocumentWithLazyDBRefsAndConstructorCreation source = new DocumentWithLazyDBRefsAndConstructorCreation(null, one,
3261+
null, null);
3262+
3263+
template.save(source);
3264+
3265+
DocumentWithLazyDBRefsAndConstructorCreation target = template.findOne(query(where("id").is(source.id)),
3266+
DocumentWithLazyDBRefsAndConstructorCreation.class);
3267+
3268+
assertThat(target.lazyDbRefProperty, instanceOf(LazyLoadingProxy.class));
3269+
assertThat(target.lazyDbRefProperty, is(one));
3270+
}
3271+
3272+
@Test // DATAMONGO-2004
3273+
public void shouldFetchMapOfLazyReferencesWithConstructorCreationCorrectly() {
3274+
3275+
Sample one = new Sample("1", "jon snow");
3276+
Sample two = new Sample("2", "tyrion lannister");
3277+
3278+
template.save(one);
3279+
template.save(two);
3280+
3281+
Map<String, Sample> map = new LinkedHashMap<>();
3282+
map.put("tyrion", two);
3283+
map.put("jon", one);
3284+
3285+
DocumentWithLazyDBRefsAndConstructorCreation source = new DocumentWithLazyDBRefsAndConstructorCreation(null, null,
3286+
null, map);
3287+
3288+
template.save(source);
3289+
3290+
DocumentWithLazyDBRefsAndConstructorCreation target = template.findOne(query(where("id").is(source.id)),
3291+
DocumentWithLazyDBRefsAndConstructorCreation.class);
3292+
3293+
assertThat(target.lazyDbRefAnnotatedMap, instanceOf(LazyLoadingProxy.class));
3294+
assertThat(target.lazyDbRefAnnotatedMap.values(), contains(two, one));
3295+
}
3296+
3297+
@Test // DATAMONGO-2004
3298+
public void shouldFetchListOfLazyReferencesWithConstructorCreationCorrectly() {
3299+
3300+
Sample one = new Sample("1", "jon snow");
3301+
Sample two = new Sample("2", "tyrion lannister");
3302+
3303+
template.save(one);
3304+
template.save(two);
3305+
3306+
List<Sample> list = Arrays.asList(two, one);
3307+
3308+
DocumentWithLazyDBRefsAndConstructorCreation source = new DocumentWithLazyDBRefsAndConstructorCreation(null, null,
3309+
list, null);
3310+
3311+
template.save(source);
3312+
3313+
DocumentWithLazyDBRefsAndConstructorCreation target = template.findOne(query(where("id").is(source.id)),
3314+
DocumentWithLazyDBRefsAndConstructorCreation.class);
3315+
3316+
assertThat(target.lazyDbRefAnnotatedList, instanceOf(LazyLoadingProxy.class));
3317+
assertThat(target.lazyDbRefAnnotatedList, contains(two, one));
3318+
}
3319+
32533320
@Test // DATAMONGO-1513
32543321
@DirtiesContext
32553322
public void populatesIdsAddedByEventListener() {
@@ -3457,6 +3524,29 @@ static class DocumentWithDBRefCollection {
34573524
@org.springframework.data.mongodb.core.mapping.DBRef(lazy = true) public Map<String, Sample> lazyDbRefAnnotatedMap;
34583525
}
34593526

3527+
@Data
3528+
static class DocumentWithLazyDBRefsAndConstructorCreation {
3529+
3530+
@Id public final String id;
3531+
3532+
public DocumentWithLazyDBRefsAndConstructorCreation(String id, Sample lazyDbRefProperty,
3533+
List<Sample> lazyDbRefAnnotatedList, Map<String, Sample> lazyDbRefAnnotatedMap) {
3534+
this.id = id;
3535+
this.lazyDbRefProperty = lazyDbRefProperty;
3536+
this.lazyDbRefAnnotatedList = lazyDbRefAnnotatedList;
3537+
this.lazyDbRefAnnotatedMap = lazyDbRefAnnotatedMap;
3538+
}
3539+
3540+
@org.springframework.data.mongodb.core.mapping.DBRef(lazy = true) //
3541+
public final Sample lazyDbRefProperty;
3542+
3543+
@Field("lazy_db_ref_list") @org.springframework.data.mongodb.core.mapping.DBRef(lazy = true) //
3544+
public final List<Sample> lazyDbRefAnnotatedList;
3545+
3546+
@Field("lazy_db_ref_map") @org.springframework.data.mongodb.core.mapping.DBRef(
3547+
lazy = true) public final Map<String, Sample> lazyDbRefAnnotatedMap;
3548+
}
3549+
34603550
@EqualsAndHashCode
34613551
static class DocumentWithCollection {
34623552

0 commit comments

Comments
 (0)