Capture execution results for update db actions to fix root references with immutably typed ids.

+ Added #getIdValueSource to DbAction.WithEntity since it now applies to all such DbAction.
+ Removed DbAction.WithGeneratedId in favor of DbAction.WithEntity where it was used.

Removes unused DbAction.Update.

Passes the root from BeforeSaveCallback through to InsertRoot,UpdateRoot
actions.

+ Isolate aggregate change #getEntity #setEntity behaviors into a new
interface for "save" changes.
+ Extract #setEntity from DbAction.WithEntity to a new DbAction interface
just for root actions.
+ JdbcAggregateChangeExecutionContext#populateIdsIfNecessary returns the root entity
from DbAction.InsertRoot or DbAction.UpdateRoot regardless of the immutability of
its @Id property.

Closes #1199
Closes #1201
Original pull request #1208
This commit is contained in:
Chirag Tailor
2022-03-18 15:00:45 -05:00
committed by Jens Schauder
parent 0500442ac3
commit 508791b7df
28 changed files with 772 additions and 404 deletions

View File

@@ -18,10 +18,10 @@ package org.springframework.data.jdbc.core;
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> T execute(AggregateChange<T> aggregateChange) {
<T> T execute(AggregateChangeWithRoot<T> 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;
<T> void execute(AggregateChange<T> aggregateChange) {
JdbcAggregateChangeExecutionContext executionContext = new JdbcAggregateChangeExecutionContext(converter,
accessStrategy);
aggregateChange.forEachAction(action -> execute(action, executionContext));
}
private void execute(DbAction<?> action, JdbcAggregateChangeExecutionContext executionContext) {
@@ -69,8 +71,6 @@ class AggregateChangeExecutor {
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) {

View File

@@ -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.PersistentPropertyPath;
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 @@ class JdbcAggregateChangeExecutionContext {
} else {
updateWithoutVersion(update);
}
}
<T> void executeUpdate(DbAction.Update<T> update) {
if (!accessStrategy.update(update.getEntity(), update.getEntityType())) {
throw new IncorrectUpdateSemanticsDataAccessException(
String.format(UPDATE_FAILED, update.getEntity(), getIdFrom(update)));
}
add(new DbActionExecutionResult(update));
}
<T> void executeDeleteRoot(DbAction.DeleteRoot<T> delete) {
@@ -203,11 +188,12 @@ class JdbcAggregateChangeExecutionContext {
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 @@ class JdbcAggregateChangeExecutionContext {
return identifier;
}
@SuppressWarnings("unchecked")
@Nullable
<T> T populateIdsIfNecessary() {
T newRoot = null;
// have the results so that the inserts on the leaves come first.
List<DbActionExecutionResult> reverseResults = new ArrayList<>(results.values());
Collections.reverse(reverseResults);
@@ -241,33 +223,29 @@ class JdbcAggregateChangeExecutionContext {
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 (newEntity != action.getEntity() && action instanceof DbAction.Insert) {
DbAction.Insert<?> insert = (DbAction.Insert<?>) action;
if (action instanceof DbAction.Insert) {
DbAction.Insert<?> insert = (DbAction.Insert<?>) action;
Pair<?, ?> qualifier = insert.getQualifier();
Pair<?, ?> qualifier = insert.getQualifier();
cascadingValues.stage(insert.getDependingOn(), insert.getPropertyPath(),
qualifier == null ? null : qualifier.getSecond(), newEntity);
} 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 <S> Object setIdAndCascadingProperties(DbAction.WithEntity<S> action, @Nullable Object generatedId,
@@ -279,7 +257,7 @@ class JdbcAggregateChangeExecutionContext {
.getRequiredPersistentEntity(action.getEntityType());
PersistentPropertyAccessor<S> 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 @@ class JdbcAggregateChangeExecutionContext {
return pathToValue;
}
if (action instanceof DbAction.UpdateRoot) {
return pathToValue;
}
throw new IllegalArgumentException(String.format("DbAction of type %s is not supported.", action.getClass()));
}

View File

@@ -31,6 +31,7 @@ import org.springframework.data.jdbc.core.convert.JdbcConverter;
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 class JdbcAggregateTemplate implements JdbcAggregateOperations {
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 class JdbcAggregateTemplate implements JdbcAggregateOperations {
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 class JdbcAggregateTemplate implements JdbcAggregateOperations {
RelationalPersistentEntity<?> persistentEntity = context.getRequiredPersistentEntity(instance.getClass());
Function<T, MutableAggregateChange<T>> changeCreator = persistentEntity.isNew(instance)
Function<T, AggregateChangeWithRoot<T>> changeCreator = persistentEntity.isNew(instance)
? entity -> createInsertChange(prepareVersionForInsert(entity))
: entity -> createUpdateChange(prepareVersionForUpdate(entity));
@@ -286,18 +281,18 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
executor.execute(change);
}
private <T> T store(T aggregateRoot, Function<T, MutableAggregateChange<T>> changeCreator,
private <T> T store(T aggregateRoot, Function<T, AggregateChangeWithRoot<T>> changeCreator,
RelationalPersistentEntity<?> persistentEntity) {
Assert.notNull(aggregateRoot, "Aggregate instance must not be null!");
aggregateRoot = triggerBeforeConvert(aggregateRoot);
MutableAggregateChange<T> change = changeCreator.apply(aggregateRoot);
AggregateChangeWithRoot<T> 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 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
MutableAggregateChange<T> change = createDeletingChange(id, entity, domainType);
entity = triggerBeforeDelete(entity, id, change);
change.setEntity(entity);
executor.execute(change);
triggerAfterDelete(entity, id, change);
}
private <T> MutableAggregateChange<T> createInsertChange(T instance) {
private <T> AggregateChangeWithRoot<T> createInsertChange(T instance) {
MutableAggregateChange<T> aggregateChange = MutableAggregateChange.forSave(instance);
jdbcEntityInsertWriter.write(instance, aggregateChange);
AggregateChangeWithRoot<T> aggregateChange = MutableAggregateChange.forSave(instance);
new RelationalEntityInsertWriter<T>(context).write(instance, aggregateChange);
return aggregateChange;
}
private <T> MutableAggregateChange<T> createUpdateChange(EntityAndPreviousVersion<T> entityAndVersion) {
private <T> AggregateChangeWithRoot<T> createUpdateChange(EntityAndPreviousVersion<T> entityAndVersion) {
MutableAggregateChange<T> aggregateChange = MutableAggregateChange.forSave(entityAndVersion.entity,
AggregateChangeWithRoot<T> aggregateChange = MutableAggregateChange.forSave(entityAndVersion.entity,
entityAndVersion.version);
jdbcEntityUpdateWriter.write(entityAndVersion.entity, aggregateChange);
new RelationalEntityUpdateWriter<T>(context).write(entityAndVersion.entity, aggregateChange);
return aggregateChange;
}
@@ -383,14 +377,14 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
previousVersion = RelationalEntityVersionUtils.getVersionNumberFromEntity(entity, persistentEntity, converter);
}
}
MutableAggregateChange<T> aggregateChange = MutableAggregateChange.forDelete(domainType, entity, previousVersion);
MutableAggregateChange<T> 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;
}

View File

@@ -39,6 +39,7 @@ import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
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<DummyEntity> 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<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
AggregateChangeWithRoot<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
entity = executor.execute(aggregateChange);
@@ -92,8 +93,8 @@ public class AggregateChangeIdGenerationImmutableUnitTests {
entity = entity.withSingle(content);
MutableAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
AggregateChangeWithRoot<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(createInsert("single", content, null));
entity = executor.execute(aggregateChange);
@@ -110,8 +111,8 @@ public class AggregateChangeIdGenerationImmutableUnitTests {
entity = entity.withContentList(asList(content, content2));
MutableAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
AggregateChangeWithRoot<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(createInsert("contentList", content, 0));
aggregateChange.addAction(createInsert("contentList", content2, 1));
@@ -129,8 +130,8 @@ public class AggregateChangeIdGenerationImmutableUnitTests {
entity = entity.withContentMap(createContentMap("a", content, "b", content2));
MutableAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
AggregateChangeWithRoot<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(createInsert("contentMap", content, "a"));
aggregateChange.addAction(createInsert("contentMap", content2, "b"));
@@ -149,8 +150,8 @@ public class AggregateChangeIdGenerationImmutableUnitTests {
DbAction.Insert<?> parentInsert = createInsert("single", content, null);
DbAction.Insert<?> insert = createDeepInsert("single", tag1, null, parentInsert);
MutableAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
AggregateChangeWithRoot<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(parentInsert);
aggregateChange.addAction(insert);
@@ -171,8 +172,8 @@ public class AggregateChangeIdGenerationImmutableUnitTests {
DbAction.Insert<?> insert1 = createDeepInsert("tagList", tag1, 0, parentInsert);
DbAction.Insert<?> insert2 = createDeepInsert("tagList", tag2, 1, parentInsert);
MutableAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
AggregateChangeWithRoot<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(parentInsert);
aggregateChange.addAction(insert1);
aggregateChange.addAction(insert2);
@@ -197,8 +198,8 @@ public class AggregateChangeIdGenerationImmutableUnitTests {
DbAction.Insert<?> insert1 = createDeepInsert("tagSet", tag1, null, parentInsert);
DbAction.Insert<?> insert2 = createDeepInsert("tagSet", tag2, null, parentInsert);
MutableAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
AggregateChangeWithRoot<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(parentInsert);
aggregateChange.addAction(insert1);
aggregateChange.addAction(insert2);
@@ -230,8 +231,8 @@ public class AggregateChangeIdGenerationImmutableUnitTests {
DbAction.Insert<?> insert1 = createDeepInsert("single", tag1, null, parentInsert1);
DbAction.Insert<?> insert2 = createDeepInsert("single", tag2, null, parentInsert2);
MutableAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
AggregateChangeWithRoot<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(parentInsert1);
aggregateChange.addAction(parentInsert2);
aggregateChange.addAction(insert1);
@@ -261,8 +262,8 @@ public class AggregateChangeIdGenerationImmutableUnitTests {
DbAction.Insert<?> insert2 = createDeepInsert("tagList", tag2, 0, parentInsert2);
DbAction.Insert<?> insert3 = createDeepInsert("tagList", tag3, 1, parentInsert2);
MutableAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
AggregateChangeWithRoot<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(parentInsert1);
aggregateChange.addAction(parentInsert2);
aggregateChange.addAction(insert1);
@@ -296,8 +297,8 @@ public class AggregateChangeIdGenerationImmutableUnitTests {
DbAction.Insert<?> insert2 = createDeepInsert("tagMap", tag2, "222", parentInsert2);
DbAction.Insert<?> insert3 = createDeepInsert("tagMap", tag3, "333", parentInsert2);
MutableAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
AggregateChangeWithRoot<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(parentInsert1);
aggregateChange.addAction(parentInsert2);
aggregateChange.addAction(insert1);
@@ -335,8 +336,8 @@ public class AggregateChangeIdGenerationImmutableUnitTests {
DbAction.Insert<?> insert1 = createDeepInsert("single", tag1, null, parentInsert1);
DbAction.Insert<?> insert2 = createDeepInsert("single", tag2, null, parentInsert2);
MutableAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
AggregateChangeWithRoot<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(parentInsert1);
aggregateChange.addAction(parentInsert2);
aggregateChange.addAction(insert1);
@@ -361,8 +362,8 @@ public class AggregateChangeIdGenerationImmutableUnitTests {
DbAction.Insert<?> parentInsert = createInsert("embedded.single", tag1, null);
MutableAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
AggregateChangeWithRoot<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(parentInsert);
entity = executor.execute(aggregateChange);

View File

@@ -36,6 +36,7 @@ import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
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<DummyEntity> 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<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
AggregateChangeWithRoot<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
executor.execute(aggregateChange);
@@ -83,8 +84,8 @@ public class AggregateChangeIdGenerationUnitTests {
entity.single = content;
MutableAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
AggregateChangeWithRoot<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(createInsert("single", content, null));
executor.execute(aggregateChange);
@@ -102,8 +103,8 @@ public class AggregateChangeIdGenerationUnitTests {
entity.contentList.add(content);
entity.contentList.add(content2);
MutableAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
AggregateChangeWithRoot<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(createInsert("contentList", content, 0));
aggregateChange.addAction(createInsert("contentList", content2, 1));
@@ -122,8 +123,8 @@ public class AggregateChangeIdGenerationUnitTests {
entity.contentMap.put("a", content);
entity.contentMap.put("b", content2);
MutableAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
AggregateChangeWithRoot<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(createInsert("contentMap", content, "a"));
aggregateChange.addAction(createInsert("contentMap", content2, "b"));
@@ -142,8 +143,8 @@ public class AggregateChangeIdGenerationUnitTests {
DbAction.Insert<?> parentInsert = createInsert("single", content, null);
DbAction.Insert<?> insert = createDeepInsert("single", tag1, null, parentInsert);
MutableAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
AggregateChangeWithRoot<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(parentInsert);
aggregateChange.addAction(insert);
@@ -165,8 +166,8 @@ public class AggregateChangeIdGenerationUnitTests {
DbAction.Insert<?> insert1 = createDeepInsert("tagList", tag1, 0, parentInsert);
DbAction.Insert<?> insert2 = createDeepInsert("tagList", tag2, 1, parentInsert);
MutableAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
AggregateChangeWithRoot<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(parentInsert);
aggregateChange.addAction(insert1);
aggregateChange.addAction(insert2);
@@ -192,8 +193,8 @@ public class AggregateChangeIdGenerationUnitTests {
DbAction.Insert<?> insert1 = createDeepInsert("tagSet", tag1, null, parentInsert);
DbAction.Insert<?> insert2 = createDeepInsert("tagSet", tag2, null, parentInsert);
MutableAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
AggregateChangeWithRoot<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(parentInsert);
aggregateChange.addAction(insert1);
aggregateChange.addAction(insert2);
@@ -226,8 +227,8 @@ public class AggregateChangeIdGenerationUnitTests {
DbAction.Insert<?> insert1 = createDeepInsert("single", tag1, null, parentInsert1);
DbAction.Insert<?> insert2 = createDeepInsert("single", tag2, null, parentInsert2);
MutableAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
AggregateChangeWithRoot<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(parentInsert1);
aggregateChange.addAction(parentInsert2);
aggregateChange.addAction(insert1);
@@ -259,8 +260,8 @@ public class AggregateChangeIdGenerationUnitTests {
DbAction.Insert<?> insert2 = createDeepInsert("tagList", tag2, 0, parentInsert2);
DbAction.Insert<?> insert3 = createDeepInsert("tagList", tag3, 1, parentInsert2);
MutableAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
AggregateChangeWithRoot<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(parentInsert1);
aggregateChange.addAction(parentInsert2);
aggregateChange.addAction(insert1);
@@ -297,8 +298,8 @@ public class AggregateChangeIdGenerationUnitTests {
DbAction.Insert<?> insert2 = createDeepInsert("tagMap", tag2, "222", parentInsert2);
DbAction.Insert<?> insert3 = createDeepInsert("tagMap", tag3, "333", parentInsert2);
MutableAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
AggregateChangeWithRoot<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(parentInsert1);
aggregateChange.addAction(parentInsert2);
aggregateChange.addAction(insert1);

View File

@@ -18,11 +18,9 @@ package org.springframework.data.jdbc.core;
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.data.relational.core.mapping.RelationalMappingContext
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 class ImmutableAggregateTemplateHsqlIntegrationTests {
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 @@ public class ImmutableAggregateTemplateHsqlIntegrationTests {
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;

View File

@@ -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() {

View File

@@ -22,6 +22,7 @@ import static org.mockito.Mockito.*;
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 class JdbcAggregateChangeExecutorContextUnitTests {
DummyEntity newRoot = executionContext.populateIdsIfNecessary();
assertThat(newRoot).isNull();
assertThat(newRoot).isEqualTo(root);
assertThat(root.id).isEqualTo(23L);
}
@@ -96,7 +89,7 @@ public class JdbcAggregateChangeExecutorContextUnitTests {
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 class JdbcAggregateChangeExecutorContextUnitTests {
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 @@ public class JdbcAggregateChangeExecutorContextUnitTests {
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 @@ public class JdbcAggregateChangeExecutorContextUnitTests {
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<DummyEntity> 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 @@ public class JdbcAggregateChangeExecutorContextUnitTests {
Content content;
ContentImmutableId contentImmutableId;
List<Content> list = new ArrayList<>();
}
@@ -212,4 +226,9 @@ public class JdbcAggregateChangeExecutorContextUnitTests {
@Id Long id;
}
@Value
private static class ContentImmutableId {
@Id Long id;
}
}

View File

@@ -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<ImmutableEntity> 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<MutableEntity> 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<MutableWithImmutableIdEntity> 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<ImmutableWithMutableIdEntity> 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<ImmutableEntity, Long> {}
@Value
@With
static class ImmutableEntity {
@Id Long id;
String name;
}
private interface MutableEntityRepository extends ListCrudRepository<MutableEntity, Long> {}
@Data
@AllArgsConstructor
static class MutableEntity {
@Id private Long id;
private String name;
}
private interface MutableWithImmutableIdEntityRepository extends ListCrudRepository<MutableWithImmutableIdEntity, Long> {}
@Data
@AllArgsConstructor
static class MutableWithImmutableIdEntity {
@Id private final Long id;
private String name;
}
private interface ImmutableWithMutableIdEntityRepository extends ListCrudRepository<ImmutableWithMutableIdEntity, Long> {}
@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<ImmutableEntity> nameSetterImmutable() {
return (aggregate, aggregateChange) -> aggregate.withName("fromBeforeSaveCallback");
}
@Bean
BeforeSaveCallback<MutableEntity> nameSetterMutable() {
return (aggregate, aggregateChange) -> {
aggregate.setName("fromBeforeSaveCallback");
return aggregate;
};
}
@Bean
BeforeSaveCallback<MutableWithImmutableIdEntity> nameSetterMutableWithImmutableId() {
return (aggregate, aggregateChange) -> {
aggregate.setName("fromBeforeSaveCallback");
return aggregate;
};
}
@Bean
BeforeSaveCallback<ImmutableWithMutableIdEntity> nameSetterImmutableWithMutableId() {
return (aggregate, aggregateChange) -> aggregate.withName("fromBeforeSaveCallback");
}
}
}

View File

@@ -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);

View File

@@ -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)
);

View File

@@ -9,7 +9,7 @@
<arg column="name" javaType="String"/>
</constructor>
</resultMap>
<insert id="insert" parameterType="MyBatisContext" useGeneratedKeys="true" keyProperty="instance.id" keyColumn="ID">
<insert id="insert" parameterType="MyBatisContext" useGeneratedKeys="true" keyProperty="id" keyColumn="ID">
INSERT INTO DummyEntity (id) VALUES (DEFAULT)
</insert>
<select id="findById" resultType="MyBatisContext" resultMap="dummyEntityMap">

View File

@@ -9,7 +9,7 @@
<arg column="name" javaType="String"/>
</constructor>
</resultMap>
<insert id="insert" parameterType="MyBatisContext" useGeneratedKeys="true" keyProperty="instance.id" keyColumn="ID">
<insert id="insert" parameterType="MyBatisContext" useGeneratedKeys="true" keyProperty="id" keyColumn="ID">
INSERT INTO DummyEntity (id) VALUES (DEFAULT)
</insert>
<select id="findById" resultType="MyBatisContext" resultMap="dummyEntityMap">