From 3d9a4c23d6e39c79e2c287fca1a24bea7ed706e9 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Fri, 9 Mar 2018 18:20:03 +0100 Subject: [PATCH 1/3] DATAJDBC-183 - Prepare branch --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 634c3fe1f5..ac278f9981 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-jdbc - 1.0.0.BUILD-SNAPSHOT + 1.0.0.DATAJDBC-183-SNAPSHOT Spring Data JDBC Spring Data module for JDBC repositories. From 8577913fa5183047127c5c657b386da0d2b91886 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Fri, 9 Mar 2018 18:29:43 +0100 Subject: [PATCH 2/3] 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 3e152f36a7..ad65a460ce 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.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 @@ private void write(Object o, AggregateChange aggregateChange, DbAction depending 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 @@ private void write(Object o, AggregateChange aggregateChange, DbAction depending } } - 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 @@ private void insertReferencedEntities(PropertyAndValue propertyAndValue, Aggrega } 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 @@ private Stream listPropertyAsStream(JdbcPersistentProperty p, Persistent 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 @@ private Stream mapPropertyAsStream(JdbcPersistentProperty p, PersistentP 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 @@ private Stream singlePropertyAsStream(JdbcPersistentProperty p, Persiste 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 f37fb2f313..df21805da2 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 void newEntityWithMapResultsInAdditionalInsertPerElement() { ); } + @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() { From b530d27e8c0c90d86ed50671f5d272ce871e02a1 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Mon, 19 Mar 2018 15:31:33 +0100 Subject: [PATCH 3/3] DATAJDBC-183 - Polishing. Removed calls to System.out. Formatting. Import order. --- .../jdbc/core/conversion/JdbcEntityWriter.java | 14 ++++++++------ .../JdbcRepositoryWithListsIntegrationTests.java | 2 -- .../JdbcRepositoryWithMapsIntegrationTests.java | 2 -- 3 files changed, 8 insertions(+), 10 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 ad65a460ce..cec9c53ecf 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 @@ -78,13 +78,13 @@ private void write(Object o, AggregateChange aggregateChange, DbAction depending Update update = DbAction.update(o, propertyPath, dependingOn); aggregateChange.addAction(update); - referencedEntities(o).forEach( - propertyAndValue -> insertReferencedEntities(propertyAndValue, aggregateChange, propertyPath.nested(propertyAndValue.property.getName()), update)); + referencedEntities(o).forEach(propertyAndValue -> insertReferencedEntities(propertyAndValue, aggregateChange, + propertyPath.nested(propertyAndValue.property.getName()), update)); } } private void insertReferencedEntities(PropertyAndValue propertyAndValue, AggregateChange aggregateChange, - JdbcPropertyPath propertyPath, DbAction dependingOn) { + JdbcPropertyPath propertyPath, DbAction dependingOn) { Insert insert; if (propertyAndValue.property.isQualified()) { @@ -116,7 +116,7 @@ private Stream referencedEntities(Object o) { .flatMap( // p -> referencedEntity(p, persistentEntity.getPropertyAccessor(o)) // .map(e -> new PropertyAndValue(p, e)) // - ); + ); } private Stream referencedEntity(JdbcPersistentProperty p, PersistentPropertyAccessor propertyAccessor) { @@ -147,7 +147,7 @@ private Stream referencedEntity(JdbcPersistentProperty p, PersistentProp } private Stream collectionPropertyAsStream(JdbcPersistentProperty p, - PersistentPropertyAccessor propertyAccessor) { + PersistentPropertyAccessor propertyAccessor) { Object property = propertyAccessor.getProperty(p); @@ -160,7 +160,9 @@ private Stream listPropertyAsStream(JdbcPersistentProperty p, Persistent Object property = propertyAccessor.getProperty(p); - if (property == null) return Stream.empty(); + if (property == null) { + return Stream.empty(); + } // ugly hackery since Java streams don't have a zip method. AtomicInteger index = new AtomicInteger(); diff --git a/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryWithListsIntegrationTests.java b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryWithListsIntegrationTests.java index 23607d0fe2..8c5c72e920 100644 --- a/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryWithListsIntegrationTests.java +++ b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryWithListsIntegrationTests.java @@ -132,8 +132,6 @@ public void findAllLoadsList() { Iterable reloaded = repository.findAll(); - reloaded.forEach(de -> System.out.println("id " + de.id + " content " + de.content.iterator().next().content)); - assertThat(reloaded) // .extracting(e -> e.id, e -> e.content.size()) // .containsExactly(tuple(entity.id, entity.content.size())); diff --git a/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryWithMapsIntegrationTests.java b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryWithMapsIntegrationTests.java index b5e7419108..f0efb19c0a 100644 --- a/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryWithMapsIntegrationTests.java +++ b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryWithMapsIntegrationTests.java @@ -127,8 +127,6 @@ public void findAllLoadsMap() { Iterable reloaded = repository.findAll(); - reloaded.forEach(de -> System.out.println("id " + de.id + " content " + de.content.values().iterator().next().content)); - assertThat(reloaded) // .extracting(e -> e.id, e -> e.content.size()) // .containsExactly(tuple(entity.id, entity.content.size()));