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;