From f151060773fee19d154e790bf3488007b4c438a5 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Wed, 25 Nov 2015 16:06:52 +0100 Subject: [PATCH] DATAMONGO-1335 - DBObjectAccessor now writes all nested fields correctly. Previously, DBObjectAccessor has always reset the in-between values when traversing nested properties. This caused previously written values to be erased if subsequent values are written. We now reuse an already existing BasicDBObject if present. --- .../core/convert/DBObjectAccessor.java | 34 ++++++++++++++++--- .../convert/DBObjectAccessorUnitTests.java | 28 +++++++++++++++ 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DBObjectAccessor.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DBObjectAccessor.java index 8e16a1554..a3d9cc9b1 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DBObjectAccessor.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DBObjectAccessor.java @@ -75,9 +75,7 @@ class DBObjectAccessor { String part = parts.next(); if (parts.hasNext()) { - BasicDBObject nestedDbObject = new BasicDBObject(); - dbObject.put(part, nestedDbObject); - dbObject = nestedDbObject; + dbObject = getOrCreateNestedDbObject(part, dbObject); } else { dbObject.put(part, value); } @@ -116,8 +114,14 @@ class DBObjectAccessor { return result; } + /** + * Returns the given source object as map, i.e. {@link BasicDBObject}s and maps as is or {@literal null} otherwise. + * + * @param source can be {@literal null}. + * @return + */ @SuppressWarnings("unchecked") - private Map getAsMap(Object source) { + private static Map getAsMap(Object source) { if (source instanceof BasicDBObject) { return (BasicDBObject) source; @@ -129,4 +133,26 @@ class DBObjectAccessor { return null; } + + /** + * Returns the {@link DBObject} which either already exists in the given source under the given key, or creates a new + * nested one, registers it with the source and returns it. + * + * @param key must not be {@literal null} or empty. + * @param source must not be {@literal null}. + * @return + */ + private static DBObject getOrCreateNestedDbObject(String key, DBObject source) { + + Object existing = source.get(key); + + if (existing instanceof BasicDBObject) { + return (BasicDBObject) existing; + } + + DBObject nested = new BasicDBObject(); + source.put(key, nested); + + return nested; + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DBObjectAccessorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DBObjectAccessorUnitTests.java index e476d7e1d..b4ad05236 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DBObjectAccessorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DBObjectAccessorUnitTests.java @@ -79,6 +79,28 @@ public class DBObjectAccessorUnitTests { new DBObjectAccessor(null); } + /** + * @see DATAMONGO-1335 + */ + @Test + public void writesAllNestingsCorrectly() { + + MongoPersistentEntity entity = context.getPersistentEntity(TypeWithTwoNestings.class); + + BasicDBObject target = new BasicDBObject(); + + DBObjectAccessor accessor = new DBObjectAccessor(target); + accessor.put(entity.getPersistentProperty("id"), "id"); + accessor.put(entity.getPersistentProperty("b"), "b"); + accessor.put(entity.getPersistentProperty("c"), "c"); + + DBObject nestedA = DBObjectTestUtils.getAsDBObject(target, "a"); + + assertThat(nestedA, is(notNullValue())); + assertThat(nestedA.get("b"), is((Object) "b")); + assertThat(nestedA.get("c"), is((Object) "c")); + } + static class ProjectingType { String name; @@ -91,4 +113,10 @@ public class DBObjectAccessorUnitTests { String c; } + static class TypeWithTwoNestings { + + String id; + @Field("a.b") String b; + @Field("a.c") String c; + } }