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. 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..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 @@ -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); @@ -71,58 +78,18 @@ 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 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) { + 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) { @@ -143,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) { @@ -174,7 +147,7 @@ private Stream referencedEntity(JdbcPersistentProperty p, PersistentProp } private Stream collectionPropertyAsStream(JdbcPersistentProperty p, - PersistentPropertyAccessor propertyAccessor) { + PersistentPropertyAccessor propertyAccessor) { Object property = propertyAccessor.getProperty(p); @@ -187,15 +160,15 @@ 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(); 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 +177,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 +190,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() { 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()));