diff --git a/pom.xml b/pom.xml
index d864b2e4e6..57743d548f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.springframework.data
spring-data-mongodb-parent
- 4.1.0-SNAPSHOT
+ 4.1.x-GH-4312-SNAPSHOT
pom
Spring Data MongoDB
diff --git a/spring-data-mongodb-benchmarks/pom.xml b/spring-data-mongodb-benchmarks/pom.xml
index 1b2a1390e6..92928ff7ba 100644
--- a/spring-data-mongodb-benchmarks/pom.xml
+++ b/spring-data-mongodb-benchmarks/pom.xml
@@ -7,7 +7,7 @@
org.springframework.data
spring-data-mongodb-parent
- 4.1.0-SNAPSHOT
+ 4.1.x-GH-4312-SNAPSHOT
../pom.xml
diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml
index 8db8d798fb..14d6fdacb3 100644
--- a/spring-data-mongodb-distribution/pom.xml
+++ b/spring-data-mongodb-distribution/pom.xml
@@ -15,7 +15,7 @@
org.springframework.data
spring-data-mongodb-parent
- 4.1.0-SNAPSHOT
+ 4.1.x-GH-4312-SNAPSHOT
../pom.xml
diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml
index 9a57f7eb52..bfae9dca63 100644
--- a/spring-data-mongodb/pom.xml
+++ b/spring-data-mongodb/pom.xml
@@ -13,7 +13,7 @@
org.springframework.data
spring-data-mongodb-parent
- 4.1.0-SNAPSHOT
+ 4.1.x-GH-4312-SNAPSHOT
../pom.xml
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java
index 43d7d39a82..e57f3d19d7 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java
@@ -668,9 +668,32 @@ private void readAssociation(Association association, P
return;
}
- DBRef dbref = value instanceof DBRef ? (DBRef) value : null;
+ if (value instanceof DBRef dbref) {
+ accessor.setProperty(property, dbRefResolver.resolveDbRef(property, dbref, callback, handler));
+ return;
+ }
- accessor.setProperty(property, dbRefResolver.resolveDbRef(property, dbref, callback, handler));
+ /*
+ * The value might be a pre resolved full document (eg. resulting from an aggregation $lookup).
+ * In this case we try to map that object to the target type without an additional step ($dbref resolution server roundtrip)
+ * in between.
+ */
+ if (value instanceof Document document) {
+ if(property.isMap()) {
+ if(document.isEmpty() || document.values().iterator().next() instanceof DBRef) {
+ accessor.setProperty(property, dbRefResolver.resolveDbRef(property, null, callback, handler));
+ } else {
+ accessor.setProperty(property, readMap(context, document, property.getTypeInformation()));
+ }
+ } else {
+ accessor.setProperty(property, read(property.getActualType(), document));
+ }
+ } else if (value instanceof Collection> collection && collection.size() > 0
+ && collection.iterator().next() instanceof Document) {
+ accessor.setProperty(property, readCollectionOrArray(context, collection, property.getTypeInformation()));
+ } else {
+ accessor.setProperty(property, dbRefResolver.resolveDbRef(property, null, callback, handler));
+ }
}
@Nullable
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterTests.java
index 03492f169e..a6f26565a5 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterTests.java
@@ -33,6 +33,7 @@
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import org.bson.Document;
import org.junit.jupiter.api.BeforeEach;
@@ -110,6 +111,98 @@ void resolvesLazyDBRefOnAccess() {
verify(dbRefResolver).bulkFetch(any());
}
+ @Test // GH-4312
+ void conversionShouldAllowReadingAlreadyResolvedReferences() {
+
+ Document sampleSource = new Document("_id", "sample-1").append("value", "one");
+ Document source = new Document("_id", "id-1").append("sample", sampleSource);
+
+ WithSingleValueDbRef read = converter.read(WithSingleValueDbRef.class, source);
+
+ assertThat(read.sample).isEqualTo(converter.read(Sample.class, sampleSource));
+ verifyNoInteractions(dbRefResolver);
+ }
+
+ @Test // GH-4312
+ void conversionShouldAllowReadingAlreadyResolvedListOfReferences() {
+
+ Document sample1Source = new Document("_id", "sample-1").append("value", "one");
+ Document sample2Source = new Document("_id", "sample-2").append("value", "two");
+ Document source = new Document("_id", "id-1").append("lazyList", List.of(sample1Source, sample2Source));
+
+ WithLazyDBRef read = converter.read(WithLazyDBRef.class, source);
+
+ assertThat(read.lazyList).containsExactly(converter.read(Sample.class, sample1Source),
+ converter.read(Sample.class, sample2Source));
+ verifyNoInteractions(dbRefResolver);
+ }
+
+ @Test // GH-4312
+ void conversionShouldAllowReadingAlreadyResolvedMapOfReferences() {
+
+ Document sample1Source = new Document("_id", "sample-1").append("value", "one");
+ Document sample2Source = new Document("_id", "sample-2").append("value", "two");
+ Document source = new Document("_id", "id-1").append("sampleMap",
+ new Document("s1", sample1Source).append("s2", sample2Source));
+
+ WithMapValueDbRef read = converter.read(WithMapValueDbRef.class, source);
+
+ assertThat(read.sampleMap) //
+ .containsEntry("s1", converter.read(Sample.class, sample1Source)) //
+ .containsEntry("s2", converter.read(Sample.class, sample2Source));
+ verifyNoInteractions(dbRefResolver);
+ }
+
+ @Test // GH-4312
+ void conversionShouldAllowReadingAlreadyResolvedMapOfLazyReferences() {
+
+ Document sample1Source = new Document("_id", "sample-1").append("value", "one");
+ Document sample2Source = new Document("_id", "sample-2").append("value", "two");
+ Document source = new Document("_id", "id-1").append("sampleMapLazy",
+ new Document("s1", sample1Source).append("s2", sample2Source));
+
+ WithMapValueDbRef read = converter.read(WithMapValueDbRef.class, source);
+
+ assertThat(read.sampleMapLazy) //
+ .containsEntry("s1", converter.read(Sample.class, sample1Source)) //
+ .containsEntry("s2", converter.read(Sample.class, sample2Source));
+ verifyNoInteractions(dbRefResolver);
+ }
+
+ @Test // GH-4312
+ void resolvesLazyDBRefMapOnAccess() {
+
+ client.getDatabase(DATABASE).getCollection("samples")
+ .insertMany(Arrays.asList(new Document("_id", "sample-1").append("value", "one"),
+ new Document("_id", "sample-2").append("value", "two")));
+
+ Document source = new Document("_id", "id-1").append("sampleMapLazy",
+ new Document("s1", new com.mongodb.DBRef("samples", "sample-1")).append("s2",
+ new com.mongodb.DBRef("samples", "sample-2")));
+
+ WithMapValueDbRef target = converter.read(WithMapValueDbRef.class, source);
+
+ verify(dbRefResolver).resolveDbRef(any(), isNull(), any(), any());
+
+ assertThat(target.sampleMapLazy).isInstanceOf(LazyLoadingProxy.class);
+ assertThat(target.getSampleMapLazy()).containsEntry("s1", new Sample("sample-1", "one")).containsEntry("s2",
+ new Sample("sample-2", "two"));
+
+ verify(dbRefResolver).bulkFetch(any());
+ }
+
+ @Test // GH-4312
+ void conversionShouldAllowReadingAlreadyResolvedLazyReferences() {
+
+ Document sampleSource = new Document("_id", "sample-1").append("value", "one");
+ Document source = new Document("_id", "id-1").append("sampleLazy", sampleSource);
+
+ WithSingleValueDbRef read = converter.read(WithSingleValueDbRef.class, source);
+
+ assertThat(read.sampleLazy).isEqualTo(converter.read(Sample.class, sampleSource));
+ verifyNoInteractions(dbRefResolver);
+ }
+
@Test // DATAMONGO-2004
void resolvesLazyDBRefConstructorArgOnAccess() {
@@ -164,6 +257,31 @@ List getLazyList() {
}
}
+ @Data
+ public static class WithSingleValueDbRef {
+
+ @Id //
+ String id;
+
+ @DBRef //
+ Sample sample;
+
+ @DBRef(lazy = true) //
+ Sample sampleLazy;
+ }
+
+ @Data
+ public static class WithMapValueDbRef {
+
+ @Id String id;
+
+ @DBRef //
+ Map sampleMap;
+
+ @DBRef(lazy = true) //
+ Map sampleMapLazy;
+ }
+
public static class WithLazyDBRefAsConstructorArg {
@Id String id;