Skip to content

Commit a95f772

Browse files
committed
DATAMONGO-1765 - DefaultDbRefResolver now maps duplicate references correctly.
On bulk resolution of a DBRef array we now map the resulting documents back to their ids to make sure that reoccurring identifiers are mapped to the corresponding documents.
1 parent d956c8c commit a95f772

File tree

2 files changed

+48
-44
lines changed

2 files changed

+48
-44
lines changed

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

Lines changed: 26 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@
2323
import java.io.Serializable;
2424
import java.lang.reflect.Method;
2525
import java.util.ArrayList;
26+
import java.util.Collection;
2627
import java.util.Collections;
27-
import java.util.Comparator;
2828
import java.util.List;
29+
import java.util.stream.Collectors;
30+
import java.util.stream.Stream;
2931

3032
import org.aopalliance.intercept.MethodInterceptor;
3133
import org.aopalliance.intercept.MethodInvocation;
@@ -142,8 +144,8 @@ public List<Document> bulkFetch(List<DBRef> refs) {
142144
}
143145

144146
String collection = refs.iterator().next().getCollectionName();
147+
List<Object> ids = new ArrayList<>(refs.size());
145148

146-
List<Object> ids = new ArrayList<Object>(refs.size());
147149
for (DBRef ref : refs) {
148150

149151
if (!collection.equals(ref.getCollectionName())) {
@@ -155,10 +157,14 @@ public List<Document> bulkFetch(List<DBRef> refs) {
155157
}
156158

157159
MongoDatabase db = mongoDbFactory.getDb();
158-
List<Document> result = new ArrayList<>();
159-
db.getCollection(collection).find(new Document("_id", new Document("$in", ids))).into(result);
160-
result.sort(new DbRefByReferencePositionComparator(ids));
161-
return result;
160+
161+
List<Document> result = db.getCollection(collection) //
162+
.find(new Document("_id", new Document("$in", ids))) //
163+
.into(new ArrayList<>());
164+
165+
return ids.stream() //
166+
.flatMap(id -> documentWithId(id, result)) //
167+
.collect(Collectors.toList());
162168
}
163169

164170
/**
@@ -223,6 +229,20 @@ private boolean isLazyDbRef(MongoPersistentProperty property) {
223229
return property.getDBRef() != null && property.getDBRef().lazy();
224230
}
225231

232+
/**
233+
* Returns document with the given identifier from the given list of {@link Document}s.
234+
*
235+
* @param identifier
236+
* @param documents
237+
* @return
238+
*/
239+
private static Stream<Document> documentWithId(Object identifier, Collection<Document> documents) {
240+
241+
return documents.stream() //
242+
.filter(it -> it.get("_id").equals(identifier)) //
243+
.limit(1);
244+
}
245+
226246
/**
227247
* A {@link MethodInterceptor} that is used within a lazy loading proxy. The property resolving is delegated to a
228248
* {@link DbRefResolverCallback}. The resolving process is triggered by a method invocation on the proxy and is
@@ -449,37 +469,4 @@ private synchronized Object resolve() {
449469
return result;
450470
}
451471
}
452-
453-
/**
454-
* {@link Comparator} for sorting {@link Document} that have been loaded in random order by a predefined list of
455-
* reference identifiers.
456-
*
457-
* @author Christoph Strobl
458-
* @author Oliver Gierke
459-
* @since 1.10
460-
*/
461-
private static class DbRefByReferencePositionComparator implements Comparator<Document> {
462-
463-
private final List<Object> reference;
464-
465-
/**
466-
* Creates a new {@link DbRefByReferencePositionComparator} for the given list of reference identifiers.
467-
*
468-
* @param referenceIds must not be {@literal null}.
469-
*/
470-
public DbRefByReferencePositionComparator(List<Object> referenceIds) {
471-
472-
Assert.notNull(referenceIds, "Reference identifiers must not be null!");
473-
this.reference = new ArrayList<Object>(referenceIds);
474-
}
475-
476-
/*
477-
* (non-Javadoc)
478-
* @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
479-
*/
480-
@Override
481-
public int compare(Document o1, Document o2) {
482-
return Integer.compare(reference.indexOf(o1.get("_id")), reference.indexOf(o2.get("_id")));
483-
}
484-
}
485472
}

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

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,18 @@
1515
*/
1616
package org.springframework.data.mongodb.core.convert;
1717

18+
import static org.assertj.core.api.Assertions.assertThat;
1819
import static org.hamcrest.Matchers.*;
1920
import static org.hamcrest.Matchers.contains;
20-
import static org.junit.Assert.*;
21+
import static org.junit.Assert.assertThat;
22+
import static org.mockito.ArgumentMatchers.*;
23+
import static org.mockito.ArgumentMatchers.any;
2124
import static org.mockito.Mockito.*;
2225

2326
import java.util.Arrays;
2427
import java.util.Collection;
2528
import java.util.Collections;
2629

27-
import com.mongodb.client.FindIterable;
28-
import com.mongodb.client.MongoCollection;
29-
import com.mongodb.client.MongoDatabase;
3030
import org.bson.Document;
3131
import org.bson.types.ObjectId;
3232
import org.junit.Before;
@@ -43,6 +43,9 @@
4343
import org.springframework.data.mongodb.core.DocumentTestUtils;
4444

4545
import com.mongodb.DBRef;
46+
import com.mongodb.client.FindIterable;
47+
import com.mongodb.client.MongoCollection;
48+
import com.mongodb.client.MongoDatabase;
4649

4750
/**
4851
* Unit tests for {@link DefaultDbRefResolver}.
@@ -100,7 +103,7 @@ public void bulkFetchShouldThrowExceptionWhenUsingDifferntCollectionsWithinSetOf
100103
@Test // DATAMONGO-1194
101104
public void bulkFetchShouldReturnEarlyForEmptyLists() {
102105

103-
resolver.bulkFetch(Collections.<DBRef>emptyList());
106+
resolver.bulkFetch(Collections.<DBRef> emptyList());
104107

105108
verify(collectionMock, never()).find(Mockito.any(Document.class));
106109
}
@@ -115,6 +118,7 @@ public void bulkFetchShouldRestoreOriginalOrder() {
115118
DBRef ref2 = new DBRef("collection-1", o2.get("_id"));
116119

117120
when(cursorMock.into(any())).then(new Answer<Object>() {
121+
118122
@Override
119123
public Object answer(InvocationOnMock invocation) throws Throwable {
120124

@@ -127,4 +131,17 @@ public Object answer(InvocationOnMock invocation) throws Throwable {
127131

128132
assertThat(resolver.bulkFetch(Arrays.asList(ref1, ref2)), contains(o1, o2));
129133
}
134+
135+
@Test // DATAMONGO-1765
136+
public void bulkFetchContainsDuplicates() {
137+
138+
Document document = new Document("_id", new ObjectId());
139+
140+
DBRef ref1 = new DBRef("collection-1", document.get("_id"));
141+
DBRef ref2 = new DBRef("collection-1", document.get("_id"));
142+
143+
when(cursorMock.into(any())).then(invocation -> Arrays.asList(document));
144+
145+
assertThat(resolver.bulkFetch(Arrays.asList(ref1, ref2))).containsExactly(document, document);
146+
}
130147
}

0 commit comments

Comments
 (0)