diff --git a/pom.xml b/pom.xml index dadda28174..4e9cb95441 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-relational-parent - 3.0.0-SNAPSHOT + 3.0.0-1201-update-return-value-SNAPSHOT pom Spring Data Relational Parent diff --git a/spring-data-jdbc-distribution/pom.xml b/spring-data-jdbc-distribution/pom.xml index db3b7ddd1a..12558a13d3 100644 --- a/spring-data-jdbc-distribution/pom.xml +++ b/spring-data-jdbc-distribution/pom.xml @@ -14,7 +14,7 @@ org.springframework.data spring-data-relational-parent - 3.0.0-SNAPSHOT + 3.0.0-1201-update-return-value-SNAPSHOT ../pom.xml diff --git a/spring-data-jdbc/pom.xml b/spring-data-jdbc/pom.xml index 547ff62b8b..d5c4ea9525 100644 --- a/spring-data-jdbc/pom.xml +++ b/spring-data-jdbc/pom.xml @@ -6,7 +6,7 @@ 4.0.0 spring-data-jdbc - 3.0.0-SNAPSHOT + 3.0.0-1201-update-return-value-SNAPSHOT Spring Data JDBC Spring Data module for JDBC repositories. @@ -15,7 +15,7 @@ org.springframework.data spring-data-relational-parent - 3.0.0-SNAPSHOT + 3.0.0-1201-update-return-value-SNAPSHOT diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/AggregateChangeExecutor.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/AggregateChangeExecutor.java index 7b016e4179..0f584a0707 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/AggregateChangeExecutor.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/AggregateChangeExecutor.java @@ -18,10 +18,10 @@ import org.springframework.data.jdbc.core.convert.DataAccessStrategy; import org.springframework.data.jdbc.core.convert.JdbcConverter; import org.springframework.data.relational.core.conversion.AggregateChange; +import org.springframework.data.relational.core.conversion.AggregateChangeWithRoot; import org.springframework.data.relational.core.conversion.DbAction; import org.springframework.data.relational.core.conversion.DbActionExecutionException; import org.springframework.data.relational.core.conversion.MutableAggregateChange; -import org.springframework.lang.Nullable; /** * Executes an {@link MutableAggregateChange}. @@ -42,20 +42,22 @@ class AggregateChangeExecutor { this.accessStrategy = accessStrategy; } - @Nullable - T execute(AggregateChange aggregateChange) { + T execute(AggregateChangeWithRoot aggregateChange) { JdbcAggregateChangeExecutionContext executionContext = new JdbcAggregateChangeExecutionContext(converter, accessStrategy); aggregateChange.forEachAction(action -> execute(action, executionContext)); - T root = executionContext.populateIdsIfNecessary(); - if (root == null) { - root = aggregateChange.getEntity(); - } + return executionContext.populateIdsIfNecessary(); + } - return root; + void execute(AggregateChange aggregateChange) { + + JdbcAggregateChangeExecutionContext executionContext = new JdbcAggregateChangeExecutionContext(converter, + accessStrategy); + + aggregateChange.forEachAction(action -> execute(action, executionContext)); } private void execute(DbAction action, JdbcAggregateChangeExecutionContext executionContext) { @@ -69,8 +71,6 @@ private void execute(DbAction action, JdbcAggregateChangeExecutionContext exe executionContext.executeInsertBatch((DbAction.InsertBatch) action); } else if (action instanceof DbAction.UpdateRoot) { executionContext.executeUpdateRoot((DbAction.UpdateRoot) action); - } else if (action instanceof DbAction.Update) { - executionContext.executeUpdate((DbAction.Update) action); } else if (action instanceof DbAction.Delete) { executionContext.executeDelete((DbAction.Delete) action); } else if (action instanceof DbAction.DeleteAll) { diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutionContext.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutionContext.java index 7b9368d37e..930b6f89b4 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutionContext.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutionContext.java @@ -15,15 +15,7 @@ */ package org.springframework.data.jdbc.core; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.function.BiConsumer; import java.util.stream.Collectors; @@ -40,6 +32,7 @@ import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.relational.core.conversion.DbAction; import org.springframework.data.relational.core.conversion.DbActionExecutionResult; +import org.springframework.data.relational.core.conversion.IdValueSource; import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; @@ -108,15 +101,7 @@ void executeUpdateRoot(DbAction.UpdateRoot update) { } else { updateWithoutVersion(update); } - } - - void executeUpdate(DbAction.Update update) { - - if (!accessStrategy.update(update.getEntity(), update.getEntityType())) { - - throw new IncorrectUpdateSemanticsDataAccessException( - String.format(UPDATE_FAILED, update.getEntity(), getIdFrom(update))); - } + add(new DbActionExecutionResult(update)); } void executeDeleteRoot(DbAction.DeleteRoot delete) { @@ -203,11 +188,12 @@ private DbAction.WithEntity getIdOwningAction(DbAction.WithEntity action, private Object getPotentialGeneratedIdFrom(DbAction.WithEntity idOwningAction) { - if (idOwningAction instanceof DbAction.WithGeneratedId) { + if (IdValueSource.GENERATED.equals(idOwningAction.getIdValueSource())) { - Object generatedId; DbActionExecutionResult dbActionExecutionResult = results.get(idOwningAction); - generatedId = dbActionExecutionResult == null ? null : dbActionExecutionResult.getId(); + Object generatedId = Optional.ofNullable(dbActionExecutionResult) // + .map(DbActionExecutionResult::getGeneratedId) // + .orElse(null); if (generatedId != null) { return generatedId; @@ -227,12 +213,8 @@ private Object getIdFrom(DbAction.WithEntity idOwningAction) { return identifier; } - @SuppressWarnings("unchecked") - @Nullable T populateIdsIfNecessary() { - T newRoot = null; - // have the results so that the inserts on the leaves come first. List reverseResults = new ArrayList<>(results.values()); Collections.reverse(reverseResults); @@ -241,33 +223,29 @@ T populateIdsIfNecessary() { for (DbActionExecutionResult result : reverseResults) { - DbAction action = result.getAction(); + DbAction.WithEntity action = result.getAction(); - if (!(action instanceof DbAction.WithGeneratedId)) { - continue; + Object newEntity = setIdAndCascadingProperties(action, result.getGeneratedId(), cascadingValues); + + if (action instanceof DbAction.InsertRoot || action instanceof DbAction.UpdateRoot) { + //noinspection unchecked + return (T) newEntity; } - DbAction.WithEntity withEntity = (DbAction.WithGeneratedId) action; - Object newEntity = setIdAndCascadingProperties(withEntity, result.getId(), cascadingValues); - // the id property was immutable so we have to propagate changes up the tree - if (newEntity != withEntity.getEntity()) { - - if (action instanceof DbAction.Insert) { - DbAction.Insert insert = (DbAction.Insert) action; - - Pair qualifier = insert.getQualifier(); + if (newEntity != action.getEntity() && action instanceof DbAction.Insert) { + DbAction.Insert insert = (DbAction.Insert) action; - cascadingValues.stage(insert.getDependingOn(), insert.getPropertyPath(), - qualifier == null ? null : qualifier.getSecond(), newEntity); + Pair qualifier = insert.getQualifier(); - } else if (action instanceof DbAction.InsertRoot) { - newRoot = (T) newEntity; - } + cascadingValues.stage(insert.getDependingOn(), insert.getPropertyPath(), + qualifier == null ? null : qualifier.getSecond(), newEntity); } } - return newRoot; + throw new IllegalStateException( + String.format("Cannot retrieve the resulting instance unless a %s or %s action was successfully executed.", + DbAction.InsertRoot.class.getName(), DbAction.UpdateRoot.class.getName())); } private Object setIdAndCascadingProperties(DbAction.WithEntity action, @Nullable Object generatedId, @@ -279,7 +257,7 @@ private Object setIdAndCascadingProperties(DbAction.WithEntity action, @N .getRequiredPersistentEntity(action.getEntityType()); PersistentPropertyAccessor propertyAccessor = converter.getPropertyAccessor(persistentEntity, originalEntity); - if (generatedId != null && persistentEntity.hasIdProperty()) { + if (IdValueSource.GENERATED.equals(action.getIdValueSource())) { propertyAccessor.setProperty(persistentEntity.getRequiredIdProperty(), generatedId); } @@ -301,6 +279,10 @@ private PersistentPropertyPath getRelativePath(DbAction action, Persistent return pathToValue; } + if (action instanceof DbAction.UpdateRoot) { + return pathToValue; + } + throw new IllegalArgumentException(String.format("DbAction of type %s is not supported.", action.getClass())); } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateTemplate.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateTemplate.java index f8f7ff6743..24c2de8689 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateTemplate.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateTemplate.java @@ -31,6 +31,7 @@ import org.springframework.data.mapping.IdentifierAccessor; import org.springframework.data.mapping.callback.EntityCallbacks; import org.springframework.data.relational.core.conversion.AggregateChange; +import org.springframework.data.relational.core.conversion.AggregateChangeWithRoot; import org.springframework.data.relational.core.conversion.MutableAggregateChange; import org.springframework.data.relational.core.conversion.RelationalEntityDeleteWriter; import org.springframework.data.relational.core.conversion.RelationalEntityInsertWriter; @@ -61,8 +62,6 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations { private final RelationalMappingContext context; private final RelationalEntityDeleteWriter jdbcEntityDeleteWriter; - private final RelationalEntityInsertWriter jdbcEntityInsertWriter; - private final RelationalEntityUpdateWriter jdbcEntityUpdateWriter; private final DataAccessStrategy accessStrategy; private final AggregateChangeExecutor executor; @@ -92,8 +91,6 @@ public JdbcAggregateTemplate(ApplicationContext publisher, RelationalMappingCont this.accessStrategy = dataAccessStrategy; this.converter = converter; - this.jdbcEntityInsertWriter = new RelationalEntityInsertWriter(context); - this.jdbcEntityUpdateWriter = new RelationalEntityUpdateWriter(context); this.jdbcEntityDeleteWriter = new RelationalEntityDeleteWriter(context); this.executor = new AggregateChangeExecutor(converter, accessStrategy); @@ -122,8 +119,6 @@ public JdbcAggregateTemplate(ApplicationEventPublisher publisher, RelationalMapp this.accessStrategy = dataAccessStrategy; this.converter = converter; - this.jdbcEntityInsertWriter = new RelationalEntityInsertWriter(context); - this.jdbcEntityUpdateWriter = new RelationalEntityUpdateWriter(context); this.jdbcEntityDeleteWriter = new RelationalEntityDeleteWriter(context); this.executor = new AggregateChangeExecutor(converter, accessStrategy); } @@ -146,7 +141,7 @@ public T save(T instance) { RelationalPersistentEntity persistentEntity = context.getRequiredPersistentEntity(instance.getClass()); - Function> changeCreator = persistentEntity.isNew(instance) + Function> changeCreator = persistentEntity.isNew(instance) ? entity -> createInsertChange(prepareVersionForInsert(entity)) : entity -> createUpdateChange(prepareVersionForUpdate(entity)); @@ -286,18 +281,18 @@ public void deleteAll(Class domainType) { executor.execute(change); } - private T store(T aggregateRoot, Function> changeCreator, + private T store(T aggregateRoot, Function> changeCreator, RelationalPersistentEntity persistentEntity) { Assert.notNull(aggregateRoot, "Aggregate instance must not be null!"); aggregateRoot = triggerBeforeConvert(aggregateRoot); - MutableAggregateChange change = changeCreator.apply(aggregateRoot); + AggregateChangeWithRoot change = changeCreator.apply(aggregateRoot); - aggregateRoot = triggerBeforeSave(change.getEntity(), change); + aggregateRoot = triggerBeforeSave(change.getRoot(), change); - change.setEntity(aggregateRoot); + change.setRoot(aggregateRoot); T entityAfterExecution = executor.execute(change); @@ -313,25 +308,24 @@ private void deleteTree(Object id, @Nullable T entity, Class domainType) MutableAggregateChange change = createDeletingChange(id, entity, domainType); entity = triggerBeforeDelete(entity, id, change); - change.setEntity(entity); executor.execute(change); triggerAfterDelete(entity, id, change); } - private MutableAggregateChange createInsertChange(T instance) { + private AggregateChangeWithRoot createInsertChange(T instance) { - MutableAggregateChange aggregateChange = MutableAggregateChange.forSave(instance); - jdbcEntityInsertWriter.write(instance, aggregateChange); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(instance); + new RelationalEntityInsertWriter(context).write(instance, aggregateChange); return aggregateChange; } - private MutableAggregateChange createUpdateChange(EntityAndPreviousVersion entityAndVersion) { + private AggregateChangeWithRoot createUpdateChange(EntityAndPreviousVersion entityAndVersion) { - MutableAggregateChange aggregateChange = MutableAggregateChange.forSave(entityAndVersion.entity, + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entityAndVersion.entity, entityAndVersion.version); - jdbcEntityUpdateWriter.write(entityAndVersion.entity, aggregateChange); + new RelationalEntityUpdateWriter(context).write(entityAndVersion.entity, aggregateChange); return aggregateChange; } @@ -383,14 +377,14 @@ private MutableAggregateChange createDeletingChange(Object id, @Nullable previousVersion = RelationalEntityVersionUtils.getVersionNumberFromEntity(entity, persistentEntity, converter); } } - MutableAggregateChange aggregateChange = MutableAggregateChange.forDelete(domainType, entity, previousVersion); + MutableAggregateChange aggregateChange = MutableAggregateChange.forDelete(domainType, previousVersion); jdbcEntityDeleteWriter.write(id, aggregateChange); return aggregateChange; } private MutableAggregateChange createDeletingChange(Class domainType) { - MutableAggregateChange aggregateChange = MutableAggregateChange.forDelete(domainType, null); + MutableAggregateChange aggregateChange = MutableAggregateChange.forDelete(domainType); jdbcEntityDeleteWriter.write(null, aggregateChange); return aggregateChange; } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AggregateChangeIdGenerationImmutableUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AggregateChangeIdGenerationImmutableUnitTests.java index f51b930e2b..3a92cca271 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AggregateChangeIdGenerationImmutableUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AggregateChangeIdGenerationImmutableUnitTests.java @@ -39,6 +39,7 @@ import org.springframework.data.jdbc.core.convert.JdbcConverter; import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.mapping.PersistentPropertyPaths; +import org.springframework.data.relational.core.conversion.AggregateChangeWithRoot; import org.springframework.data.relational.core.conversion.DbAction; import org.springframework.data.relational.core.conversion.IdValueSource; import org.springframework.data.relational.core.conversion.MutableAggregateChange; @@ -70,7 +71,7 @@ public class AggregateChangeIdGenerationImmutableUnitTests { RelationalMappingContext context = new RelationalMappingContext(); JdbcConverter converter = mock(JdbcConverter.class); - DbAction.WithEntity rootInsert = new DbAction.InsertRoot<>(entity, IdValueSource.GENERATED); + DbAction.WithRoot rootInsert = new DbAction.InsertRoot<>(entity, IdValueSource.GENERATED); DataAccessStrategy accessStrategy = mock(DataAccessStrategy.class); @@ -79,8 +80,8 @@ public class AggregateChangeIdGenerationImmutableUnitTests { @Test // DATAJDBC-291 public void singleRoot() { - MutableAggregateChange aggregateChange = MutableAggregateChange.forSave(entity); - aggregateChange.addAction(rootInsert); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity); + aggregateChange.setRootAction(rootInsert); entity = executor.execute(aggregateChange); @@ -92,8 +93,8 @@ public void simpleReference() { entity = entity.withSingle(content); - MutableAggregateChange aggregateChange = MutableAggregateChange.forSave(entity); - aggregateChange.addAction(rootInsert); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity); + aggregateChange.setRootAction(rootInsert); aggregateChange.addAction(createInsert("single", content, null)); entity = executor.execute(aggregateChange); @@ -110,8 +111,8 @@ public void listReference() { entity = entity.withContentList(asList(content, content2)); - MutableAggregateChange aggregateChange = MutableAggregateChange.forSave(entity); - aggregateChange.addAction(rootInsert); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity); + aggregateChange.setRootAction(rootInsert); aggregateChange.addAction(createInsert("contentList", content, 0)); aggregateChange.addAction(createInsert("contentList", content2, 1)); @@ -129,8 +130,8 @@ public void mapReference() { entity = entity.withContentMap(createContentMap("a", content, "b", content2)); - MutableAggregateChange aggregateChange = MutableAggregateChange.forSave(entity); - aggregateChange.addAction(rootInsert); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity); + aggregateChange.setRootAction(rootInsert); aggregateChange.addAction(createInsert("contentMap", content, "a")); aggregateChange.addAction(createInsert("contentMap", content2, "b")); @@ -149,8 +150,8 @@ public void setIdForDeepReference() { DbAction.Insert parentInsert = createInsert("single", content, null); DbAction.Insert insert = createDeepInsert("single", tag1, null, parentInsert); - MutableAggregateChange aggregateChange = MutableAggregateChange.forSave(entity); - aggregateChange.addAction(rootInsert); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity); + aggregateChange.setRootAction(rootInsert); aggregateChange.addAction(parentInsert); aggregateChange.addAction(insert); @@ -171,8 +172,8 @@ public void setIdForDeepReferenceElementList() { DbAction.Insert insert1 = createDeepInsert("tagList", tag1, 0, parentInsert); DbAction.Insert insert2 = createDeepInsert("tagList", tag2, 1, parentInsert); - MutableAggregateChange aggregateChange = MutableAggregateChange.forSave(entity); - aggregateChange.addAction(rootInsert); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity); + aggregateChange.setRootAction(rootInsert); aggregateChange.addAction(parentInsert); aggregateChange.addAction(insert1); aggregateChange.addAction(insert2); @@ -197,8 +198,8 @@ public void setIdForDeepElementSetElementSet() { DbAction.Insert insert1 = createDeepInsert("tagSet", tag1, null, parentInsert); DbAction.Insert insert2 = createDeepInsert("tagSet", tag2, null, parentInsert); - MutableAggregateChange aggregateChange = MutableAggregateChange.forSave(entity); - aggregateChange.addAction(rootInsert); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity); + aggregateChange.setRootAction(rootInsert); aggregateChange.addAction(parentInsert); aggregateChange.addAction(insert1); aggregateChange.addAction(insert2); @@ -230,8 +231,8 @@ public void setIdForDeepElementListSingleReference() { DbAction.Insert insert1 = createDeepInsert("single", tag1, null, parentInsert1); DbAction.Insert insert2 = createDeepInsert("single", tag2, null, parentInsert2); - MutableAggregateChange aggregateChange = MutableAggregateChange.forSave(entity); - aggregateChange.addAction(rootInsert); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity); + aggregateChange.setRootAction(rootInsert); aggregateChange.addAction(parentInsert1); aggregateChange.addAction(parentInsert2); aggregateChange.addAction(insert1); @@ -261,8 +262,8 @@ public void setIdForDeepElementListElementList() { DbAction.Insert insert2 = createDeepInsert("tagList", tag2, 0, parentInsert2); DbAction.Insert insert3 = createDeepInsert("tagList", tag3, 1, parentInsert2); - MutableAggregateChange aggregateChange = MutableAggregateChange.forSave(entity); - aggregateChange.addAction(rootInsert); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity); + aggregateChange.setRootAction(rootInsert); aggregateChange.addAction(parentInsert1); aggregateChange.addAction(parentInsert2); aggregateChange.addAction(insert1); @@ -296,8 +297,8 @@ public void setIdForDeepElementMapElementMap() { DbAction.Insert insert2 = createDeepInsert("tagMap", tag2, "222", parentInsert2); DbAction.Insert insert3 = createDeepInsert("tagMap", tag3, "333", parentInsert2); - MutableAggregateChange aggregateChange = MutableAggregateChange.forSave(entity); - aggregateChange.addAction(rootInsert); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity); + aggregateChange.setRootAction(rootInsert); aggregateChange.addAction(parentInsert1); aggregateChange.addAction(parentInsert2); aggregateChange.addAction(insert1); @@ -335,8 +336,8 @@ public void setIdForDeepElementListSingleReferenceWithIntermittentNoId() { DbAction.Insert insert1 = createDeepInsert("single", tag1, null, parentInsert1); DbAction.Insert insert2 = createDeepInsert("single", tag2, null, parentInsert2); - MutableAggregateChange aggregateChange = MutableAggregateChange.forSave(entity); - aggregateChange.addAction(rootInsert); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity); + aggregateChange.setRootAction(rootInsert); aggregateChange.addAction(parentInsert1); aggregateChange.addAction(parentInsert2); aggregateChange.addAction(insert1); @@ -361,8 +362,8 @@ public void setIdForEmbeddedDeepReference() { DbAction.Insert parentInsert = createInsert("embedded.single", tag1, null); - MutableAggregateChange aggregateChange = MutableAggregateChange.forSave(entity); - aggregateChange.addAction(rootInsert); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity); + aggregateChange.setRootAction(rootInsert); aggregateChange.addAction(parentInsert); entity = executor.execute(aggregateChange); diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AggregateChangeIdGenerationUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AggregateChangeIdGenerationUnitTests.java index ad520ff985..1c7a31e9b0 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AggregateChangeIdGenerationUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AggregateChangeIdGenerationUnitTests.java @@ -36,6 +36,7 @@ import org.springframework.data.jdbc.core.convert.JdbcConverter; import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.mapping.PersistentPropertyPaths; +import org.springframework.data.relational.core.conversion.AggregateChangeWithRoot; import org.springframework.data.relational.core.conversion.DbAction; import org.springframework.data.relational.core.conversion.IdValueSource; import org.springframework.data.relational.core.conversion.MutableAggregateChange; @@ -63,15 +64,15 @@ public class AggregateChangeIdGenerationUnitTests { JdbcConverter converter = new BasicJdbcConverter(context, (identifier, path) -> { throw new UnsupportedOperationException(); }); - DbAction.WithEntity rootInsert = new DbAction.InsertRoot<>(entity, IdValueSource.GENERATED); + DbAction.WithRoot rootInsert = new DbAction.InsertRoot<>(entity, IdValueSource.GENERATED); DataAccessStrategy accessStrategy = mock(DataAccessStrategy.class, new IncrementingIds()); AggregateChangeExecutor executor = new AggregateChangeExecutor(converter, accessStrategy); @Test // DATAJDBC-291 public void singleRoot() { - MutableAggregateChange aggregateChange = MutableAggregateChange.forSave(entity); - aggregateChange.addAction(rootInsert); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity); + aggregateChange.setRootAction(rootInsert); executor.execute(aggregateChange); @@ -83,8 +84,8 @@ public void simpleReference() { entity.single = content; - MutableAggregateChange aggregateChange = MutableAggregateChange.forSave(entity); - aggregateChange.addAction(rootInsert); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity); + aggregateChange.setRootAction(rootInsert); aggregateChange.addAction(createInsert("single", content, null)); executor.execute(aggregateChange); @@ -102,8 +103,8 @@ public void listReference() { entity.contentList.add(content); entity.contentList.add(content2); - MutableAggregateChange aggregateChange = MutableAggregateChange.forSave(entity); - aggregateChange.addAction(rootInsert); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity); + aggregateChange.setRootAction(rootInsert); aggregateChange.addAction(createInsert("contentList", content, 0)); aggregateChange.addAction(createInsert("contentList", content2, 1)); @@ -122,8 +123,8 @@ public void mapReference() { entity.contentMap.put("a", content); entity.contentMap.put("b", content2); - MutableAggregateChange aggregateChange = MutableAggregateChange.forSave(entity); - aggregateChange.addAction(rootInsert); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity); + aggregateChange.setRootAction(rootInsert); aggregateChange.addAction(createInsert("contentMap", content, "a")); aggregateChange.addAction(createInsert("contentMap", content2, "b")); @@ -142,8 +143,8 @@ public void setIdForDeepReference() { DbAction.Insert parentInsert = createInsert("single", content, null); DbAction.Insert insert = createDeepInsert("single", tag1, null, parentInsert); - MutableAggregateChange aggregateChange = MutableAggregateChange.forSave(entity); - aggregateChange.addAction(rootInsert); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity); + aggregateChange.setRootAction(rootInsert); aggregateChange.addAction(parentInsert); aggregateChange.addAction(insert); @@ -165,8 +166,8 @@ public void setIdForDeepReferenceElementList() { DbAction.Insert insert1 = createDeepInsert("tagList", tag1, 0, parentInsert); DbAction.Insert insert2 = createDeepInsert("tagList", tag2, 1, parentInsert); - MutableAggregateChange aggregateChange = MutableAggregateChange.forSave(entity); - aggregateChange.addAction(rootInsert); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity); + aggregateChange.setRootAction(rootInsert); aggregateChange.addAction(parentInsert); aggregateChange.addAction(insert1); aggregateChange.addAction(insert2); @@ -192,8 +193,8 @@ public void setIdForDeepElementSetElementSet() { DbAction.Insert insert1 = createDeepInsert("tagSet", tag1, null, parentInsert); DbAction.Insert insert2 = createDeepInsert("tagSet", tag2, null, parentInsert); - MutableAggregateChange aggregateChange = MutableAggregateChange.forSave(entity); - aggregateChange.addAction(rootInsert); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity); + aggregateChange.setRootAction(rootInsert); aggregateChange.addAction(parentInsert); aggregateChange.addAction(insert1); aggregateChange.addAction(insert2); @@ -226,8 +227,8 @@ public void setIdForDeepElementListSingleReference() { DbAction.Insert insert1 = createDeepInsert("single", tag1, null, parentInsert1); DbAction.Insert insert2 = createDeepInsert("single", tag2, null, parentInsert2); - MutableAggregateChange aggregateChange = MutableAggregateChange.forSave(entity); - aggregateChange.addAction(rootInsert); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity); + aggregateChange.setRootAction(rootInsert); aggregateChange.addAction(parentInsert1); aggregateChange.addAction(parentInsert2); aggregateChange.addAction(insert1); @@ -259,8 +260,8 @@ public void setIdForDeepElementListElementList() { DbAction.Insert insert2 = createDeepInsert("tagList", tag2, 0, parentInsert2); DbAction.Insert insert3 = createDeepInsert("tagList", tag3, 1, parentInsert2); - MutableAggregateChange aggregateChange = MutableAggregateChange.forSave(entity); - aggregateChange.addAction(rootInsert); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity); + aggregateChange.setRootAction(rootInsert); aggregateChange.addAction(parentInsert1); aggregateChange.addAction(parentInsert2); aggregateChange.addAction(insert1); @@ -297,8 +298,8 @@ public void setIdForDeepElementMapElementMap() { DbAction.Insert insert2 = createDeepInsert("tagMap", tag2, "222", parentInsert2); DbAction.Insert insert3 = createDeepInsert("tagMap", tag3, "333", parentInsert2); - MutableAggregateChange aggregateChange = MutableAggregateChange.forSave(entity); - aggregateChange.addAction(rootInsert); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity); + aggregateChange.setRootAction(rootInsert); aggregateChange.addAction(parentInsert1); aggregateChange.addAction(parentInsert2); aggregateChange.addAction(insert1); diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/ImmutableAggregateTemplateHsqlIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/ImmutableAggregateTemplateHsqlIntegrationTests.java index f5d8479bb5..9da2eb1e24 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/ImmutableAggregateTemplateHsqlIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/ImmutableAggregateTemplateHsqlIntegrationTests.java @@ -18,11 +18,9 @@ import static java.util.Collections.*; import static org.assertj.core.api.Assertions.*; -import lombok.Value; -import lombok.With; - +import lombok.AllArgsConstructor; +import lombok.Data; import org.assertj.core.api.SoftAssertions; - import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; @@ -38,10 +36,11 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.test.context.junit4.rules.SpringClassRule; -import org.springframework.test.context.junit4.rules.SpringMethodRule; import org.springframework.transaction.annotation.Transactional; +import lombok.Value; +import lombok.With; + /** * Integration tests for {@link JdbcAggregateTemplate} and it's handling of immutable entities. * @@ -238,6 +237,26 @@ public void replaceReferencedEntity() { softly.assertAll(); } + @Test // GH-1201 + void replaceReferencedEntity_saveResult() { + + Root root = new Root(null, "originalRoot", new NonRoot(null, "originalNonRoot")); + Root originalSavedRoot = template.save(root); + + assertThat(originalSavedRoot.id).isNotNull(); + assertThat(originalSavedRoot.name).isEqualTo("originalRoot"); + assertThat(originalSavedRoot.reference.id).isNotNull(); + assertThat(originalSavedRoot.reference.name).isEqualTo("originalNonRoot"); + + Root updatedRoot = new Root(originalSavedRoot.id, "updatedRoot", new NonRoot(null, "updatedNonRoot")); + Root updatedSavedRoot = template.save(updatedRoot); + + assertThat(updatedSavedRoot.id).isNotNull(); + assertThat(updatedSavedRoot.name).isEqualTo("updatedRoot"); + assertThat(updatedSavedRoot.reference.id).isNotNull().isNotEqualTo(originalSavedRoot.reference.id); + assertThat(updatedSavedRoot.reference.name).isEqualTo("updatedNonRoot"); + } + @Test // DATAJDBC-241 public void changeReferencedEntity() { @@ -306,6 +325,21 @@ static class Author { String name; } + @Data + @AllArgsConstructor + static class Root { + @Id private Long id; + private String name; + private NonRoot reference; + } + + @Value + @With + static class NonRoot { + @Id Long id; + String name; + } + static class WithCopyConstructor { @Id private final Long id; diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutorContextImmutableUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutorContextImmutableUnitTests.java index 9a74fcefbc..f62de3498d 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutorContextImmutableUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutorContextImmutableUnitTests.java @@ -55,14 +55,6 @@ public class JdbcAggregateChangeExecutorContextImmutableUnitTests { DummyEntity root = new DummyEntity(); - @Test // DATAJDBC-453 - public void rootOfEmptySetOfActionsisNull() { - - Object root = executionContext.populateIdsIfNecessary(); - - assertThat(root).isNull(); - } - @Test // DATAJDBC-453 public void afterInsertRootIdMaybeUpdated() { diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutorContextUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutorContextUnitTests.java index 9db3d8d92c..0bff8beaf4 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutorContextUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutorContextUnitTests.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.List; +import lombok.Value; import org.junit.jupiter.api.Test; import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Version; @@ -61,14 +62,6 @@ public class JdbcAggregateChangeExecutorContextUnitTests { DummyEntity root = new DummyEntity(); - @Test // DATAJDBC-453 - public void rootOfEmptySetOfActionIsNull() { - - Object root = executionContext.populateIdsIfNecessary(); - - assertThat(root).isNull(); - } - @Test // DATAJDBC-453 public void afterInsertRootIdMaybeUpdated() { @@ -78,7 +71,7 @@ public void afterInsertRootIdMaybeUpdated() { DummyEntity newRoot = executionContext.populateIdsIfNecessary(); - assertThat(newRoot).isNull(); + assertThat(newRoot).isEqualTo(root); assertThat(root.id).isEqualTo(23L); } @@ -96,7 +89,7 @@ public void idGenerationOfChild() { DummyEntity newRoot = executionContext.populateIdsIfNecessary(); - assertThat(newRoot).isNull(); + assertThat(newRoot).isEqualTo(root); assertThat(root.id).isEqualTo(23L); assertThat(content.id).isEqualTo(24L); @@ -117,7 +110,7 @@ public void idGenerationOfChildInList() { DummyEntity newRoot = executionContext.populateIdsIfNecessary(); - assertThat(newRoot).isNull(); + assertThat(newRoot).isEqualTo(root); assertThat(root.id).isEqualTo(23L); assertThat(content.id).isEqualTo(24L); @@ -142,7 +135,7 @@ void batchInsertOperation_withGeneratedIds() { DummyEntity newRoot = executionContext.populateIdsIfNecessary(); - assertThat(newRoot).isNull(); + assertThat(newRoot).isEqualTo(root); assertThat(root.id).isEqualTo(123L); assertThat(content.id).isEqualTo(456L); } @@ -166,11 +159,30 @@ void batchInsertOperation_withoutGeneratedIds() { DummyEntity newRoot = executionContext.populateIdsIfNecessary(); - assertThat(newRoot).isNull(); + assertThat(newRoot).isEqualTo(root); assertThat(root.id).isEqualTo(123L); assertThat(content.id).isNull(); } + @Test // GH-1201 + void updates_whenReferencesWithImmutableIdAreInserted() { + when(accessStrategy.update(any(), any())).thenReturn(true); + root.id = 123L; + DbAction.UpdateRoot rootInsert = new DbAction.UpdateRoot<>(root, null); + executionContext.executeUpdateRoot(rootInsert); + + ContentImmutableId contentImmutableId = new ContentImmutableId(null); + root.contentImmutableId = contentImmutableId; + Identifier identifier = Identifier.empty().withPart(SqlIdentifier.quoted("DUMMY_ENTITY"), 123L, Long.class); + when(accessStrategy.insert(contentImmutableId, ContentImmutableId.class, identifier, IdValueSource.GENERATED)).thenReturn(456L); + executionContext.executeInsert(createInsert(rootInsert, "contentImmutableId", contentImmutableId, null)); + + DummyEntity newRoot = executionContext.populateIdsIfNecessary(); + assertThat(newRoot).isEqualTo(root); + assertThat(root.id).isEqualTo(123L); + assertThat(root.contentImmutableId.id).isEqualTo(456L); + } + DbAction.Insert createInsert(DbAction.WithEntity parent, String propertyName, Object value, @Nullable Object key) { @@ -205,6 +217,8 @@ private static class DummyEntity { Content content; + ContentImmutableId contentImmutableId; + List list = new ArrayList<>(); } @@ -212,4 +226,9 @@ private static class Content { @Id Long id; } + @Value + private static class ContentImmutableId { + @Id Long id; + } + } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryBeforeSaveHsqlIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryBeforeSaveHsqlIntegrationTests.java new file mode 100644 index 0000000000..516e886b31 --- /dev/null +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryBeforeSaveHsqlIntegrationTests.java @@ -0,0 +1,228 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.repository; + +import static org.assertj.core.api.Assertions.*; + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.FilterType; +import org.springframework.context.annotation.Import; +import org.springframework.data.annotation.Id; +import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories; +import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; +import org.springframework.data.relational.core.mapping.NamingStrategy; +import org.springframework.data.relational.core.mapping.event.BeforeSaveCallback; +import org.springframework.data.repository.ListCrudRepository; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.Value; +import lombok.With; + +/** + * Integration tests for the {@link BeforeSaveCallback}. + * + * @author Chirag Tailor + */ +@ContextConfiguration +@ExtendWith(SpringExtension.class) +@ActiveProfiles("hsql") +public class JdbcRepositoryBeforeSaveHsqlIntegrationTests { + + @Configuration + @Import(TestConfiguration.class) + static class Config { + + @Autowired JdbcRepositoryFactory factory; + + @Bean + Class testClass() { + return JdbcRepositoryBeforeSaveHsqlIntegrationTests.class; + } + } + + @Autowired NamedParameterJdbcTemplate template; + @Autowired ImmutableEntityRepository immutableWithManualIdEntityRepository; + @Autowired MutableEntityRepository mutableEntityRepository; + @Autowired MutableWithImmutableIdEntityRepository mutableWithImmutableIdEntityRepository; + @Autowired ImmutableWithMutableIdEntityRepository immutableWithMutableIdEntityRepository; + + @Test // GH-1199 + public void immutableEntity() { + + ImmutableEntity entity = new ImmutableEntity(null, "immutable"); + ImmutableEntity saved = immutableWithManualIdEntityRepository.save(entity); + + assertThat(saved.getId()).isNotNull(); + assertThat(saved.getName()).isEqualTo("fromBeforeSaveCallback"); + + List entities = immutableWithManualIdEntityRepository.findAll(); + assertThat(entities).hasSize(1); + ImmutableEntity reloaded = entities.get(0); + assertThat(reloaded.getId()).isNotNull(); + assertThat(reloaded.getName()).isEqualTo("fromBeforeSaveCallback"); + } + + @Test // GH-1199 + public void mutableEntity() { + + MutableEntity entity = new MutableEntity(null, "immutable"); + MutableEntity saved = mutableEntityRepository.save(entity); + + assertThat(saved.getId()).isNotNull(); + assertThat(saved.getName()).isEqualTo("fromBeforeSaveCallback"); + + List entities = mutableEntityRepository.findAll(); + assertThat(entities).hasSize(1); + MutableEntity reloaded = entities.get(0); + assertThat(reloaded.getId()).isNotNull(); + assertThat(reloaded.getName()).isEqualTo("fromBeforeSaveCallback"); + } + + @Test // GH-1199 + public void mutableWithImmutableIdEntity() { + + MutableWithImmutableIdEntity entity = new MutableWithImmutableIdEntity(null, "immutable"); + MutableWithImmutableIdEntity saved = mutableWithImmutableIdEntityRepository.save(entity); + + assertThat(saved.getId()).isNotNull(); + assertThat(saved.getName()).isEqualTo("fromBeforeSaveCallback"); + + List entities = mutableWithImmutableIdEntityRepository.findAll(); + assertThat(entities).hasSize(1); + MutableWithImmutableIdEntity reloaded = entities.get(0); + assertThat(reloaded.getId()).isNotNull(); + assertThat(reloaded.getName()).isEqualTo("fromBeforeSaveCallback"); + } + + @Test // GH-1199 + public void immutableWithMutableIdEntity() { + + ImmutableWithMutableIdEntity entity = new ImmutableWithMutableIdEntity(null, "immutable"); + ImmutableWithMutableIdEntity saved = immutableWithMutableIdEntityRepository.save(entity); + + assertThat(saved.getId()).isNotNull(); + assertThat(saved.getName()).isEqualTo("fromBeforeSaveCallback"); + + List entities = immutableWithMutableIdEntityRepository.findAll(); + assertThat(entities).hasSize(1); + ImmutableWithMutableIdEntity reloaded = entities.get(0); + assertThat(reloaded.getId()).isNotNull(); + assertThat(reloaded.getName()).isEqualTo("fromBeforeSaveCallback"); + } + + private interface ImmutableEntityRepository extends ListCrudRepository {} + + @Value + @With + static class ImmutableEntity { + @Id Long id; + String name; + } + + private interface MutableEntityRepository extends ListCrudRepository {} + + @Data + @AllArgsConstructor + static class MutableEntity { + @Id private Long id; + private String name; + } + + private interface MutableWithImmutableIdEntityRepository extends ListCrudRepository {} + + @Data + @AllArgsConstructor + static class MutableWithImmutableIdEntity { + @Id private final Long id; + private String name; + } + + private interface ImmutableWithMutableIdEntityRepository extends ListCrudRepository {} + + @Data + @AllArgsConstructor + static class ImmutableWithMutableIdEntity { + @Id private Long id; + @With + private final String name; + } + + @Configuration + @ComponentScan("org.springframework.data.jdbc.testing") + @EnableJdbcRepositories(considerNestedRepositories = true, + includeFilters = @ComponentScan.Filter(value = ListCrudRepository.class, type = FilterType.ASSIGNABLE_TYPE)) + static class TestConfiguration { + + @Bean + Class testClass() { + return JdbcRepositoryBeforeSaveHsqlIntegrationTests.class; + } + + /** + * {@link NamingStrategy} that harmlessly uppercases the table name, demonstrating how to inject one while not + * breaking existing SQL operations. + */ + @Bean + NamingStrategy namingStrategy() { + + return new NamingStrategy() { + + @Override + public String getTableName(Class type) { + return type.getSimpleName().toUpperCase(); + } + }; + } + + @Bean + BeforeSaveCallback nameSetterImmutable() { + return (aggregate, aggregateChange) -> aggregate.withName("fromBeforeSaveCallback"); + } + + @Bean + BeforeSaveCallback nameSetterMutable() { + return (aggregate, aggregateChange) -> { + aggregate.setName("fromBeforeSaveCallback"); + return aggregate; + }; + } + + @Bean + BeforeSaveCallback nameSetterMutableWithImmutableId() { + return (aggregate, aggregateChange) -> { + aggregate.setName("fromBeforeSaveCallback"); + return aggregate; + }; + } + + @Bean + BeforeSaveCallback nameSetterImmutableWithMutableId() { + return (aggregate, aggregateChange) -> aggregate.withName("fromBeforeSaveCallback"); + } + } +} diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/ImmutableAggregateTemplateHsqlIntegrationTests-hsql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/ImmutableAggregateTemplateHsqlIntegrationTests-hsql.sql index 42059f7663..fba412c6f2 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/ImmutableAggregateTemplateHsqlIntegrationTests-hsql.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/ImmutableAggregateTemplateHsqlIntegrationTests-hsql.sql @@ -29,3 +29,16 @@ CREATE TABLE WITH_COPY_CONSTRUCTOR ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY, NAME VARCHAR(30) ); + +CREATE TABLE ROOT +( + ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY, + NAME VARCHAR(30) +); +CREATE TABLE NON_ROOT +( + ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY, + ROOT BIGINT NOT NULL, + NAME VARCHAR(30) +); +ALTER TABLE NON_ROOT ADD FOREIGN KEY (ROOT) REFERENCES ROOT (ID); \ No newline at end of file diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryBeforeSaveHsqlIntegrationTests-hsql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryBeforeSaveHsqlIntegrationTests-hsql.sql new file mode 100644 index 0000000000..3e8d6ed8c3 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryBeforeSaveHsqlIntegrationTests-hsql.sql @@ -0,0 +1,22 @@ +-- noinspection SqlNoDataSourceInspectionForFile + +CREATE TABLE ImmutableEntity +( + ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY, + NAME VARCHAR(100) +); +CREATE TABLE MutableEntity +( + ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY, + NAME VARCHAR(100) +); +CREATE TABLE MutableWithImmutableIdEntity +( + ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY, + NAME VARCHAR(100) +); +CREATE TABLE ImmutableWithMutableIdEntity +( + ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY, + NAME VARCHAR(100) +); diff --git a/spring-data-jdbc/src/test/resources/org/springframework/data/jdbc/mybatis/DummyEntityMapper.xml b/spring-data-jdbc/src/test/resources/org/springframework/data/jdbc/mybatis/DummyEntityMapper.xml index 3cc47c485b..a123b05ac1 100644 --- a/spring-data-jdbc/src/test/resources/org/springframework/data/jdbc/mybatis/DummyEntityMapper.xml +++ b/spring-data-jdbc/src/test/resources/org/springframework/data/jdbc/mybatis/DummyEntityMapper.xml @@ -9,7 +9,7 @@ - + INSERT INTO DummyEntity (id) VALUES (DEFAULT) diff --git a/spring-data-relational/pom.xml b/spring-data-relational/pom.xml index 717ac86edf..6b10340c97 100644 --- a/spring-data-relational/pom.xml +++ b/spring-data-relational/pom.xml @@ -6,7 +6,7 @@ 4.0.0 spring-data-relational - 3.0.0-SNAPSHOT + 3.0.0-1201-update-return-value-SNAPSHOT Spring Data Relational Spring Data Relational support @@ -14,7 +14,7 @@ org.springframework.data spring-data-relational-parent - 3.0.0-SNAPSHOT + 3.0.0-1201-update-return-value-SNAPSHOT diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/AggregateChange.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/AggregateChange.java index 6afe39d55a..b392a175b0 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/AggregateChange.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/AggregateChange.java @@ -17,13 +17,12 @@ import java.util.function.Consumer; -import org.springframework.lang.Nullable; - /** * Represents the change happening to the aggregate (as used in the context of Domain Driven Design) as a whole. * * @author Jens Schauder * @author Mark Paluch + * @author Chirag Tailor */ public interface AggregateChange { @@ -41,14 +40,6 @@ public interface AggregateChange { */ Class getEntityType(); - /** - * The entity to which this {@link AggregateChange} relates. - * - * @return may be {@literal null}. - */ - @Nullable - T getEntity(); - /** * Applies the given consumer to each {@link DbAction} in this {@code AggregateChange}. * diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/AggregateChangeWithRoot.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/AggregateChangeWithRoot.java new file mode 100644 index 0000000000..e5f0d5afa4 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/AggregateChangeWithRoot.java @@ -0,0 +1,44 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.relational.core.conversion; + +/** + * Represents the change happening to the aggregate (as used in the context of Domain Driven Design) as a whole. + * + * @author Chirag Tailor + * @since 2.6 + */ +public interface AggregateChangeWithRoot extends MutableAggregateChange { + + /** + * The root object to which this {@link AggregateChange} relates. Guaranteed to be not {@code null}. + */ + T getRoot(); + + /** + * Set the root object of the {@code AggregateChange}. + * + * @param aggregateRoot must not be {@literal null}. + */ + void setRoot(T aggregateRoot); + + /** + * Sets the action for the root object of this {@code AggregateChange}. + * + * @param action must not be {@literal null}. + */ + void setRootAction(DbAction.WithRoot action); +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DbAction.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DbAction.java index d2701ed4a3..1d340c25d2 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DbAction.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DbAction.java @@ -46,7 +46,7 @@ public interface DbAction { * * @param type of the entity for which this represents a database interaction. */ - class Insert implements WithGeneratedId, WithDependingOn { + class Insert implements WithDependingOn { private final T entity; private final PersistentPropertyPath propertyPath; @@ -104,9 +104,9 @@ public String toString() { * * @param type of the entity for which this represents a database interaction. */ - class InsertRoot implements WithGeneratedId { + class InsertRoot implements WithRoot { - private final T entity; + private T entity; private final IdValueSource idValueSource; public InsertRoot(T entity, IdValueSource idValueSource) { @@ -119,41 +119,18 @@ public T getEntity() { return this.entity; } - public IdValueSource getIdValueSource() { - return idValueSource; - } - @Override - public String toString() { - return "InsertRoot{" + "entity=" + entity + ", idValueSource=" + idValueSource + '}'; - } - } - - /** - * Represents an update statement for a single entity that is not the root of an aggregate. - * - * @param type of the entity for which this represents a database interaction. - */ - final class Update implements WithEntity { - - private final T entity; - private final PersistentPropertyPath propertyPath; - - public Update(T entity, PersistentPropertyPath propertyPath) { + public void setEntity(T entity) { this.entity = entity; - this.propertyPath = propertyPath; } - public T getEntity() { - return this.entity; - } - - public PersistentPropertyPath getPropertyPath() { - return this.propertyPath; + public IdValueSource getIdValueSource() { + return idValueSource; } + @Override public String toString() { - return "DbAction.Update(entity=" + this.getEntity() + ", propertyPath=" + this.getPropertyPath() + ")"; + return "InsertRoot{" + "entity=" + entity + ", idValueSource=" + idValueSource + '}'; } } @@ -162,9 +139,9 @@ public String toString() { * * @param type of the entity for which this represents a database interaction. */ - class UpdateRoot implements WithEntity { + class UpdateRoot implements WithRoot { - private final T entity; + private T entity; @Nullable private final Number previousVersion; @@ -177,6 +154,16 @@ public T getEntity() { return this.entity; } + @Override + public void setEntity(T entity) { + this.entity = entity; + } + + @Override + public IdValueSource getIdValueSource() { + return IdValueSource.PROVIDED; + } + @Nullable public Number getPreviousVersion() { return previousVersion; @@ -448,6 +435,7 @@ default Class getEntityType() { * A {@link DbAction} that stores the information of a single entity in the database. * * @author Jens Schauder + * @author Chirag Tailor */ interface WithEntity extends DbAction { @@ -461,21 +449,23 @@ interface WithEntity extends DbAction { default Class getEntityType() { return (Class) getEntity().getClass(); } + + /** + * @return the {@link IdValueSource} for the entity to persist. Guaranteed to be not {@code null}. + */ + IdValueSource getIdValueSource(); } /** - * A {@link DbAction} that may "update" its entity. In order to support immutable entities this requires at least - * potentially creating a new instance, which this interface makes available. + * A {@link DbAction} pertaining to the root on an aggregate. * - * @author Jens Schauder + * @author Chirag Tailor */ - interface WithGeneratedId extends WithEntity { - - @SuppressWarnings("unchecked") - @Override - default Class getEntityType() { - return (Class) getEntity().getClass(); - } + interface WithRoot extends WithEntity { + /** + * @param entity the entity to persist. Must not be {@code null}. + */ + void setEntity(T entity); } /** diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DbActionExecutionResult.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DbActionExecutionResult.java index b65165950a..a7a5da32ff 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DbActionExecutionResult.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DbActionExecutionResult.java @@ -19,31 +19,38 @@ /** * @author Jens Schauder + * @author Chirag Tailor * @since 2.0 */ public class DbActionExecutionResult { - private final Object id; - private final DbAction action; + private final Object generatedId; + private final DbAction.WithEntity action; - public DbActionExecutionResult(DbAction action, @Nullable Object newId) { + public DbActionExecutionResult(DbAction.WithEntity action) { this.action = action; - this.id = newId; + this.generatedId = null; + } + + public DbActionExecutionResult(DbAction.WithEntity action, @Nullable Object generatedId) { + + this.action = action; + this.generatedId = generatedId; } public DbActionExecutionResult() { action = null; - id = null; + generatedId = null; } @Nullable - public Object getId() { - return id; + public Object getGeneratedId() { + return generatedId; } - public DbAction getAction() { + public DbAction.WithEntity getAction() { return action; } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DefaultAggregateChange.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DefaultAggregateChange.java index 9c8bdc8faa..990d8849ca 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DefaultAggregateChange.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DefaultAggregateChange.java @@ -39,17 +39,13 @@ class DefaultAggregateChange implements MutableAggregateChange { private final List> actions = new ArrayList<>(); - /** Aggregate root, to which the change applies, if available */ - private @Nullable T entity; - /** The previous version assigned to the instance being changed, if available */ @Nullable private final Number previousVersion; - public DefaultAggregateChange(Kind kind, Class entityType, @Nullable T entity, @Nullable Number previousVersion) { + public DefaultAggregateChange(Kind kind, Class entityType, @Nullable Number previousVersion) { this.kind = kind; this.entityType = entityType; - this.entity = entity; this.previousVersion = previousVersion; } @@ -76,33 +72,12 @@ public Class getEntityType() { return this.entityType; } - /** - * Set the root object of the {@code AggregateChange}. - * - * @param aggregateRoot may be {@literal null} if the change refers to a list of aggregates or references it by id. - */ - @Override - public void setEntity(@Nullable T aggregateRoot) { - - if (aggregateRoot != null) { - Assert.isInstanceOf(this.entityType, aggregateRoot, - String.format("AggregateRoot must be of type %s", entityType.getName())); - } - - this.entity = aggregateRoot; - } - @Nullable @Override public Number getPreviousVersion() { return previousVersion; } - @Override - public T getEntity() { - return this.entity; - } - @Override public void forEachAction(Consumer> consumer) { diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DefaultAggregateChangeWithRoot.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DefaultAggregateChangeWithRoot.java new file mode 100644 index 0000000000..60b481469e --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DefaultAggregateChangeWithRoot.java @@ -0,0 +1,128 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.relational.core.conversion; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * Represents the change happening to the aggregate (as used in the context of Domain Driven Design) as a whole. + * + * @author Chirag Tailor + * @since 2.6 + */ +class DefaultAggregateChangeWithRoot implements AggregateChangeWithRoot { + + private final Kind kind; + + /** Type of the aggregate root to be changed */ + private final Class entityType; + + private final List> actions = new ArrayList<>(); + + private DbAction.WithRoot rootAction; + + /** The previous version assigned to the instance being changed, if available */ + @Nullable private final Number previousVersion; + + public DefaultAggregateChangeWithRoot(Kind kind, Class entityType, @Nullable Number previousVersion) { + + this.kind = kind; + this.entityType = entityType; + this.previousVersion = previousVersion; + } + + /** + * Adds an action to this {@code AggregateChange}. + * + * @param action must not be {@literal null}. + */ + @Override + public void addAction(DbAction action) { + + Assert.notNull(action, "Action must not be null."); + + actions.add(action); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.conversion.AggregateChange#getKind() + */ + @Override + public Kind getKind() { + return this.kind; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.conversion.AggregateChange#getEntityType() + */ + @Override + public Class getEntityType() { + return this.entityType; + } + + @Override + public void setRoot(T aggregateRoot) { + + Assert.isInstanceOf(this.entityType, aggregateRoot, + String.format("AggregateRoot must be of type %s", entityType.getName())); + + rootAction.setEntity(aggregateRoot); + } + + @Override + public void setRootAction(DbAction.WithRoot action) { + + Assert.isNull(this.rootAction, "The rootAction must be set exactly once"); + + this.rootAction = action; + } + + @Nullable + @Override + public Number getPreviousVersion() { + return previousVersion; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.conversion.AggregateChange#getEntity() + */ + @Override + public T getRoot() { + return this.rootAction.getEntity(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.conversion.AggregateChange#forEachAction(java.util.function.Consumer) + */ + @Override + public void forEachAction(Consumer> consumer) { + + Assert.notNull(consumer, "Consumer must not be null."); + Assert.notNull(rootAction, "DbAction.WithRoot must not be null."); + + consumer.accept(rootAction); + actions.forEach(consumer); + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MutableAggregateChange.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MutableAggregateChange.java index 79d1f6fc9b..a76d17ab7a 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MutableAggregateChange.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MutableAggregateChange.java @@ -30,32 +30,32 @@ public interface MutableAggregateChange extends AggregateChange { /** - * Factory method to create an {@link MutableAggregateChange} for saving entities. + * Factory method to create an {@link AggregateChangeWithRoot} for saving entities. * * @param entity aggregate root to save. * @param entity type. - * @return the {@link MutableAggregateChange} for saving the root {@code entity}. + * @return the {@link AggregateChangeWithRoot} for saving the root {@code entity}. * @since 1.2 */ - static MutableAggregateChange forSave(T entity) { + static AggregateChangeWithRoot forSave(T entity) { return forSave(entity, null); } /** - * Factory method to create an {@link MutableAggregateChange} for saving entities. + * Factory method to create an {@link AggregateChangeWithRoot} for saving entities. * * @param entity aggregate root to save. * @param previousVersion the previous version assigned to the instance being saved. May be {@literal null}. * @param entity type. - * @return the {@link MutableAggregateChange} for saving the root {@code entity}. + * @return the {@link AggregateChangeWithRoot} for saving the root {@code entity}. * @since 2.4 */ @SuppressWarnings("unchecked") - static MutableAggregateChange forSave(T entity, @Nullable Number previousVersion) { + static AggregateChangeWithRoot forSave(T entity, @Nullable Number previousVersion) { Assert.notNull(entity, "Entity must not be null"); - return new DefaultAggregateChange<>(Kind.SAVE, (Class) ClassUtils.getUserClass(entity), entity, previousVersion); + return new DefaultAggregateChangeWithRoot<>(Kind.SAVE, (Class) ClassUtils.getUserClass(entity), previousVersion); } /** @@ -71,37 +71,35 @@ static MutableAggregateChange forDelete(T entity) { Assert.notNull(entity, "Entity must not be null"); - return forDelete((Class) ClassUtils.getUserClass(entity), entity); + return forDelete((Class) ClassUtils.getUserClass(entity)); } /** * Factory method to create an {@link MutableAggregateChange} for deleting entities. * * @param entityClass aggregate root type. - * @param entity aggregate root to delete. * @param entity type. * @return the {@link MutableAggregateChange} for deleting the root {@code entity}. * @since 1.2 */ - static MutableAggregateChange forDelete(Class entityClass, @Nullable T entity) { - return forDelete(entityClass, entity, null); + static MutableAggregateChange forDelete(Class entityClass) { + return forDelete(entityClass, null); } /** * Factory method to create an {@link MutableAggregateChange} for deleting entities. * * @param entityClass aggregate root type. - * @param entity aggregate root to delete. * @param previousVersion the previous version assigned to the instance being saved. May be {@literal null}. * @param entity type. * @return the {@link MutableAggregateChange} for deleting the root {@code entity}. * @since 2.4 */ - static MutableAggregateChange forDelete(Class entityClass, @Nullable T entity, @Nullable Number previousVersion) { + static MutableAggregateChange forDelete(Class entityClass, @Nullable Number previousVersion) { Assert.notNull(entityClass, "Entity class must not be null"); - return new DefaultAggregateChange<>(Kind.DELETE, entityClass, entity, previousVersion); + return new DefaultAggregateChange<>(Kind.DELETE, entityClass, previousVersion); } /** @@ -111,13 +109,6 @@ static MutableAggregateChange forDelete(Class entityClass, @Nullable T */ void addAction(DbAction action); - /** - * Set the root object of the {@code AggregateChange}. - * - * @param aggregateRoot may be {@literal null} if the change refers to a list of aggregates or references it by id. - */ - void setEntity(@Nullable T aggregateRoot); - @Nullable Number getPreviousVersion(); } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityInsertWriter.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityInsertWriter.java index a13932f39f..0022379db6 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityInsertWriter.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityInsertWriter.java @@ -15,20 +15,19 @@ */ package org.springframework.data.relational.core.conversion; -import java.util.List; - import org.springframework.data.convert.EntityWriter; import org.springframework.data.relational.core.mapping.RelationalMappingContext; /** - * Converts an aggregate represented by its root into an {@link MutableAggregateChange}. Does not perform any isNew + * Converts an aggregate represented by its root into an {@link AggregateChangeWithRoot}. Does not perform any isNew * check. * * @author Thomas Lang * @author Jens Schauder + * @author Chirag Tailor * @since 1.1 */ -public class RelationalEntityInsertWriter implements EntityWriter> { +public class RelationalEntityInsertWriter implements EntityWriter> { private final RelationalMappingContext context; @@ -37,9 +36,7 @@ public RelationalEntityInsertWriter(RelationalMappingContext context) { } @Override - public void write(Object root, MutableAggregateChange aggregateChange) { - - List> actions = new WritingContext(context, root, aggregateChange).insert(); - actions.forEach(aggregateChange::addAction); + public void write(T root, AggregateChangeWithRoot aggregateChange) { + new WritingContext<>(context, root, aggregateChange).insert(); } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityUpdateWriter.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityUpdateWriter.java index fb62ad69d2..4f9ebd1b87 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityUpdateWriter.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityUpdateWriter.java @@ -15,20 +15,19 @@ */ package org.springframework.data.relational.core.conversion; -import java.util.List; - import org.springframework.data.convert.EntityWriter; import org.springframework.data.relational.core.mapping.RelationalMappingContext; /** - * Converts an aggregate represented by its root into an {@link MutableAggregateChange}. Does not perform any isNew + * Converts an aggregate represented by its root into an {@link AggregateChangeWithRoot}. Does not perform any isNew * check. * * @author Thomas Lang * @author Jens Schauder + * @author Chirag Tailor * @since 1.1 */ -public class RelationalEntityUpdateWriter implements EntityWriter> { +public class RelationalEntityUpdateWriter implements EntityWriter> { private final RelationalMappingContext context; @@ -37,9 +36,7 @@ public RelationalEntityUpdateWriter(RelationalMappingContext context) { } @Override - public void write(Object root, MutableAggregateChange aggregateChange) { - - List> actions = new WritingContext(context, root, aggregateChange).update(); - actions.forEach(aggregateChange::addAction); + public void write(T root, AggregateChangeWithRoot aggregateChange) { + new WritingContext<>(context, root, aggregateChange).update(); } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityWriter.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityWriter.java index 3d917a3d74..b551a21f09 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityWriter.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityWriter.java @@ -15,18 +15,17 @@ */ package org.springframework.data.relational.core.conversion; -import java.util.List; - import org.springframework.data.convert.EntityWriter; import org.springframework.data.relational.core.mapping.RelationalMappingContext; /** - * Converts an aggregate represented by its root into an {@link MutableAggregateChange}. + * Converts an aggregate represented by its root into an {@link AggregateChangeWithRoot}. * * @author Jens Schauder * @author Mark Paluch + * @author Chirag Tailor */ -public class RelationalEntityWriter implements EntityWriter> { +public class RelationalEntityWriter implements EntityWriter> { private final RelationalMappingContext context; @@ -35,9 +34,7 @@ public RelationalEntityWriter(RelationalMappingContext context) { } @Override - public void write(Object root, MutableAggregateChange aggregateChange) { - - List> actions = new WritingContext(context, root, aggregateChange).save(); - actions.forEach(aggregateChange::addAction); + public void write(T root, AggregateChangeWithRoot aggregateChange) { + new WritingContext<>(context, root, aggregateChange).save(); } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/WritingContext.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/WritingContext.java index 5f936dd751..a453d279fb 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/WritingContext.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/WritingContext.java @@ -42,25 +42,25 @@ * @author Myeonghyeon Lee * @author Chirag Tailor */ -class WritingContext { +class WritingContext { private final RelationalMappingContext context; - private final Object root; - private final Object entity; - private final Class entityType; + private final T root; + private final Class entityType; private final PersistentPropertyPaths paths; private final Map> previousActions = new HashMap<>(); private final Map, List> nodesCache = new HashMap<>(); private final IdValueSource rootIdValueSource; @Nullable private final Number previousVersion; + private final AggregateChangeWithRoot aggregateChange; - WritingContext(RelationalMappingContext context, Object root, MutableAggregateChange aggregateChange) { + WritingContext(RelationalMappingContext context, T root, AggregateChangeWithRoot aggregateChange) { this.context = context; this.root = root; - this.entity = aggregateChange.getEntity(); this.entityType = aggregateChange.getEntityType(); this.previousVersion = aggregateChange.getPreviousVersion(); + this.aggregateChange = aggregateChange; this.rootIdValueSource = IdValueSource.forInstance(root, context.getRequiredPersistentEntity(aggregateChange.getEntityType())); this.paths = context.findPersistentPropertyPaths(entityType, (p) -> p.isEntity() && !p.isEmbedded()); @@ -69,48 +69,39 @@ class WritingContext { /** * Leaves out the isNew check as defined in #DATAJDBC-282 * - * @return List of {@link DbAction}s * @see DAJDBC-282 */ - List> insert() { + void insert() { - List> actions = new ArrayList<>(); - actions.add(setRootAction(new DbAction.InsertRoot<>(entity, rootIdValueSource))); - actions.addAll(insertReferenced()); - return actions; + setRootAction(new DbAction.InsertRoot<>(root, rootIdValueSource)); + insertReferenced().forEach(aggregateChange::addAction); } /** * Leaves out the isNew check as defined in #DATAJDBC-282 Possible Deadlocks in Execution Order in #DATAJDBC-488 * - * @return List of {@link DbAction}s * @see DAJDBC-282 * @see DAJDBC-488 */ - List> update() { + void update() { - List> actions = new ArrayList<>(); - actions.add(setRootAction(new DbAction.UpdateRoot<>(entity, previousVersion))); - actions.addAll(deleteReferenced()); - actions.addAll(insertReferenced()); - return actions; + setRootAction(new DbAction.UpdateRoot<>(root, previousVersion)); + deleteReferenced().forEach(aggregateChange::addAction); + insertReferenced().forEach(aggregateChange::addAction); } - List> save() { + void save() { - List> actions = new ArrayList<>(); if (isNew(root)) { - actions.add(setRootAction(new DbAction.InsertRoot<>(entity, rootIdValueSource))); - actions.addAll(insertReferenced()); + setRootAction(new DbAction.InsertRoot<>(root, rootIdValueSource)); + insertReferenced().forEach(aggregateChange::addAction); } else { - actions.add(setRootAction(new DbAction.UpdateRoot<>(entity, previousVersion))); - actions.addAll(deleteReferenced()); - actions.addAll(insertReferenced()); + setRootAction(new DbAction.UpdateRoot<>(root, previousVersion)); + deleteReferenced().forEach(aggregateChange::addAction); + insertReferenced().forEach(aggregateChange::addAction); } - - return actions; } private boolean isNew(Object o) { @@ -181,17 +172,16 @@ private List> deleteReferenced() { private DbAction.Delete deleteReferenced(PersistentPropertyPath path) { - Object id = context.getRequiredPersistentEntity(entityType).getIdentifierAccessor(entity).getIdentifier(); + Object id = context.getRequiredPersistentEntity(entityType).getIdentifierAccessor(root).getIdentifier(); return new DbAction.Delete<>(id, path); } //// methods not directly related to the creation of DbActions - private DbAction setRootAction(DbAction dbAction) { - + private void setRootAction(DbAction.WithRoot dbAction) { + aggregateChange.setRootAction(dbAction); previousActions.put(null, dbAction); - return dbAction; } @Nullable @@ -263,7 +253,7 @@ private boolean isDirectlyReferencedByRootIgnoringEmbeddables( private Object getFromRootValue(PersistentPropertyPath path) { if (path.getLength() == 0) { - return entity; + return root; } Object parent = getFromRootValue(path.getParentPath()); diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityDeleteWriterUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityDeleteWriterUnitTests.java index 42dae35f2f..e2d6bd3a53 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityDeleteWriterUnitTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityDeleteWriterUnitTests.java @@ -50,7 +50,7 @@ public void deleteDeletesTheEntityAndReferencedEntities() { SomeEntity entity = new SomeEntity(23L); - MutableAggregateChange aggregateChange = MutableAggregateChange.forDelete(SomeEntity.class, entity); + MutableAggregateChange aggregateChange = MutableAggregateChange.forDelete(SomeEntity.class); converter.write(entity.id, aggregateChange); @@ -69,7 +69,7 @@ public void deleteDeletesTheEntityAndNoReferencedEntities() { SingleEntity entity = new SingleEntity(23L); - MutableAggregateChange aggregateChange = MutableAggregateChange.forDelete(SingleEntity.class, entity); + MutableAggregateChange aggregateChange = MutableAggregateChange.forDelete(SingleEntity.class); converter.write(entity.id, aggregateChange); @@ -81,7 +81,7 @@ public void deleteDeletesTheEntityAndNoReferencedEntities() { @Test // DATAJDBC-188 public void deleteAllDeletesAllEntitiesAndReferencedEntities() { - MutableAggregateChange aggregateChange = MutableAggregateChange.forDelete(SomeEntity.class, null); + MutableAggregateChange aggregateChange = MutableAggregateChange.forDelete(SomeEntity.class); converter.write(null, aggregateChange); @@ -98,7 +98,7 @@ public void deleteAllDeletesAllEntitiesAndReferencedEntities() { @Test // DATAJDBC-493 public void deleteAllDeletesAllEntitiesAndNoReferencedEntities() { - MutableAggregateChange aggregateChange = MutableAggregateChange.forDelete(SingleEntity.class, null); + MutableAggregateChange aggregateChange = MutableAggregateChange.forDelete(SingleEntity.class); converter.write(null, aggregateChange); diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityInsertWriterUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityInsertWriterUnitTests.java index 1615b32662..bfba7e0e33 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityInsertWriterUnitTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityInsertWriterUnitTests.java @@ -38,16 +38,15 @@ public class RelationalEntityInsertWriterUnitTests { public static final long SOME_ENTITY_ID = 23L; - RelationalEntityInsertWriter converter = new RelationalEntityInsertWriter(new RelationalMappingContext()); + RelationalMappingContext context = new RelationalMappingContext(); @Test // DATAJDBC-112 public void newEntityGetsConvertedToOneInsert() { SingleReferenceEntity entity = new SingleReferenceEntity(null); - MutableAggregateChange aggregateChange = // - MutableAggregateChange.forSave(entity); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity); - converter.write(entity, aggregateChange); + new RelationalEntityInsertWriter(context).write(entity, aggregateChange); assertThat(extractActions(aggregateChange)) // .extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath, @@ -62,10 +61,9 @@ public void existingEntityGetsNotConvertedToDeletePlusUpdate() { SingleReferenceEntity entity = new SingleReferenceEntity(SOME_ENTITY_ID); - MutableAggregateChange aggregateChange = // - MutableAggregateChange.forSave(entity); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity); - converter.write(entity, aggregateChange); + new RelationalEntityInsertWriter(context).write(entity, aggregateChange); assertThat(extractActions(aggregateChange)) // .extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath, diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityUpdateWriterUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityUpdateWriterUnitTests.java index 0c1443e647..a7e0342f0a 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityUpdateWriterUnitTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityUpdateWriterUnitTests.java @@ -38,16 +38,16 @@ public class RelationalEntityUpdateWriterUnitTests { public static final long SOME_ENTITY_ID = 23L; - RelationalEntityUpdateWriter converter = new RelationalEntityUpdateWriter(new RelationalMappingContext()); + private RelationalMappingContext context = new RelationalMappingContext(); @Test // DATAJDBC-112 public void existingEntityGetsConvertedToDeletePlusUpdate() { SingleReferenceEntity entity = new SingleReferenceEntity(SOME_ENTITY_ID); - MutableAggregateChange aggregateChange = MutableAggregateChange.forSave(entity); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity); - converter.write(entity, aggregateChange); + new RelationalEntityUpdateWriter(context).write(entity, aggregateChange); assertThat(extractActions(aggregateChange)) // .extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath, diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityWriterUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityWriterUnitTests.java index bb449fe4c3..18b709c48f 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityWriterUnitTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityWriterUnitTests.java @@ -60,7 +60,6 @@ public class RelationalEntityWriterUnitTests { static final long SOME_ENTITY_ID = 23L; final RelationalMappingContext context = new RelationalMappingContext(); - final RelationalEntityWriter converter = new RelationalEntityWriter(context); final PersistentPropertyPath listContainerElements = toPath("elements", ListContainer.class, context); @@ -84,10 +83,9 @@ public class RelationalEntityWriterUnitTests { public void newEntityGetsConvertedToOneInsert() { SingleReferenceEntity entity = new SingleReferenceEntity(null); - MutableAggregateChange aggregateChange = // - new DefaultAggregateChange<>(AggregateChange.Kind.SAVE, SingleReferenceEntity.class, entity, null); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity); - converter.write(entity, aggregateChange); + new RelationalEntityWriter(context).write(entity, aggregateChange); assertThat(extractActions(aggregateChange)) // .extracting(DbAction::getClass, // @@ -106,10 +104,9 @@ public void newEntityGetsConvertedToOneInsert() { void newEntityWithPrimitiveLongId_insertDoesNotIncludeId_whenIdValueIsZero() { PrimitiveLongIdEntity entity = new PrimitiveLongIdEntity(); - MutableAggregateChange aggregateChange = // - new DefaultAggregateChange<>(AggregateChange.Kind.SAVE, PrimitiveLongIdEntity.class, entity, null); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity); - converter.write(entity, aggregateChange); + new RelationalEntityWriter(context).write(entity, aggregateChange); assertThat(extractActions(aggregateChange)) // .extracting(DbAction::getClass, // @@ -128,10 +125,9 @@ void newEntityWithPrimitiveLongId_insertDoesNotIncludeId_whenIdValueIsZero() { void newEntityWithPrimitiveIntId_insertDoesNotIncludeId_whenIdValueIsZero() { PrimitiveIntIdEntity entity = new PrimitiveIntIdEntity(); - MutableAggregateChange aggregateChange = // - new DefaultAggregateChange<>(AggregateChange.Kind.SAVE, PrimitiveIntIdEntity.class, entity, null); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity); - converter.write(entity, aggregateChange); + new RelationalEntityWriter(context).write(entity, aggregateChange); assertThat(extractActions(aggregateChange)) // .extracting(DbAction::getClass, // @@ -152,10 +148,9 @@ public void newEntityGetsConvertedToOneInsertByEmbeddedEntities() { EmbeddedReferenceEntity entity = new EmbeddedReferenceEntity(null); entity.other = new Element(2L); - MutableAggregateChange aggregateChange = // - new DefaultAggregateChange<>(AggregateChange.Kind.SAVE, EmbeddedReferenceEntity.class, entity, null); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity); - converter.write(entity, aggregateChange); + new RelationalEntityWriter(context).write(entity, aggregateChange); assertThat(extractActions(aggregateChange)) // .extracting(DbAction::getClass, // @@ -176,10 +171,9 @@ public void newEntityWithReferenceGetsConvertedToTwoInserts() { SingleReferenceEntity entity = new SingleReferenceEntity(null); entity.other = new Element(null); - MutableAggregateChange aggregateChange = // - new DefaultAggregateChange<>(AggregateChange.Kind.SAVE, SingleReferenceEntity.class, entity, null); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity); - converter.write(entity, aggregateChange); + new RelationalEntityWriter(context).write(entity, aggregateChange); assertThat(extractActions(aggregateChange)) // .extracting(DbAction::getClass, // @@ -202,10 +196,10 @@ void newEntityWithReference_whenReferenceHasPrimitiveId_insertDoesNotIncludeId_w entity.primitiveLongIdEntity = new PrimitiveLongIdEntity(); entity.primitiveIntIdEntity = new PrimitiveIntIdEntity(); - MutableAggregateChange aggregateChange = // - new DefaultAggregateChange<>(AggregateChange.Kind.SAVE, EntityWithReferencesToPrimitiveIdEntity.class, entity, null); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange + .forSave(entity); - converter.write(entity, aggregateChange); + new RelationalEntityWriter(context).write(entity, aggregateChange); assertThat(extractActions(aggregateChange)) // .extracting(DbAction::getClass, // @@ -229,10 +223,9 @@ public void existingEntityGetsConvertedToDeletePlusUpdate() { SingleReferenceEntity entity = new SingleReferenceEntity(SOME_ENTITY_ID); - MutableAggregateChange aggregateChange = // - new DefaultAggregateChange<>(AggregateChange.Kind.SAVE, SingleReferenceEntity.class, entity, 1L); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity, 1L); - converter.write(entity, aggregateChange); + new RelationalEntityWriter(context).write(entity, aggregateChange); assertThat(extractActions(aggregateChange)) // .extracting(DbAction::getClass, // @@ -253,10 +246,9 @@ public void newReferenceTriggersDeletePlusInsert() { SingleReferenceEntity entity = new SingleReferenceEntity(SOME_ENTITY_ID); entity.other = new Element(null); - MutableAggregateChange aggregateChange = new DefaultAggregateChange<>( - AggregateChange.Kind.SAVE, SingleReferenceEntity.class, entity, 1L); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity, 1L); - converter.write(entity, aggregateChange); + new RelationalEntityWriter(context).write(entity, aggregateChange); assertThat(extractActions(aggregateChange)) // .extracting(DbAction::getClass, // @@ -276,10 +268,9 @@ public void newReferenceTriggersDeletePlusInsert() { public void newEntityWithEmptySetResultsInSingleInsert() { SetContainer entity = new SetContainer(null); - MutableAggregateChange aggregateChange = new DefaultAggregateChange<>(AggregateChange.Kind.SAVE, - SetContainer.class, entity, null); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity); - converter.write(entity, aggregateChange); + new RelationalEntityWriter(context).write(entity, aggregateChange); assertThat(extractActions(aggregateChange)) // .extracting(DbAction::getClass, // @@ -299,9 +290,8 @@ public void newEntityWithSetContainingMultipleElementsResultsInAnInsertForTheBat entity.elements.add(new Element(null)); entity.elements.add(new Element(null)); - MutableAggregateChange aggregateChange = new DefaultAggregateChange<>(AggregateChange.Kind.SAVE, - SetContainer.class, entity, null); - converter.write(entity, aggregateChange); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity); + new RelationalEntityWriter(context).write(entity, aggregateChange); List> actions = extractActions(aggregateChange); assertThat(actions).extracting(DbAction::getClass, // @@ -342,10 +332,9 @@ public void cascadingReferencesTriggerCascadingActions() { new Element(null)) // ); - MutableAggregateChange aggregateChange = new DefaultAggregateChange<>( - AggregateChange.Kind.SAVE, CascadingReferenceEntity.class, entity, null); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity); - converter.write(entity, aggregateChange); + new RelationalEntityWriter(context).write(entity, aggregateChange); List> actions = extractActions(aggregateChange); assertThat(actions).extracting(DbAction::getClass, // @@ -404,10 +393,9 @@ public void cascadingReferencesTriggerCascadingActionsForUpdate() { new Element(null)) // ); - MutableAggregateChange aggregateChange = new DefaultAggregateChange<>( - AggregateChange.Kind.SAVE, CascadingReferenceEntity.class, entity, 1L); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity, 1L); - converter.write(entity, aggregateChange); + new RelationalEntityWriter(context).write(entity, aggregateChange); List> actions = extractActions(aggregateChange); assertThat(actions).extracting(DbAction::getClass, // @@ -456,10 +444,9 @@ public void cascadingReferencesTriggerCascadingActionsForUpdate() { public void newEntityWithEmptyMapResultsInSingleInsert() { MapContainer entity = new MapContainer(null); - MutableAggregateChange aggregateChange = new DefaultAggregateChange<>(AggregateChange.Kind.SAVE, - MapContainer.class, entity, null); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity); - converter.write(entity, aggregateChange); + new RelationalEntityWriter(context).write(entity, aggregateChange); assertThat(extractActions(aggregateChange)).extracting(DbAction::getClass, // DbAction::getEntityType, // @@ -476,9 +463,8 @@ public void newEntityWithMapResultsInAdditionalInsertPerElement() { entity.elements.put("one", new Element(null)); entity.elements.put("two", new Element(null)); - MutableAggregateChange aggregateChange = new DefaultAggregateChange<>(AggregateChange.Kind.SAVE, - MapContainer.class, entity, null); - converter.write(entity, aggregateChange); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity); + new RelationalEntityWriter(context).write(entity, aggregateChange); List> actions = extractActions(aggregateChange); assertThat(actions).extracting(DbAction::getClass, // @@ -520,9 +506,8 @@ public void newEntityWithFullMapResultsInAdditionalInsertPerElement() { entity.elements.put("a", new Element(null)); entity.elements.put("b", new Element(null)); - MutableAggregateChange aggregateChange = new DefaultAggregateChange<>(AggregateChange.Kind.SAVE, - MapContainer.class, entity, null); - converter.write(entity, aggregateChange); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity); + new RelationalEntityWriter(context).write(entity, aggregateChange); List> actions = extractActions(aggregateChange); assertThat(actions).extracting(DbAction::getClass, // @@ -560,10 +545,9 @@ public void newEntityWithFullMapResultsInAdditionalInsertPerElement() { public void newEntityWithEmptyListResultsInSingleInsert() { ListContainer entity = new ListContainer(null); - MutableAggregateChange aggregateChange = new DefaultAggregateChange<>(AggregateChange.Kind.SAVE, - ListContainer.class, entity, null); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity); - converter.write(entity, aggregateChange); + new RelationalEntityWriter(context).write(entity, aggregateChange); assertThat(extractActions(aggregateChange)).extracting(DbAction::getClass, // DbAction::getEntityType, // @@ -580,9 +564,8 @@ public void newEntityWithListResultsInAdditionalInsertPerElement() { entity.elements.add(new Element(null)); entity.elements.add(new Element(null)); - MutableAggregateChange aggregateChange = new DefaultAggregateChange<>(AggregateChange.Kind.SAVE, - ListContainer.class, entity, null); - converter.write(entity, aggregateChange); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity); + new RelationalEntityWriter(context).write(entity, aggregateChange); List> actions = extractActions(aggregateChange); assertThat(actions).extracting(DbAction::getClass, // @@ -612,10 +595,9 @@ public void mapTriggersDeletePlusInsert() { MapContainer entity = new MapContainer(SOME_ENTITY_ID); entity.elements.put("one", new Element(null)); - MutableAggregateChange aggregateChange = new DefaultAggregateChange<>(AggregateChange.Kind.SAVE, - MapContainer.class, entity, 1L); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity, 1L); - converter.write(entity, aggregateChange); + new RelationalEntityWriter(context).write(entity, aggregateChange); assertThat(extractActions(aggregateChange)) // .extracting(DbAction::getClass, // @@ -636,10 +618,9 @@ public void listTriggersDeletePlusInsert() { ListContainer entity = new ListContainer(SOME_ENTITY_ID); entity.elements.add(new Element(null)); - MutableAggregateChange aggregateChange = new DefaultAggregateChange<>(AggregateChange.Kind.SAVE, - ListContainer.class, entity, 1L); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity, 1L); - converter.write(entity, aggregateChange); + new RelationalEntityWriter(context).write(entity, aggregateChange); assertThat(extractActions(aggregateChange)) // .extracting(DbAction::getClass, // @@ -661,10 +642,9 @@ public void multiLevelQualifiedReferencesWithId() { listMapContainer.maps.add(new MapContainer(SOME_ENTITY_ID)); listMapContainer.maps.get(0).elements.put("one", new Element(null)); - MutableAggregateChange aggregateChange = new DefaultAggregateChange<>(AggregateChange.Kind.SAVE, - ListMapContainer.class, listMapContainer, 1L); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(listMapContainer, 1L); - converter.write(listMapContainer, aggregateChange); + new RelationalEntityWriter(context).write(listMapContainer, aggregateChange); assertThat(extractActions(aggregateChange)) // .extracting(DbAction::getClass, // @@ -689,10 +669,9 @@ public void multiLevelQualifiedReferencesWithOutId() { listMapContainer.maps.add(new NoIdMapContainer()); listMapContainer.maps.get(0).elements.put("one", new NoIdElement()); - MutableAggregateChange aggregateChange = new DefaultAggregateChange<>( - AggregateChange.Kind.SAVE, NoIdListMapContainer.class, listMapContainer, 1L); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(listMapContainer, 1L); - converter.write(listMapContainer, aggregateChange); + new RelationalEntityWriter(context).write(listMapContainer, aggregateChange); assertThat(extractActions(aggregateChange)) // .extracting(DbAction::getClass, // @@ -716,10 +695,9 @@ public void savingANullEmbeddedWithEntity() { EmbeddedReferenceChainEntity entity = new EmbeddedReferenceChainEntity(null); // the embedded is null !!! - MutableAggregateChange aggregateChange = // - new DefaultAggregateChange<>(AggregateChange.Kind.SAVE, EmbeddedReferenceChainEntity.class, entity, null); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity); - converter.write(entity, aggregateChange); + new RelationalEntityWriter(context).write(entity, aggregateChange); assertThat(extractActions(aggregateChange)) // .extracting(DbAction::getClass, // @@ -741,10 +719,10 @@ public void savingInnerNullEmbeddedWithEntity() { root.other = new EmbeddedReferenceChainEntity(null); // the embedded is null !!! - MutableAggregateChange aggregateChange = // - new DefaultAggregateChange<>(AggregateChange.Kind.SAVE, RootWithEmbeddedReferenceChainEntity.class, root, null); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange + .forSave(root); - converter.write(root, aggregateChange); + new RelationalEntityWriter(context).write(root, aggregateChange); assertThat(extractActions(aggregateChange)) // .extracting(DbAction::getClass, // @@ -769,10 +747,9 @@ void newEntityWithCollectionWhereSomeElementsHaveIdSet_producesABatchInsertEachF root.elements.add(new Element(1L)); root.elements.add(new Element(null)); root.elements.add(new Element(2L)); - MutableAggregateChange aggregateChange = // - new DefaultAggregateChange<>(AggregateChange.Kind.SAVE, ListContainer.class, root, null); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(root); - converter.write(root, aggregateChange); + new RelationalEntityWriter(context).write(root, aggregateChange); List> actions = extractActions(aggregateChange); assertThat(actions).extracting(DbAction::getClass, // @@ -817,10 +794,10 @@ void newEntityWithCollection_whenElementHasPrimitiveId_batchInsertDoesNotInclude entity.primitiveLongIdEntities.add(new PrimitiveLongIdEntity()); entity.primitiveIntIdEntities.add(new PrimitiveIntIdEntity()); - MutableAggregateChange aggregateChange = // - new DefaultAggregateChange<>(AggregateChange.Kind.SAVE, EntityWithReferencesToPrimitiveIdEntity.class, entity, null); + AggregateChangeWithRoot aggregateChange = MutableAggregateChange + .forSave(entity); - converter.write(entity, aggregateChange); + new RelationalEntityWriter(context).write(entity, aggregateChange); List> actions = extractActions(aggregateChange); assertThat(actions).extracting(DbAction::getClass, //