Update SaveBatchingAggregateChange to batch Delete actions.
Original pull request #1229
This commit is contained in:
committed by
Jens Schauder
parent
64d9bbbd55
commit
aa1610d381
@@ -92,6 +92,8 @@ class AggregateChangeExecutor {
|
||||
executionContext.executeUpdateRoot((DbAction.UpdateRoot<?>) action);
|
||||
} else if (action instanceof DbAction.Delete) {
|
||||
executionContext.executeDelete((DbAction.Delete<?>) action);
|
||||
} else if (action instanceof DbAction.BatchDelete<?>) {
|
||||
executionContext.executeBatchDelete((DbAction.BatchDelete<?>) action);
|
||||
} else if (action instanceof DbAction.DeleteAll) {
|
||||
executionContext.executeDeleteAll((DbAction.DeleteAll<?>) action);
|
||||
} else if (action instanceof DbAction.DeleteRoot) {
|
||||
|
||||
@@ -135,6 +135,12 @@ class JdbcAggregateChangeExecutionContext {
|
||||
accessStrategy.delete(delete.getRootId(), delete.getPropertyPath());
|
||||
}
|
||||
|
||||
<T> void executeBatchDelete(DbAction.BatchDelete<T> batchDelete) {
|
||||
|
||||
List<Object> rootIds = batchDelete.getActions().stream().map(DbAction.Delete::getRootId).toList();
|
||||
accessStrategy.delete(rootIds, batchDelete.getBatchValue());
|
||||
}
|
||||
|
||||
<T> void executeDeleteAllRoot(DbAction.DeleteAllRoot<T> deleteAllRoot) {
|
||||
|
||||
accessStrategy.deleteAll(deleteAllRoot.getEntityType());
|
||||
|
||||
@@ -87,6 +87,11 @@ public class CascadingDataAccessStrategy implements DataAccessStrategy {
|
||||
collectVoid(das -> das.delete(rootId, propertyPath));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(Iterable<Object> rootIds, PersistentPropertyPath<RelationalPersistentProperty> propertyPath) {
|
||||
collectVoid(das -> das.delete(rootIds, propertyPath));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void deleteAll(Class<T> domainType) {
|
||||
collectVoid(das -> das.deleteAll(domainType));
|
||||
|
||||
@@ -152,6 +152,14 @@ public interface DataAccessStrategy extends RelationResolver {
|
||||
*/
|
||||
void delete(Object rootId, PersistentPropertyPath<RelationalPersistentProperty> propertyPath);
|
||||
|
||||
/**
|
||||
* Deletes all entities reachable via {@literal propertyPath} from the instances identified by {@literal rootIds}.
|
||||
*
|
||||
* @param rootIds Ids of the root objects on which the {@literal propertyPath} is based. Must not be {@code null} or empty.
|
||||
* @param propertyPath Leading from the root object to the entities to be deleted. Must not be {@code null}.
|
||||
*/
|
||||
void delete(Iterable<Object> rootIds, PersistentPropertyPath<RelationalPersistentProperty> propertyPath);
|
||||
|
||||
/**
|
||||
* Deletes all entities of the given domain type.
|
||||
*
|
||||
|
||||
@@ -196,6 +196,21 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
|
||||
operations.update(delete, parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(Iterable<Object> rootIds, PersistentPropertyPath<RelationalPersistentProperty> propertyPath) {
|
||||
|
||||
RelationalPersistentEntity<?> rootEntity = context
|
||||
.getRequiredPersistentEntity(propertyPath.getBaseProperty().getOwner().getType());
|
||||
|
||||
RelationalPersistentProperty referencingProperty = propertyPath.getLeafProperty();
|
||||
Assert.notNull(referencingProperty, "No property found matching the PropertyPath " + propertyPath);
|
||||
|
||||
String delete = sql(rootEntity.getType()).createDeleteInByPath(propertyPath);
|
||||
|
||||
SqlIdentifierParameterSource parameters = sqlParametersFactory.forQueryByIds(rootIds, rootEntity.getType());
|
||||
operations.update(delete, parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void deleteAll(Class<T> domainType) {
|
||||
operations.getJdbcOperations().update(sql(domainType).createDeleteAllSql(null));
|
||||
|
||||
@@ -71,6 +71,11 @@ public class DelegatingDataAccessStrategy implements DataAccessStrategy {
|
||||
delegate.delete(rootId, propertyPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(Iterable<Object> rootIds, PersistentPropertyPath<RelationalPersistentProperty> propertyPath) {
|
||||
delegate.delete(rootIds, propertyPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(Object id, Class<?> domainType) {
|
||||
delegate.delete(id, domainType);
|
||||
|
||||
@@ -360,7 +360,8 @@ class SqlGenerator {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@code DELETE} query and filter by {@link PersistentPropertyPath}.
|
||||
* Create a {@code DELETE} query and filter by {@link PersistentPropertyPath} using {@code WHERE} with the {@code =}
|
||||
* operator.
|
||||
*
|
||||
* @param path must not be {@literal null}.
|
||||
* @return the statement as a {@link String}. Guaranteed to be not {@literal null}.
|
||||
@@ -370,6 +371,18 @@ class SqlGenerator {
|
||||
filterColumn -> filterColumn.isEqualTo(getBindMarker(ROOT_ID_PARAMETER)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@code DELETE} query and filter by {@link PersistentPropertyPath} using {@code WHERE} with the {@code IN}
|
||||
* operator.
|
||||
*
|
||||
* @param path must not be {@literal null}.
|
||||
* @return the statement as a {@link String}. Guaranteed to be not {@literal null}.
|
||||
*/
|
||||
String createDeleteInByPath(PersistentPropertyPath<RelationalPersistentProperty> path) {
|
||||
return createDeleteByPathAndCriteria(new PersistentPropertyPathExtension(mappingContext, path),
|
||||
filterColumn -> filterColumn.in(getBindMarker(IDS_SQL_PARAMETER)));
|
||||
}
|
||||
|
||||
private String createFindOneSql() {
|
||||
|
||||
Select select = selectBuilder().where(getIdColumn().isEqualTo(getBindMarker(ID_SQL_PARAMETER))) //
|
||||
|
||||
@@ -218,6 +218,11 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
|
||||
sqlSession().delete(statement, parameter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(Iterable<Object> rootIds, PersistentPropertyPath<RelationalPersistentProperty> propertyPath) {
|
||||
rootIds.forEach(rootId -> delete(rootId, propertyPath));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void deleteAll(Class<T> domainType) {
|
||||
|
||||
|
||||
@@ -147,6 +147,14 @@ class SqlGeneratorUnitTests {
|
||||
assertThat(sql).isEqualTo("DELETE FROM referenced_entity WHERE referenced_entity.dummy_entity = :rootId");
|
||||
}
|
||||
|
||||
@Test // GH-537
|
||||
void cascadingDeleteInByPathFirstLevel() {
|
||||
|
||||
String sql = sqlGenerator.createDeleteInByPath(getPath("ref", DummyEntity.class));
|
||||
|
||||
assertThat(sql).isEqualTo("DELETE FROM referenced_entity WHERE referenced_entity.dummy_entity IN (:ids)");
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-112
|
||||
void cascadingDeleteByPathSecondLevel() {
|
||||
|
||||
@@ -156,6 +164,15 @@ class SqlGeneratorUnitTests {
|
||||
"DELETE FROM second_level_referenced_entity WHERE second_level_referenced_entity.referenced_entity IN (SELECT referenced_entity.x_l1id FROM referenced_entity WHERE referenced_entity.dummy_entity = :rootId)");
|
||||
}
|
||||
|
||||
@Test // GH-537
|
||||
void cascadingDeleteInByPathSecondLevel() {
|
||||
|
||||
String sql = sqlGenerator.createDeleteInByPath(getPath("ref.further", DummyEntity.class));
|
||||
|
||||
assertThat(sql).isEqualTo(
|
||||
"DELETE FROM second_level_referenced_entity WHERE second_level_referenced_entity.referenced_entity IN (SELECT referenced_entity.x_l1id FROM referenced_entity WHERE referenced_entity.dummy_entity IN (:ids))");
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-112
|
||||
void deleteAll() {
|
||||
|
||||
|
||||
@@ -422,6 +422,18 @@ public interface DbAction<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a batch delete statement for multiple entities that are reachable via a given path from the aggregate root.
|
||||
*
|
||||
* @param <T> type of the entity for which this represents a database interaction.
|
||||
* @since 3.0
|
||||
*/
|
||||
final class BatchDelete<T> extends BatchWithValue<T, Delete<T>, PersistentPropertyPath<RelationalPersistentProperty>> {
|
||||
public BatchDelete(List<Delete<T>> actions) {
|
||||
super(actions, Delete::getPropertyPath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An action depending on another action for providing additional information like the id of a parent entity.
|
||||
*
|
||||
|
||||
@@ -31,8 +31,8 @@ import org.springframework.util.Assert;
|
||||
/**
|
||||
* A {@link BatchingAggregateChange} implementation for save changes that can contain actions for any mix of insert and
|
||||
* update operations. When consumed, actions are yielded in the appropriate entity tree order with inserts carried out
|
||||
* from root to leaves and deletes in reverse. All insert operations are grouped into batches to offer the ability for
|
||||
* an optimized batch operation to be used.
|
||||
* from root to leaves and deletes in reverse. All operations that can be batched are grouped and combined to offer the
|
||||
* ability for an optimized batch operation to be used.
|
||||
*
|
||||
* @author Chirag Tailor
|
||||
* @since 3.0
|
||||
@@ -51,7 +51,7 @@ public class SaveBatchingAggregateChange<T> implements BatchingAggregateChange<T
|
||||
private final List<DbAction.InsertRoot<T>> insertRootBatchCandidates = new ArrayList<>();
|
||||
private final Map<PersistentPropertyPath<RelationalPersistentProperty>, Map<IdValueSource, List<DbAction.Insert<Object>>>> insertActions = //
|
||||
new HashMap<>();
|
||||
private final Map<PersistentPropertyPath<RelationalPersistentProperty>, List<DbAction.Delete<?>>> deleteActions = //
|
||||
private final Map<PersistentPropertyPath<RelationalPersistentProperty>, List<DbAction.Delete<Object>>> deleteActions = //
|
||||
new HashMap<>();
|
||||
|
||||
SaveBatchingAggregateChange(Class<T> entityType) {
|
||||
@@ -80,9 +80,17 @@ public class SaveBatchingAggregateChange<T> implements BatchingAggregateChange<T
|
||||
insertRootBatchCandidates.forEach(consumer);
|
||||
}
|
||||
deleteActions.entrySet().stream().sorted(Map.Entry.comparingByKey(pathLengthComparator.reversed()))
|
||||
.forEach((entry) -> entry.getValue().forEach(consumer));
|
||||
insertActions.entrySet().stream().sorted(Map.Entry.comparingByKey(pathLengthComparator)).forEach((entry) -> entry
|
||||
.getValue().forEach((idValueSource, inserts) -> consumer.accept(new DbAction.BatchInsert<>(inserts))));
|
||||
.forEach((entry) -> {
|
||||
List<DbAction.Delete<Object>> deletes = entry.getValue();
|
||||
if (deletes.size() > 1) {
|
||||
consumer.accept(new DbAction.BatchDelete<>(deletes));
|
||||
} else {
|
||||
deletes.forEach(consumer);
|
||||
}
|
||||
});
|
||||
insertActions.entrySet().stream().sorted(Map.Entry.comparingByKey(pathLengthComparator))
|
||||
.forEach((entry) -> entry.getValue().forEach((idValueSource, inserts) ->
|
||||
consumer.accept(new DbAction.BatchInsert<>(inserts))));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -107,7 +115,8 @@ public class SaveBatchingAggregateChange<T> implements BatchingAggregateChange<T
|
||||
// noinspection unchecked
|
||||
addInsert((DbAction.Insert<Object>) insertAction);
|
||||
} else if (action instanceof DbAction.Delete<?> deleteAction) {
|
||||
addDelete(deleteAction);
|
||||
// noinspection unchecked
|
||||
addDelete((DbAction.Delete<Object>) deleteAction);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -140,7 +149,7 @@ public class SaveBatchingAggregateChange<T> implements BatchingAggregateChange<T
|
||||
});
|
||||
}
|
||||
|
||||
private void addDelete(DbAction.Delete<?> action) {
|
||||
private void addDelete(DbAction.Delete<Object> action) {
|
||||
|
||||
PersistentPropertyPath<RelationalPersistentProperty> propertyPath = action.getPropertyPath();
|
||||
deleteActions.merge(propertyPath, new ArrayList<>(singletonList(action)), (actions, defaultValue) -> {
|
||||
|
||||
@@ -277,7 +277,7 @@ class SaveBatchingAggregateChangeTest {
|
||||
Root root1 = new Root(1L, null);
|
||||
RootAggregateChange<Root> aggregateChange1 = MutableAggregateChange.forSave(root1);
|
||||
aggregateChange1.setRootAction(new DbAction.UpdateRoot<>(root1, null));
|
||||
DbAction.Delete<?> root1IntermediateDelete = new DbAction.Delete<>(1L,
|
||||
DbAction.Delete<Intermediate> root1IntermediateDelete = new DbAction.Delete<>(1L,
|
||||
context.getPersistentPropertyPath("intermediate", Root.class));
|
||||
aggregateChange1.addAction(root1IntermediateDelete);
|
||||
|
||||
@@ -289,7 +289,7 @@ class SaveBatchingAggregateChangeTest {
|
||||
context.getPersistentPropertyPath("intermediate.leaf", Root.class));
|
||||
aggregateChange2.addAction(root2LeafDelete);
|
||||
|
||||
DbAction.Delete<?> root2IntermediateDelete = new DbAction.Delete<>(1L,
|
||||
DbAction.Delete<Intermediate> root2IntermediateDelete = new DbAction.Delete<>(1L,
|
||||
context.getPersistentPropertyPath("intermediate", Root.class));
|
||||
aggregateChange2.addAction(root2IntermediateDelete);
|
||||
|
||||
@@ -297,8 +297,38 @@ class SaveBatchingAggregateChangeTest {
|
||||
change.add(aggregateChange1);
|
||||
change.add(aggregateChange2);
|
||||
|
||||
assertThat(extractActions(change)).containsSubsequence(root2LeafDelete, root1IntermediateDelete,
|
||||
root2IntermediateDelete);
|
||||
List<DbAction<?>> actions = extractActions(change);
|
||||
assertThat(actions).extracting(DbAction::getClass, DbAction::getEntityType).containsSubsequence(
|
||||
Tuple.tuple(DbAction.Delete.class, Leaf.class), //
|
||||
Tuple.tuple(DbAction.BatchDelete.class, Intermediate.class));
|
||||
assertThat(getBatchWithValueAction(actions, Intermediate.class, DbAction.BatchDelete.class).getActions())
|
||||
.containsExactly(root1IntermediateDelete, root2IntermediateDelete);
|
||||
}
|
||||
|
||||
@Test
|
||||
void yieldsDeleteActionsAsBatchDeletes_groupedByPath_whenGroupContainsMultipleDeletes() {
|
||||
|
||||
Root root = new Root(1L, null);
|
||||
RootAggregateChange<Root> aggregateChange = MutableAggregateChange.forSave(root);
|
||||
DbAction.UpdateRoot<Root> updateRoot = new DbAction.UpdateRoot<>(root, null);
|
||||
aggregateChange.setRootAction(updateRoot);
|
||||
DbAction.Delete<Intermediate> intermediateDelete1 = new DbAction.Delete<>(1L,
|
||||
context.getPersistentPropertyPath("intermediate", Root.class));
|
||||
DbAction.Delete<Intermediate> intermediateDelete2 = new DbAction.Delete<>(2L,
|
||||
context.getPersistentPropertyPath("intermediate", Root.class));
|
||||
aggregateChange.addAction(intermediateDelete1);
|
||||
aggregateChange.addAction(intermediateDelete2);
|
||||
|
||||
BatchingAggregateChange<Root, RootAggregateChange<Root>> change = BatchingAggregateChange.forSave(Root.class);
|
||||
change.add(aggregateChange);
|
||||
|
||||
List<DbAction<?>> actions = extractActions(change);
|
||||
assertThat(actions).extracting(DbAction::getClass, DbAction::getEntityType) //
|
||||
.containsExactly( //
|
||||
Tuple.tuple(DbAction.UpdateRoot.class, Root.class), //
|
||||
Tuple.tuple(DbAction.BatchDelete.class, Intermediate.class));
|
||||
assertThat(getBatchWithValueAction(actions, Intermediate.class, DbAction.BatchDelete.class).getActions())
|
||||
.containsExactly(intermediateDelete1, intermediateDelete2);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user