Skip to content

Commit a2127a4

Browse files
christophstroblmp911de
authored andcommitted
Allow reading already resolved references.
This commit adds the ability to read (eg. by an aggregation $lookup) already fully resolved references between documents. No proxy will be created for lazy loading references and we'll also skip the additional server roundtrip to load the reference by its id. Closes #4312 Original pull request: #4323
1 parent 67215f1 commit a2127a4

File tree

2 files changed

+143
-2
lines changed

2 files changed

+143
-2
lines changed

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

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -649,9 +649,32 @@ private void readAssociation(Association<MongoPersistentProperty> association, P
649649
return;
650650
}
651651

652-
DBRef dbref = value instanceof DBRef ? (DBRef) value : null;
652+
if (value instanceof DBRef dbref) {
653+
accessor.setProperty(property, dbRefResolver.resolveDbRef(property, dbref, callback, handler));
654+
return;
655+
}
653656

654-
accessor.setProperty(property, dbRefResolver.resolveDbRef(property, dbref, callback, handler));
657+
/*
658+
* The value might be a pre resolved full document (eg. resulting from an aggregation $lookup).
659+
* In this case we try to map that object to the target type without an additional step ($dbref resolution server roundtrip)
660+
* in between.
661+
*/
662+
if (value instanceof Document document) {
663+
if(property.isMap()) {
664+
if(document.isEmpty() || document.values().iterator().next() instanceof DBRef) {
665+
accessor.setProperty(property, dbRefResolver.resolveDbRef(property, null, callback, handler));
666+
} else {
667+
accessor.setProperty(property, readMap(context, document, property.getTypeInformation()));
668+
}
669+
} else {
670+
accessor.setProperty(property, read(property.getActualType(), document));
671+
}
672+
} else if (value instanceof Collection<?> collection && collection.size() > 0
673+
&& collection.iterator().next() instanceof Document) {
674+
accessor.setProperty(property, readCollectionOrArray(context, collection, property.getTypeInformation()));
675+
} else {
676+
accessor.setProperty(property, dbRefResolver.resolveDbRef(property, null, callback, handler));
677+
}
655678
}
656679

657680
@Nullable

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

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import java.util.Collections;
3434
import java.util.HashSet;
3535
import java.util.List;
36+
import java.util.Map;
3637

3738
import org.bson.Document;
3839
import org.junit.jupiter.api.BeforeEach;
@@ -110,6 +111,98 @@ void resolvesLazyDBRefOnAccess() {
110111
verify(dbRefResolver).bulkFetch(any());
111112
}
112113

114+
@Test // GH-4312
115+
void conversionShouldAllowReadingAlreadyResolvedReferences() {
116+
117+
Document sampleSource = new Document("_id", "sample-1").append("value", "one");
118+
Document source = new Document("_id", "id-1").append("sample", sampleSource);
119+
120+
WithSingleValueDbRef read = converter.read(WithSingleValueDbRef.class, source);
121+
122+
assertThat(read.sample).isEqualTo(converter.read(Sample.class, sampleSource));
123+
verifyNoInteractions(dbRefResolver);
124+
}
125+
126+
@Test // GH-4312
127+
void conversionShouldAllowReadingAlreadyResolvedListOfReferences() {
128+
129+
Document sample1Source = new Document("_id", "sample-1").append("value", "one");
130+
Document sample2Source = new Document("_id", "sample-2").append("value", "two");
131+
Document source = new Document("_id", "id-1").append("lazyList", List.of(sample1Source, sample2Source));
132+
133+
WithLazyDBRef read = converter.read(WithLazyDBRef.class, source);
134+
135+
assertThat(read.lazyList).containsExactly(converter.read(Sample.class, sample1Source),
136+
converter.read(Sample.class, sample2Source));
137+
verifyNoInteractions(dbRefResolver);
138+
}
139+
140+
@Test // GH-4312
141+
void conversionShouldAllowReadingAlreadyResolvedMapOfReferences() {
142+
143+
Document sample1Source = new Document("_id", "sample-1").append("value", "one");
144+
Document sample2Source = new Document("_id", "sample-2").append("value", "two");
145+
Document source = new Document("_id", "id-1").append("sampleMap",
146+
new Document("s1", sample1Source).append("s2", sample2Source));
147+
148+
WithMapValueDbRef read = converter.read(WithMapValueDbRef.class, source);
149+
150+
assertThat(read.sampleMap) //
151+
.containsEntry("s1", converter.read(Sample.class, sample1Source)) //
152+
.containsEntry("s2", converter.read(Sample.class, sample2Source));
153+
verifyNoInteractions(dbRefResolver);
154+
}
155+
156+
@Test // GH-4312
157+
void conversionShouldAllowReadingAlreadyResolvedMapOfLazyReferences() {
158+
159+
Document sample1Source = new Document("_id", "sample-1").append("value", "one");
160+
Document sample2Source = new Document("_id", "sample-2").append("value", "two");
161+
Document source = new Document("_id", "id-1").append("sampleMapLazy",
162+
new Document("s1", sample1Source).append("s2", sample2Source));
163+
164+
WithMapValueDbRef read = converter.read(WithMapValueDbRef.class, source);
165+
166+
assertThat(read.sampleMapLazy) //
167+
.containsEntry("s1", converter.read(Sample.class, sample1Source)) //
168+
.containsEntry("s2", converter.read(Sample.class, sample2Source));
169+
verifyNoInteractions(dbRefResolver);
170+
}
171+
172+
@Test // GH-4312
173+
void resolvesLazyDBRefMapOnAccess() {
174+
175+
client.getDatabase(DATABASE).getCollection("samples")
176+
.insertMany(Arrays.asList(new Document("_id", "sample-1").append("value", "one"),
177+
new Document("_id", "sample-2").append("value", "two")));
178+
179+
Document source = new Document("_id", "id-1").append("sampleMapLazy",
180+
new Document("s1", new com.mongodb.DBRef("samples", "sample-1")).append("s2",
181+
new com.mongodb.DBRef("samples", "sample-2")));
182+
183+
WithMapValueDbRef target = converter.read(WithMapValueDbRef.class, source);
184+
185+
verify(dbRefResolver).resolveDbRef(any(), isNull(), any(), any());
186+
187+
assertThat(target.sampleMapLazy).isInstanceOf(LazyLoadingProxy.class);
188+
assertThat(target.getSampleMapLazy()).containsEntry("s1", new Sample("sample-1", "one")).containsEntry("s2",
189+
new Sample("sample-2", "two"));
190+
191+
verify(dbRefResolver).bulkFetch(any());
192+
}
193+
194+
@Test // GH-4312
195+
void conversionShouldAllowReadingAlreadyResolvedLazyReferences() {
196+
197+
Document sampleSource = new Document("_id", "sample-1").append("value", "one");
198+
Document source = new Document("_id", "id-1").append("sampleLazy", sampleSource);
199+
200+
WithSingleValueDbRef read = converter.read(WithSingleValueDbRef.class, source);
201+
202+
assertThat(read.sampleLazy).isEqualTo(converter.read(Sample.class, sampleSource));
203+
verifyNoInteractions(dbRefResolver);
204+
}
205+
113206
@Test // DATAMONGO-2004
114207
void resolvesLazyDBRefConstructorArgOnAccess() {
115208

@@ -164,6 +257,31 @@ List<Sample> getLazyList() {
164257
}
165258
}
166259

260+
@Data
261+
public static class WithSingleValueDbRef {
262+
263+
@Id //
264+
String id;
265+
266+
@DBRef //
267+
Sample sample;
268+
269+
@DBRef(lazy = true) //
270+
Sample sampleLazy;
271+
}
272+
273+
@Data
274+
public static class WithMapValueDbRef {
275+
276+
@Id String id;
277+
278+
@DBRef //
279+
Map<String, Sample> sampleMap;
280+
281+
@DBRef(lazy = true) //
282+
Map<String, Sample> sampleMapLazy;
283+
}
284+
167285
public static class WithLazyDBRefAsConstructorArg {
168286

169287
@Id String id;

0 commit comments

Comments
 (0)