Add DeleteBatchingAggregateChange to batch deletes when deleting multiple aggregate roots.
+ Rename DefaultAggregateChange to DeleteAggregateChange. Original pull request #1230
This commit is contained in:
committed by
Jens Schauder
parent
cf6e174088
commit
64a07e608d
@@ -80,6 +80,15 @@ public interface JdbcAggregateOperations {
|
||||
*/
|
||||
<T> void deleteById(Object id, Class<T> domainType);
|
||||
|
||||
/**
|
||||
* Deletes all aggregates identified by their aggregate root ids.
|
||||
*
|
||||
* @param ids the ids of the aggregate roots of the aggregates to be deleted. Must not be {@code null}.
|
||||
* @param domainType the type of the aggregate root.
|
||||
* @param <T> the type of the aggregate root.
|
||||
*/
|
||||
<T> void deleteAllById(Iterable<?> ids, Class<T> domainType);
|
||||
|
||||
/**
|
||||
* Delete an aggregate identified by it's aggregate root.
|
||||
*
|
||||
@@ -96,6 +105,15 @@ public interface JdbcAggregateOperations {
|
||||
*/
|
||||
void deleteAll(Class<?> domainType);
|
||||
|
||||
/**
|
||||
* Delete all aggregates identified by their aggregate roots.
|
||||
*
|
||||
* @param aggregateRoots to delete. Must not be {@code null}.
|
||||
* @param domainType type of the aggregate roots to be deleted. Must not be {@code null}.
|
||||
* @param <T> the type of the aggregate roots.
|
||||
*/
|
||||
<T> void deleteAll(Iterable<? extends T> aggregateRoots, Class<T> domainType);
|
||||
|
||||
/**
|
||||
* Counts the number of aggregates of a given type.
|
||||
*
|
||||
|
||||
@@ -17,7 +17,9 @@ package org.springframework.data.jdbc.core;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
@@ -33,6 +35,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.BatchingAggregateChange;
|
||||
import org.springframework.data.relational.core.conversion.DeleteAggregateChange;
|
||||
import org.springframework.data.relational.core.conversion.MutableAggregateChange;
|
||||
import org.springframework.data.relational.core.conversion.RelationalEntityDeleteWriter;
|
||||
import org.springframework.data.relational.core.conversion.RelationalEntityInsertWriter;
|
||||
@@ -275,6 +278,25 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
|
||||
deleteTree(id, null, domainType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void deleteAllById(Iterable<?> ids, Class<T> domainType) {
|
||||
|
||||
Assert.isTrue(ids.iterator().hasNext(), "Ids must not be empty!");
|
||||
|
||||
BatchingAggregateChange<T, DeleteAggregateChange<T>> batchingAggregateChange = BatchingAggregateChange
|
||||
.forDelete(domainType);
|
||||
|
||||
ids.forEach(id -> {
|
||||
DeleteAggregateChange<T> change = createDeletingChange(id, null, domainType);
|
||||
triggerBeforeDelete(null, id, change);
|
||||
batchingAggregateChange.add(change);
|
||||
});
|
||||
|
||||
executor.executeDelete(batchingAggregateChange);
|
||||
|
||||
ids.forEach(id -> triggerAfterDelete(null, id, batchingAggregateChange));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAll(Class<?> domainType) {
|
||||
|
||||
@@ -284,6 +306,28 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
|
||||
executor.executeDelete(change);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void deleteAll(Iterable<? extends T> instances, Class<T> domainType) {
|
||||
|
||||
Assert.isTrue(instances.iterator().hasNext(), "Aggregate instances must not be empty!");
|
||||
|
||||
BatchingAggregateChange<T, DeleteAggregateChange<T>> batchingAggregateChange = BatchingAggregateChange
|
||||
.forDelete(domainType);
|
||||
Map<Object, T> instancesBeforeExecute = new LinkedHashMap<>();
|
||||
|
||||
instances.forEach(instance -> {
|
||||
Object id = context.getRequiredPersistentEntity(domainType).getIdentifierAccessor(instance)
|
||||
.getRequiredIdentifier();
|
||||
DeleteAggregateChange<T> change = createDeletingChange(id, instance, domainType);
|
||||
instancesBeforeExecute.put(id, triggerBeforeDelete(instance, id, change));
|
||||
batchingAggregateChange.add(change);
|
||||
});
|
||||
|
||||
executor.executeDelete(batchingAggregateChange);
|
||||
|
||||
instancesBeforeExecute.forEach((id, instance) -> triggerAfterDelete(instance, id, batchingAggregateChange));
|
||||
}
|
||||
|
||||
private <T> T afterExecute(AggregateChange<T> change, T entityAfterExecution) {
|
||||
|
||||
Object identifier = context.getRequiredPersistentEntity(change.getEntityType())
|
||||
@@ -416,7 +460,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
|
||||
return (RelationalPersistentEntity<T>) context.getRequiredPersistentEntity(instance.getClass());
|
||||
}
|
||||
|
||||
private <T> MutableAggregateChange<T> createDeletingChange(Object id, @Nullable T entity, Class<T> domainType) {
|
||||
private <T> DeleteAggregateChange<T> createDeletingChange(Object id, @Nullable T entity, Class<T> domainType) {
|
||||
|
||||
Number previousVersion = null;
|
||||
if (entity != null) {
|
||||
@@ -425,7 +469,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
|
||||
previousVersion = RelationalEntityVersionUtils.getVersionNumberFromEntity(entity, persistentEntity, converter);
|
||||
}
|
||||
}
|
||||
MutableAggregateChange<T> aggregateChange = MutableAggregateChange.forDelete(domainType, previousVersion);
|
||||
DeleteAggregateChange<T> aggregateChange = MutableAggregateChange.forDelete(domainType, previousVersion);
|
||||
jdbcEntityDeleteWriter.write(id, aggregateChange);
|
||||
return aggregateChange;
|
||||
}
|
||||
@@ -475,7 +519,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
|
||||
return entityCallbacks.callback(AfterSaveCallback.class, aggregateRoot);
|
||||
}
|
||||
|
||||
private <T> void triggerAfterDelete(@Nullable T aggregateRoot, Object id, MutableAggregateChange<T> change) {
|
||||
private <T> void triggerAfterDelete(@Nullable T aggregateRoot, Object id, AggregateChange<T> change) {
|
||||
|
||||
publisher.publishEvent(new AfterDeleteEvent<>(Identifier.of(id), aggregateRoot, change));
|
||||
|
||||
|
||||
@@ -101,14 +101,13 @@ public class SimpleJdbcRepository<T, ID> implements CrudRepository<T,ID>, Paging
|
||||
|
||||
@Override
|
||||
public void deleteAllById(Iterable<? extends ID> ids) {
|
||||
ids.forEach(it -> entityOperations.deleteById(it, entity.getType()));
|
||||
entityOperations.deleteAllById(ids, entity.getType());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void deleteAll(Iterable<? extends T> entities) {
|
||||
entities.forEach(it -> entityOperations.delete(it, (Class<T>) it.getClass()));
|
||||
entityOperations.deleteAll(entities, entity.getType());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
|
||||
@@ -331,6 +331,38 @@ class JdbcAggregateTemplateIntegrationTests {
|
||||
softly.assertAll();
|
||||
}
|
||||
|
||||
@Test // GH-537
|
||||
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
|
||||
void saveAndDeleteAllByAggregateRootsWithReferencedEntity() {
|
||||
LegoSet legoSet1 = template.save(legoSet);
|
||||
LegoSet legoSet2 = template.save(createLegoSet("Some Name"));
|
||||
|
||||
template.deleteAll(List.of(legoSet1, legoSet2), LegoSet.class);
|
||||
|
||||
SoftAssertions softly = new SoftAssertions();
|
||||
|
||||
assertThat(template.findAll(LegoSet.class)).isEmpty();
|
||||
assertThat(template.findAll(Manual.class)).isEmpty();
|
||||
|
||||
softly.assertAll();
|
||||
}
|
||||
|
||||
@Test // GH-537
|
||||
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
|
||||
void saveAndDeleteAllByIdsWithReferencedEntity() {
|
||||
LegoSet legoSet1 = template.save(legoSet);
|
||||
LegoSet legoSet2 = template.save(createLegoSet("Some Name"));
|
||||
|
||||
template.deleteAllById(List.of(legoSet1.id, legoSet2.id), LegoSet.class);
|
||||
|
||||
SoftAssertions softly = new SoftAssertions();
|
||||
|
||||
assertThat(template.findAll(LegoSet.class)).isEmpty();
|
||||
assertThat(template.findAll(Manual.class)).isEmpty();
|
||||
|
||||
softly.assertAll();
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-112
|
||||
@EnabledOnFeature({ SUPPORTS_QUOTED_IDS, SUPPORTS_GENERATED_IDS_IN_REFERENCED_ENTITIES })
|
||||
void updateReferencedEntityFromNull() {
|
||||
|
||||
@@ -47,4 +47,19 @@ public interface BatchingAggregateChange<T, C extends MutableAggregateChange<T>>
|
||||
|
||||
return new SaveBatchingAggregateChange<>(entityClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a {@link BatchingAggregateChange} for deleting entities.
|
||||
*
|
||||
* @param entityClass aggregate root type.
|
||||
* @param <T> entity type.
|
||||
* @return the {@link BatchingAggregateChange} for deleting root entities.
|
||||
* @since 3.0
|
||||
*/
|
||||
static <T> BatchingAggregateChange<T, DeleteAggregateChange<T>> forDelete(Class<T> entityClass) {
|
||||
|
||||
Assert.notNull(entityClass, "Entity class must not be null");
|
||||
|
||||
return new DeleteBatchingAggregateChange<>(entityClass);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,9 +30,7 @@ import org.springframework.util.Assert;
|
||||
* @author Chirag Tailor
|
||||
* @since 2.0
|
||||
*/
|
||||
class DefaultAggregateChange<T> implements MutableAggregateChange<T> {
|
||||
|
||||
private final Kind kind;
|
||||
public class DeleteAggregateChange<T> implements MutableAggregateChange<T> {
|
||||
|
||||
/** Type of the aggregate root to be changed */
|
||||
private final Class<T> entityType;
|
||||
@@ -42,9 +40,7 @@ class DefaultAggregateChange<T> implements MutableAggregateChange<T> {
|
||||
/** The previous version assigned to the instance being changed, if available */
|
||||
@Nullable private final Number previousVersion;
|
||||
|
||||
public DefaultAggregateChange(Kind kind, Class<T> entityType, @Nullable Number previousVersion) {
|
||||
|
||||
this.kind = kind;
|
||||
public DeleteAggregateChange(Class<T> entityType, @Nullable Number previousVersion) {
|
||||
this.entityType = entityType;
|
||||
this.previousVersion = previousVersion;
|
||||
}
|
||||
@@ -64,7 +60,7 @@ class DefaultAggregateChange<T> implements MutableAggregateChange<T> {
|
||||
|
||||
@Override
|
||||
public Kind getKind() {
|
||||
return this.kind;
|
||||
return Kind.DELETE;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -0,0 +1,89 @@
|
||||
package org.springframework.data.relational.core.conversion;
|
||||
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static java.util.Collections.*;
|
||||
|
||||
/**
|
||||
* A {@link BatchingAggregateChange} implementation for delete changes that can contain actions for one or more delete
|
||||
* operations. When consumed, actions are yielded in the appropriate entity tree order with deletes carried out from
|
||||
* leaves to root. 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
|
||||
*/
|
||||
public class DeleteBatchingAggregateChange<T> implements BatchingAggregateChange<T, DeleteAggregateChange<T>> {
|
||||
|
||||
private static final Comparator<PersistentPropertyPath<RelationalPersistentProperty>> pathLengthComparator = //
|
||||
Comparator.comparing(PersistentPropertyPath::getLength);
|
||||
|
||||
private final Class<T> entityType;
|
||||
private final List<DbAction.DeleteRoot<T>> rootActions = new ArrayList<>();
|
||||
private final List<DbAction.AcquireLockRoot<?>> lockActions = new ArrayList<>();
|
||||
private final Map<PersistentPropertyPath<RelationalPersistentProperty>, List<DbAction.Delete<Object>>> deleteActions = //
|
||||
new HashMap<>();
|
||||
|
||||
public DeleteBatchingAggregateChange(Class<T> entityType) {
|
||||
this.entityType = entityType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Kind getKind() {
|
||||
return Kind.DELETE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<T> getEntityType() {
|
||||
return entityType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEachAction(Consumer<? super DbAction<?>> consumer) {
|
||||
|
||||
lockActions.forEach(consumer);
|
||||
deleteActions.entrySet().stream().sorted(Map.Entry.comparingByKey(pathLengthComparator.reversed()))
|
||||
.forEach((entry) -> {
|
||||
List<DbAction.Delete<Object>> deletes = entry.getValue();
|
||||
if (deletes.size() > 1) {
|
||||
consumer.accept(new DbAction.BatchDelete<>(deletes));
|
||||
} else {
|
||||
deletes.forEach(consumer);
|
||||
}
|
||||
});
|
||||
rootActions.forEach(consumer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(DeleteAggregateChange<T> aggregateChange) {
|
||||
|
||||
aggregateChange.forEachAction(action -> {
|
||||
if (action instanceof DbAction.DeleteRoot<?> deleteRootAction) {
|
||||
//noinspection unchecked
|
||||
rootActions.add((DbAction.DeleteRoot<T>) deleteRootAction);
|
||||
} else if (action instanceof DbAction.Delete<?> deleteAction) {
|
||||
// noinspection unchecked
|
||||
addDelete((DbAction.Delete<Object>) deleteAction);
|
||||
} else if (action instanceof DbAction.AcquireLockRoot<?> lockRootAction) {
|
||||
lockActions.add(lockRootAction);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void addDelete(DbAction.Delete<Object> action) {
|
||||
|
||||
PersistentPropertyPath<RelationalPersistentProperty> propertyPath = action.getPropertyPath();
|
||||
deleteActions.merge(propertyPath, new ArrayList<>(singletonList(action)), (actions, defaultValue) -> {
|
||||
actions.add(action);
|
||||
return actions;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -59,15 +59,15 @@ public interface MutableAggregateChange<T> extends AggregateChange<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create an {@link MutableAggregateChange} for deleting entities.
|
||||
* Factory method to create a {@link DeleteAggregateChange} for deleting entities.
|
||||
*
|
||||
* @param entity aggregate root to delete.
|
||||
* @param <T> entity type.
|
||||
* @return the {@link MutableAggregateChange} for deleting the root {@code entity}.
|
||||
* @return the {@link DeleteAggregateChange} for deleting the root {@code entity}.
|
||||
* @since 1.2
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
static <T> MutableAggregateChange<T> forDelete(T entity) {
|
||||
static <T> DeleteAggregateChange<T> forDelete(T entity) {
|
||||
|
||||
Assert.notNull(entity, "Entity must not be null");
|
||||
|
||||
@@ -75,31 +75,31 @@ public interface MutableAggregateChange<T> extends AggregateChange<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create an {@link MutableAggregateChange} for deleting entities.
|
||||
* Factory method to create a {@link DeleteAggregateChange} for deleting entities.
|
||||
*
|
||||
* @param entityClass aggregate root type.
|
||||
* @param <T> entity type.
|
||||
* @return the {@link MutableAggregateChange} for deleting the root {@code entity}.
|
||||
* @return the {@link DeleteAggregateChange} for deleting the root {@code entity}.
|
||||
* @since 1.2
|
||||
*/
|
||||
static <T> MutableAggregateChange<T> forDelete(Class<T> entityClass) {
|
||||
static <T> DeleteAggregateChange<T> forDelete(Class<T> entityClass) {
|
||||
return forDelete(entityClass, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create an {@link MutableAggregateChange} for deleting entities.
|
||||
* Factory method to create a {@link DeleteAggregateChange} for deleting entities.
|
||||
*
|
||||
* @param entityClass aggregate root type.
|
||||
* @param previousVersion the previous version assigned to the instance being saved. May be {@literal null}.
|
||||
* @param <T> entity type.
|
||||
* @return the {@link MutableAggregateChange} for deleting the root {@code entity}.
|
||||
* @return the {@link DeleteAggregateChange} for deleting the root {@code entity}.
|
||||
* @since 2.4
|
||||
*/
|
||||
static <T> MutableAggregateChange<T> forDelete(Class<T> entityClass, @Nullable Number previousVersion) {
|
||||
static <T> DeleteAggregateChange<T> forDelete(Class<T> entityClass, @Nullable Number previousVersion) {
|
||||
|
||||
Assert.notNull(entityClass, "Entity class must not be null");
|
||||
|
||||
return new DefaultAggregateChange<>(Kind.DELETE, entityClass, previousVersion);
|
||||
return new DeleteAggregateChange<>(entityClass, previousVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
package org.springframework.data.relational.core.conversion;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.assertj.core.groups.Tuple;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link DeleteBatchingAggregateChange}.
|
||||
*
|
||||
* @author Chirag Tailor
|
||||
*/
|
||||
class DeleteBatchingAggregateChangeTest {
|
||||
|
||||
RelationalMappingContext context = new RelationalMappingContext();
|
||||
|
||||
@Test
|
||||
void yieldsDeleteActions() {
|
||||
|
||||
Root root = new Root(1L, null);
|
||||
DeleteAggregateChange<Root> aggregateChange = MutableAggregateChange.forDelete(root);
|
||||
DbAction.Delete<Intermediate> intermediateDelete = new DbAction.Delete<>(1L,
|
||||
context.getPersistentPropertyPath("intermediate", Root.class));
|
||||
aggregateChange.addAction(intermediateDelete);
|
||||
|
||||
BatchingAggregateChange<Root, DeleteAggregateChange<Root>> change = BatchingAggregateChange.forDelete(Root.class);
|
||||
change.add(aggregateChange);
|
||||
|
||||
assertThat(extractActions(change)).containsExactly(intermediateDelete);
|
||||
}
|
||||
|
||||
@Test
|
||||
void yieldsNestedDeleteActionsInTreeOrderFromLeavesToRoot() {
|
||||
|
||||
Root root = new Root(2L, null);
|
||||
DeleteAggregateChange<Root> aggregateChange = MutableAggregateChange.forDelete(root);
|
||||
DbAction.Delete<Intermediate> intermediateDelete = new DbAction.Delete<>(1L,
|
||||
context.getPersistentPropertyPath("intermediate", Root.class));
|
||||
aggregateChange.addAction(intermediateDelete);
|
||||
DbAction.Delete<?> leafDelete = new DbAction.Delete<>(1L,
|
||||
context.getPersistentPropertyPath("intermediate.leaf", Root.class));
|
||||
aggregateChange.addAction(leafDelete);
|
||||
|
||||
BatchingAggregateChange<Root, DeleteAggregateChange<Root>> change = BatchingAggregateChange.forDelete(Root.class);
|
||||
change.add(aggregateChange);
|
||||
|
||||
List<DbAction<?>> actions = extractActions(change);
|
||||
assertThat(actions).containsExactly(leafDelete, intermediateDelete);
|
||||
}
|
||||
|
||||
@Test
|
||||
void yieldsDeleteActionsAsBatchDeletes_groupedByPath_whenGroupContainsMultipleDeletes() {
|
||||
|
||||
Root root = new Root(1L, null);
|
||||
DeleteAggregateChange<Root> aggregateChange = MutableAggregateChange.forDelete(root);
|
||||
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, DeleteAggregateChange<Root>> change = BatchingAggregateChange.forDelete(Root.class);
|
||||
change.add(aggregateChange);
|
||||
|
||||
List<DbAction<?>> actions = extractActions(change);
|
||||
assertThat(actions).extracting(DbAction::getClass, DbAction::getEntityType) //
|
||||
.containsExactly(Tuple.tuple(DbAction.BatchDelete.class, Intermediate.class));
|
||||
assertThat(getBatchWithValueAction(actions, Intermediate.class, DbAction.BatchDelete.class).getActions())
|
||||
.containsExactly(intermediateDelete1, intermediateDelete2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void yieldsDeleteRootActions() {
|
||||
|
||||
DeleteAggregateChange<Root> aggregateChange = MutableAggregateChange.forDelete(new Root(null, null));
|
||||
DbAction.DeleteRoot<Root> deleteRoot = new DbAction.DeleteRoot<>(1L, Root.class, null);
|
||||
aggregateChange.addAction(deleteRoot);
|
||||
|
||||
BatchingAggregateChange<Root, DeleteAggregateChange<Root>> change = BatchingAggregateChange.forDelete(Root.class);
|
||||
change.add(aggregateChange);
|
||||
|
||||
assertThat(extractActions(change)).containsExactly(deleteRoot);
|
||||
}
|
||||
|
||||
@Test
|
||||
void yieldsDeleteRootActionsAfterDeleteActions() {
|
||||
|
||||
DeleteAggregateChange<Root> aggregateChange = MutableAggregateChange.forDelete(new Root(null, null));
|
||||
DbAction.DeleteRoot<Root> deleteRoot = new DbAction.DeleteRoot<>(1L, Root.class, null);
|
||||
aggregateChange.addAction(deleteRoot);
|
||||
DbAction.Delete<?> intermediateDelete = new DbAction.Delete<>(1L,
|
||||
context.getPersistentPropertyPath("intermediate", Root.class));
|
||||
aggregateChange.addAction(intermediateDelete);
|
||||
|
||||
BatchingAggregateChange<Root, DeleteAggregateChange<Root>> change = BatchingAggregateChange.forDelete(Root.class);
|
||||
change.add(aggregateChange);
|
||||
|
||||
assertThat(extractActions(change)).containsExactly(intermediateDelete, deleteRoot);
|
||||
}
|
||||
|
||||
@Test
|
||||
void yieldsLockRootActions() {
|
||||
|
||||
DeleteAggregateChange<Root> aggregateChange = MutableAggregateChange.forDelete(new Root(null, null));
|
||||
DbAction.AcquireLockRoot<Root> lockRootAction = new DbAction.AcquireLockRoot<>(1L, Root.class);
|
||||
aggregateChange.addAction(lockRootAction);
|
||||
|
||||
BatchingAggregateChange<Root, DeleteAggregateChange<Root>> change = BatchingAggregateChange.forDelete(Root.class);
|
||||
change.add(aggregateChange);
|
||||
|
||||
assertThat(extractActions(change)).containsExactly(lockRootAction);
|
||||
}
|
||||
|
||||
@Test
|
||||
void yieldsLockRootActionsBeforeDeleteActions() {
|
||||
|
||||
DeleteAggregateChange<Root> aggregateChange = MutableAggregateChange.forDelete(new Root(null, null));
|
||||
DbAction.Delete<?> intermediateDelete = new DbAction.Delete<>(1L,
|
||||
context.getPersistentPropertyPath("intermediate", Root.class));
|
||||
aggregateChange.addAction(intermediateDelete);
|
||||
DbAction.AcquireLockRoot<Root> lockRootAction = new DbAction.AcquireLockRoot<>(1L, Root.class);
|
||||
aggregateChange.addAction(lockRootAction);
|
||||
|
||||
BatchingAggregateChange<Root, DeleteAggregateChange<Root>> change = BatchingAggregateChange.forDelete(Root.class);
|
||||
change.add(aggregateChange);
|
||||
|
||||
assertThat(extractActions(change)).containsExactly(lockRootAction, intermediateDelete);
|
||||
}
|
||||
|
||||
private <T> List<DbAction<?>> extractActions(BatchingAggregateChange<T, ? extends MutableAggregateChange<T>> change) {
|
||||
|
||||
List<DbAction<?>> actions = new ArrayList<>();
|
||||
change.forEachAction(actions::add);
|
||||
return actions;
|
||||
}
|
||||
|
||||
private <T, A> DbAction.BatchWithValue<T, DbAction<T>, Object> getBatchWithValueAction(List<DbAction<?>> actions,
|
||||
Class<T> entityType, Class<A> batchActionType) {
|
||||
|
||||
return getBatchWithValueActions(actions, entityType, batchActionType).stream().findFirst()
|
||||
.orElseThrow(() -> new RuntimeException("No BatchWithValue action found!"));
|
||||
}
|
||||
|
||||
private <T, A> DbAction.BatchWithValue<T, DbAction<T>, Object> getBatchWithValueAction(List<DbAction<?>> actions,
|
||||
Class<T> entityType, Class<A> batchActionType, Object batchValue) {
|
||||
|
||||
return getBatchWithValueActions(actions, entityType, batchActionType).stream()
|
||||
.filter(batchWithValue -> batchWithValue.getBatchValue() == batchValue).findFirst().orElseThrow(
|
||||
() -> new RuntimeException(String.format("No BatchWithValue with batch value '%s' found!", batchValue)));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T, A> List<DbAction.BatchWithValue<T, DbAction<T>, Object>> getBatchWithValueActions(
|
||||
List<DbAction<?>> actions, Class<T> entityType, Class<A> batchActionType) {
|
||||
|
||||
return actions.stream() //
|
||||
.filter(dbAction -> dbAction.getClass().equals(batchActionType)) //
|
||||
.filter(dbAction -> dbAction.getEntityType().equals(entityType)) //
|
||||
.map(dbAction -> (DbAction.BatchWithValue<T, DbAction<T>, Object>) dbAction).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Value
|
||||
static class Root {
|
||||
@Id Long id;
|
||||
Intermediate intermediate;
|
||||
}
|
||||
|
||||
@Value
|
||||
static class Intermediate {
|
||||
@Id Long id;
|
||||
String name;
|
||||
Leaf leaf;
|
||||
}
|
||||
|
||||
@Value
|
||||
static class Leaf {
|
||||
@Id Long id;
|
||||
String name;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user