From cb302e90a6696dd94cbb367821e0172e04248a40 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Fri, 9 Mar 2018 18:29:43 +0100 Subject: [PATCH] DATAJDBC-183 - Using dedicated class for handling map entries. Instead of using Map.Entry we now use a dedicated class to hold key and value of a map entry. This avoids side effects from the implementation of Map.Entry. Replaced call to saveReferencedEntities with insertReferencedEntities which is simpler but equivalent since referenced entities always get only inserted, never updated. Removed superfluous methods resulting from that change. --- .../core/conversion/JdbcEntityWriter.java | 100 +++++++----------- .../conversion/JdbcEntityWriterUnitTests.java | 39 +++++++ 2 files changed, 79 insertions(+), 60 deletions(-) diff --git a/src/main/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriter.java b/src/main/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriter.java index 3e152f36a..ad65a460c 100644 --- a/src/main/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriter.java +++ b/src/main/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriter.java @@ -15,7 +15,14 @@ */ package org.springframework.data.jdbc.core.conversion; -import lombok.RequiredArgsConstructor; +import lombok.Data; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; + import org.springframework.data.jdbc.core.conversion.DbAction.Insert; import org.springframework.data.jdbc.core.conversion.DbAction.Update; import org.springframework.data.jdbc.mapping.model.JdbcMappingContext; @@ -27,13 +34,6 @@ import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.util.StreamUtils; import org.springframework.util.ClassUtils; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.stream.Stream; - /** * Converts an entity that is about to be saved into {@link DbAction}s inside a {@link AggregateChange} that need to be * executed against the database to recreate the appropriate state in the database. @@ -62,8 +62,15 @@ public class JdbcEntityWriter extends JdbcEntityWriterSupport { Insert insert = DbAction.insert(o, propertyPath, dependingOn); aggregateChange.addAction(insert); - referencedEntities(o).forEach(propertyAndValue -> saveReferencedEntities(propertyAndValue, aggregateChange, - propertyPath.nested(propertyAndValue.property.getName()), insert)); + referencedEntities(o) // + .forEach( // + propertyAndValue -> // + insertReferencedEntities( // + propertyAndValue, // + aggregateChange, // + propertyPath.nested(propertyAndValue.property.getName()), // + insert) // + ); } else { deleteReferencedEntities(entityInformation.getRequiredId(o), aggregateChange); @@ -76,53 +83,13 @@ public class JdbcEntityWriter extends JdbcEntityWriterSupport { } } - private void saveReferencedEntities(PropertyAndValue propertyAndValue, AggregateChange aggregateChange, - JdbcPropertyPath propertyPath, DbAction dependingOn) { - - saveActions(propertyAndValue, propertyPath, dependingOn).forEach(a -> { - - aggregateChange.addAction(a); - referencedEntities(propertyAndValue.value) - .forEach(pav -> saveReferencedEntities(pav, aggregateChange, propertyPath.nested(pav.property.getName()), a)); - }); - } - - private Stream saveActions(PropertyAndValue propertyAndValue, JdbcPropertyPath propertyPath, - DbAction dependingOn) { - - if (Map.Entry.class.isAssignableFrom(ClassUtils.getUserClass(propertyAndValue.value))) { - return mapEntrySaveAction(propertyAndValue, propertyPath, dependingOn); - } - - return Stream.of(singleSaveAction(propertyAndValue.value, propertyPath, dependingOn)); - } - - private Stream mapEntrySaveAction(PropertyAndValue propertyAndValue, JdbcPropertyPath propertyPath, - DbAction dependingOn) { - - Map.Entry entry = (Map.Entry) propertyAndValue.value; - - DbAction action = singleSaveAction(entry.getValue(), propertyPath, dependingOn); - action.getAdditionalValues().put(propertyAndValue.property.getKeyColumn(), entry.getKey()); - return Stream.of(action); - } - - private DbAction singleSaveAction(T t, JdbcPropertyPath propertyPath, DbAction dependingOn) { - - JdbcPersistentEntityInformation entityInformation = context - .getRequiredPersistentEntityInformation((Class) ClassUtils.getUserClass(t)); - - return entityInformation.isNew(t) ? DbAction.insert(t, propertyPath, dependingOn) - : DbAction.update(t, propertyPath, dependingOn); - } - private void insertReferencedEntities(PropertyAndValue propertyAndValue, AggregateChange aggregateChange, JdbcPropertyPath propertyPath, DbAction dependingOn) { Insert insert; if (propertyAndValue.property.isQualified()) { - Entry valueAsEntry = (Entry) propertyAndValue.value; + KeyValue valueAsEntry = (KeyValue) propertyAndValue.value; insert = DbAction.insert(valueAsEntry.getValue(), propertyPath, dependingOn); insert.getAdditionalValues().put(propertyAndValue.property.getKeyColumn(), valueAsEntry.getKey()); } else { @@ -130,8 +97,14 @@ public class JdbcEntityWriter extends JdbcEntityWriterSupport { } aggregateChange.addAction(insert); - referencedEntities(insert.getEntity()) - .forEach(pav -> insertReferencedEntities(pav, aggregateChange, propertyPath.nested(pav.property.getName()), dependingOn)); + referencedEntities(insert.getEntity()) // + .peek(System.out::println) + .forEach(pav -> insertReferencedEntities( // + pav, // + aggregateChange, // + propertyPath.nested(pav.property.getName()), // + dependingOn) // + ); } private Stream referencedEntities(Object o) { @@ -189,13 +162,11 @@ public class JdbcEntityWriter extends JdbcEntityWriterSupport { if (property == null) return Stream.empty(); + // ugly hackery since Java streams don't have a zip method. + AtomicInteger index = new AtomicInteger(); List listProperty = (List) property; - HashMap map = new HashMap<>(); - for (int i = 0; i < listProperty.size(); i++) { - map.put(i, listProperty.get(i)); - } - return map.entrySet().stream().map(e -> (Object) e); + return listProperty.stream().map(e -> new KeyValue(index.getAndIncrement(), e)); } private Stream mapPropertyAsStream(JdbcPersistentProperty p, PersistentPropertyAccessor propertyAccessor) { @@ -204,7 +175,7 @@ public class JdbcEntityWriter extends JdbcEntityWriterSupport { return property == null // ? Stream.empty() // - : ((Map) property).entrySet().stream().map(e -> (Object) e); + : ((Map) property).entrySet().stream().map(e -> new KeyValue(e.getKey(), e.getValue())); } private Stream singlePropertyAsStream(JdbcPersistentProperty p, PersistentPropertyAccessor propertyAccessor) { @@ -217,7 +188,16 @@ public class JdbcEntityWriter extends JdbcEntityWriterSupport { return Stream.of(property); } - @RequiredArgsConstructor + /** + * Holds key and value of a {@link Map.Entry} but without any ties to {@link Map} implementations. + */ + @Data + private static class KeyValue { + private final Object key; + private final Object value; + } + + @Data private static class PropertyAndValue { private final JdbcPersistentProperty property; diff --git a/src/test/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriterUnitTests.java b/src/test/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriterUnitTests.java index f37fb2f31..df21805da 100644 --- a/src/test/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriterUnitTests.java +++ b/src/test/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriterUnitTests.java @@ -203,6 +203,45 @@ public class JdbcEntityWriterUnitTests { ); } + @Test // DATAJDBC-183 + public void newEntityWithFullMapResultsInAdditionalInsertPerElement() { + + MapContainer entity = new MapContainer(null); + entity.elements.put("1", new Element(null)); + entity.elements.put("2", new Element(null)); + entity.elements.put("3", new Element(null)); + entity.elements.put("4", new Element(null)); + entity.elements.put("5", new Element(null)); + entity.elements.put("6", new Element(null)); + entity.elements.put("7", new Element(null)); + entity.elements.put("8", new Element(null)); + entity.elements.put("9", new Element(null)); + entity.elements.put("0", new Element(null)); + entity.elements.put("a", new Element(null)); + entity.elements.put("b", new Element(null)); + + AggregateChange aggregateChange = new AggregateChange(Kind.SAVE, MapContainer.class, entity); + converter.write(entity, aggregateChange); + + assertThat(aggregateChange.getActions()) + .extracting(DbAction::getClass, DbAction::getEntityType, this::getMapKey, this::extractPath) // + .containsExactlyInAnyOrder( // + tuple(Insert.class, MapContainer.class, null, ""), // + tuple(Insert.class, Element.class, "1", "elements"), // + tuple(Insert.class, Element.class, "2", "elements"), // + tuple(Insert.class, Element.class, "3", "elements"), // + tuple(Insert.class, Element.class, "4", "elements"), // + tuple(Insert.class, Element.class, "5", "elements"), // + tuple(Insert.class, Element.class, "6", "elements"), // + tuple(Insert.class, Element.class, "7", "elements"), // + tuple(Insert.class, Element.class, "8", "elements"), // + tuple(Insert.class, Element.class, "9", "elements"), // + tuple(Insert.class, Element.class, "0", "elements"), // + tuple(Insert.class, Element.class, "a", "elements"), // + tuple(Insert.class, Element.class, "b", "elements") // + ); + } + @Test // DATAJDBC-130 public void newEntityWithEmptyListResultsInSingleInsert() {