Skip to content

Commit ed1f2c7

Browse files
DATAMONGO-2004 - Polishing.
Make sure to place the LazyLoadingProxy early in the mapping process to avoid eager fetching of documents that might then get replaced by the LazyLoadingProxy. Original Pull Request: #571
1 parent c545c85 commit ed1f2c7

File tree

5 files changed

+173
-24
lines changed

5 files changed

+173
-24
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public DocumentAccessor(Bson document) {
6262
* @return the underlying {@link Bson document}.
6363
* @since 2.1
6464
*/
65-
public Bson getDocument() {
65+
Bson getDocument() {
6666
return this.document;
6767
}
6868

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

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -660,7 +660,8 @@ protected Bson createMap(Map<Object, Object> map, MongoPersistentProperty proper
660660
* @param sink the {@link Collection} to write to.
661661
* @return
662662
*/
663-
private List<Object> writeCollectionInternal(Collection<?> source, @Nullable TypeInformation<?> type, Collection<?> sink) {
663+
private List<Object> writeCollectionInternal(Collection<?> source, @Nullable TypeInformation<?> type,
664+
Collection<?> sink) {
664665

665666
TypeInformation<?> componentType = null;
666667

@@ -868,7 +869,7 @@ private Object getPotentiallyConvertedSimpleWrite(@Nullable Object value) {
868869
*/
869870
@Nullable
870871
@SuppressWarnings({ "rawtypes", "unchecked" })
871-
private Object getPotentiallyConvertedSimpleRead(@Nullable Object value, @Nullable Class<?> target) {
872+
private Object getPotentiallyConvertedSimpleRead(@Nullable Object value, @Nullable Class<?> target) {
872873

873874
if (value == null || target == null || ClassUtils.isAssignableValue(target, value)) {
874875
return value;
@@ -1271,6 +1272,8 @@ private Object removeTypeInfo(Object object, boolean recursively) {
12711272
* of the configured source {@link Document}.
12721273
*
12731274
* @author Oliver Gierke
1275+
* @author Mark Paluch
1276+
* @author Christoph Strobl
12741277
*/
12751278
class MongoDbPropertyValueProvider implements PropertyValueProvider<MongoPersistentProperty> {
12761279

@@ -1286,15 +1289,8 @@ class MongoDbPropertyValueProvider implements PropertyValueProvider<MongoPersist
12861289
* @param evaluator must not be {@literal null}.
12871290
* @param path must not be {@literal null}.
12881291
*/
1289-
public MongoDbPropertyValueProvider(Bson source, SpELExpressionEvaluator evaluator, ObjectPath path) {
1290-
1291-
Assert.notNull(source, "Source document must no be null!");
1292-
Assert.notNull(evaluator, "SpELExpressionEvaluator must not be null!");
1293-
Assert.notNull(path, "ObjectPath must not be null!");
1294-
1295-
this.source = new DocumentAccessor(source);
1296-
this.evaluator = evaluator;
1297-
this.path = path;
1292+
MongoDbPropertyValueProvider(Bson source, SpELExpressionEvaluator evaluator, ObjectPath path) {
1293+
this(new DocumentAccessor(source), evaluator, path);
12981294
}
12991295

13001296
/**
@@ -1305,7 +1301,7 @@ public MongoDbPropertyValueProvider(Bson source, SpELExpressionEvaluator evaluat
13051301
* @param evaluator must not be {@literal null}.
13061302
* @param path must not be {@literal null}.
13071303
*/
1308-
public MongoDbPropertyValueProvider(DocumentAccessor accessor, SpELExpressionEvaluator evaluator, ObjectPath path) {
1304+
MongoDbPropertyValueProvider(DocumentAccessor accessor, SpELExpressionEvaluator evaluator, ObjectPath path) {
13091305

13101306
Assert.notNull(accessor, "DocumentAccessor must no be null!");
13111307
Assert.notNull(evaluator, "SpELExpressionEvaluator must not be null!");
@@ -1340,6 +1336,7 @@ public <T> T getPropertyValue(MongoPersistentProperty property) {
13401336
* resolution to {@link DbRefResolver}.
13411337
*
13421338
* @author Mark Paluch
1339+
* @author Christoph Strobl
13431340
* @since 2.1
13441341
*/
13451342
class AssociationAwareMongoDbPropertyValueProvider extends MongoDbPropertyValueProvider {
@@ -1352,8 +1349,7 @@ class AssociationAwareMongoDbPropertyValueProvider extends MongoDbPropertyValueP
13521349
* @param evaluator must not be {@literal null}.
13531350
* @param path must not be {@literal null}.
13541351
*/
1355-
public AssociationAwareMongoDbPropertyValueProvider(Bson source, SpELExpressionEvaluator evaluator,
1356-
ObjectPath path) {
1352+
AssociationAwareMongoDbPropertyValueProvider(Bson source, SpELExpressionEvaluator evaluator, ObjectPath path) {
13571353
super(source, evaluator, path);
13581354
}
13591355

@@ -1365,17 +1361,21 @@ public AssociationAwareMongoDbPropertyValueProvider(Bson source, SpELExpressionE
13651361
@SuppressWarnings("unchecked")
13661362
public <T> T getPropertyValue(MongoPersistentProperty property) {
13671363

1368-
T value = super.getPropertyValue(property);
1364+
if (property.isDbReference() && property.getDBRef().lazy()) {
13691365

1370-
if (value == null || !property.isAssociation()) {
1371-
return value;
1372-
}
1366+
Object rawRefValue = source.get(property);
1367+
if (rawRefValue == null) {
1368+
return null;
1369+
}
1370+
1371+
DbRefResolverCallback callback = new DefaultDbRefResolverCallback(source.getDocument(), path, evaluator,
1372+
MappingMongoConverter.this);
13731373

1374-
DbRefResolverCallback callback = new DefaultDbRefResolverCallback(source.getDocument(), path, evaluator,
1375-
MappingMongoConverter.this);
1376-
DBRef dbref = value instanceof DBRef ? (DBRef) value : null;
1374+
DBRef dbref = rawRefValue instanceof DBRef ? (DBRef) rawRefValue : null;
1375+
return (T) dbRefResolver.resolveDbRef(property, dbref, callback, dbRefProxyHandler);
1376+
}
13771377

1378-
return (T) dbRefResolver.resolveDbRef(property, dbref, callback, dbRefProxyHandler);
1378+
return super.getPropertyValue(property);
13791379
}
13801380
}
13811381

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3448,7 +3448,7 @@ public void shouldFetchListOfLazyReferencesWithConstructorCreationCorrectly() {
34483448
DocumentWithLazyDBRefsAndConstructorCreation.class);
34493449

34503450
assertThat(target.lazyDbRefAnnotatedList, instanceOf(LazyLoadingProxy.class));
3451-
assertThat(target.lazyDbRefAnnotatedList, contains(two, one));
3451+
assertThat(target.getLazyDbRefAnnotatedList(), contains(two, one));
34523452
}
34533453

34543454
@Test // DATAMONGO-1513
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/*
2+
* Copyright 2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.mongodb.core.convert;
17+
18+
import static org.assertj.core.api.Assertions.*;
19+
import static org.mockito.ArgumentMatchers.isNull;
20+
import static org.mockito.Mockito.*;
21+
import static org.mockito.Mockito.any;
22+
23+
import lombok.AllArgsConstructor;
24+
import lombok.Data;
25+
import lombok.NoArgsConstructor;
26+
27+
import java.util.Arrays;
28+
import java.util.List;
29+
30+
import org.bson.Document;
31+
import org.junit.Before;
32+
import org.junit.Test;
33+
import org.springframework.data.annotation.Id;
34+
import org.springframework.data.mongodb.MongoDbFactory;
35+
import org.springframework.data.mongodb.core.SimpleMongoDbFactory;
36+
import org.springframework.data.mongodb.core.mapping.DBRef;
37+
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
38+
39+
import com.mongodb.MongoClient;
40+
41+
/**
42+
* Integration tests for {@link MappingMongoConverter}.
43+
*
44+
* @author Christoph Strobl
45+
*/
46+
public class MappingMongoConverterTests {
47+
48+
MongoClient client;
49+
50+
MappingMongoConverter converter;
51+
MongoMappingContext mappingContext;
52+
DbRefResolver dbRefResolver;
53+
54+
@Before
55+
public void setUp() {
56+
57+
client = new MongoClient();
58+
client.dropDatabase("mapping-converter-tests");
59+
60+
MongoDbFactory factory = new SimpleMongoDbFactory(client, "mapping-converter-tests");
61+
62+
dbRefResolver = spy(new DefaultDbRefResolver(factory));
63+
mappingContext = new MongoMappingContext();
64+
mappingContext.afterPropertiesSet();
65+
66+
converter = new MappingMongoConverter(dbRefResolver, mappingContext);
67+
}
68+
69+
@Test // DATAMONGO-2004
70+
public void resolvesLazyDBRefOnAccess() {
71+
72+
client.getDatabase("mapping-converter-tests").getCollection("samples")
73+
.insertMany(Arrays.asList(new Document("_id", "sample-1").append("value", "one"),
74+
new Document("_id", "sample-2").append("value", "two")));
75+
76+
Document source = new Document("_id", "id-1").append("lazyList",
77+
Arrays.asList(new com.mongodb.DBRef("samples", "sample-1"), new com.mongodb.DBRef("samples", "sample-2")));
78+
79+
WithLazyDBRef target = converter.read(WithLazyDBRef.class, source);
80+
81+
verify(dbRefResolver).resolveDbRef(any(), isNull(), any(), any());
82+
verifyNoMoreInteractions(dbRefResolver);
83+
84+
assertThat(target.lazyList).isInstanceOf(LazyLoadingProxy.class);
85+
assertThat(target.getLazyList()).contains(new Sample("sample-1", "one"), new Sample("sample-2", "two"));
86+
87+
verify(dbRefResolver).bulkFetch(any());
88+
}
89+
90+
@Test // DATAMONGO-2004
91+
public void resolvesLazyDBRefConstructorArgOnAccess() {
92+
93+
client.getDatabase("mapping-converter-tests").getCollection("samples")
94+
.insertMany(Arrays.asList(new Document("_id", "sample-1").append("value", "one"),
95+
new Document("_id", "sample-2").append("value", "two")));
96+
97+
Document source = new Document("_id", "id-1").append("lazyList",
98+
Arrays.asList(new com.mongodb.DBRef("samples", "sample-1"), new com.mongodb.DBRef("samples", "sample-2")));
99+
100+
WithLazyDBRefAsConstructorArg target = converter.read(WithLazyDBRefAsConstructorArg.class, source);
101+
102+
verify(dbRefResolver).resolveDbRef(any(), isNull(), any(), any());
103+
verifyNoMoreInteractions(dbRefResolver);
104+
105+
assertThat(target.lazyList).isInstanceOf(LazyLoadingProxy.class);
106+
assertThat(target.getLazyList()).contains(new Sample("sample-1", "one"), new Sample("sample-2", "two"));
107+
108+
verify(dbRefResolver).bulkFetch(any());
109+
}
110+
111+
public static class WithLazyDBRef {
112+
113+
@Id String id;
114+
@DBRef(lazy = true) List<Sample> lazyList;
115+
116+
public List<Sample> getLazyList() {
117+
return lazyList;
118+
}
119+
}
120+
121+
public static class WithLazyDBRefAsConstructorArg {
122+
123+
@Id String id;
124+
@DBRef(lazy = true) List<Sample> lazyList;
125+
126+
public WithLazyDBRefAsConstructorArg(String id, List<Sample> lazyList) {
127+
128+
this.id = id;
129+
this.lazyList = lazyList;
130+
}
131+
132+
public List<Sample> getLazyList() {
133+
return lazyList;
134+
}
135+
}
136+
137+
@Data
138+
@AllArgsConstructor
139+
@NoArgsConstructor
140+
static class Sample {
141+
142+
@Id String id;
143+
String value;
144+
}
145+
}

src/main/asciidoc/reference/mapping.adoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -584,9 +584,13 @@ public class Person {
584584
====
585585

586586
You need not use `@OneToMany` or similar mechanisms because the List of objects tells the mapping framework that you want a one-to-many relationship. When the object is stored in MongoDB, there is a list of DBRefs rather than the `Account` objects themselves.
587+
When it comes to loading collections of ``DBRef``s it is advisable to restrict references held in collection types to a specific MongoDB collection. This allows bulk loading of all references, whereas references pointing to different MongoDB collections need to be resolved one by one.
587588

588589
IMPORTANT: The mapping framework does not handle cascading saves. If you change an `Account` object that is referenced by a `Person` object, you must save the `Account` object separately. Calling `save` on the `Person` object does not automatically save the `Account` objects in the `accounts` property.
589590

591+
``DBRef``s can also be resolved lazily. In this case the actual `Object` or `Collection` of references is resolved on first access of the property. Use the `lazy` attribute of `@DBRef` to specify this.
592+
Required properties that are also defined as lazy loading ``DBRef`` and used as constructor arguments are also decorated with the lazy loading proxy making sure to put as little pressure on the database and network as possible.
593+
590594
[[mapping-usage-events]]
591595
=== Mapping Framework Events
592596

0 commit comments

Comments
 (0)