Update SaveBatchingAggregateChange to batch InsertRoot actions.

Original pull request #1228
This commit is contained in:
Chirag Tailor
2022-04-13 13:37:14 -05:00
committed by Jens Schauder
parent c1164383d6
commit ec3cec3be3
8 changed files with 318 additions and 68 deletions

View File

@@ -82,6 +82,8 @@ class AggregateChangeExecutor {
try {
if (action instanceof DbAction.InsertRoot) {
executionContext.executeInsertRoot((DbAction.InsertRoot<?>) action);
} else if (action instanceof DbAction.BatchInsertRoot<?>) {
executionContext.executeBatchInsertRoot((DbAction.BatchInsertRoot<?>) action);
} else if (action instanceof DbAction.Insert) {
executionContext.executeInsert((DbAction.Insert<?>) action);
} else if (action instanceof DbAction.BatchInsert) {

View File

@@ -75,6 +75,20 @@ class JdbcAggregateChangeExecutionContext {
add(new DbActionExecutionResult(insert, id));
}
<T> void executeBatchInsertRoot(DbAction.BatchInsertRoot<T> batchInsertRoot) {
List<DbAction.InsertRoot<T>> inserts = batchInsertRoot.getActions();
List<InsertSubject<T>> insertSubjects = inserts.stream()
.map(insert -> InsertSubject.describedBy(insert.getEntity(), Identifier.empty())).collect(Collectors.toList());
Object[] ids = accessStrategy.insert(insertSubjects, batchInsertRoot.getEntityType(),
batchInsertRoot.getBatchValue());
for (int i = 0; i < inserts.size(); i++) {
add(new DbActionExecutionResult(inserts.get(i), ids.length > 0 ? ids[i] : null));
}
}
<T> void executeInsert(DbAction.Insert<T> insert) {
Identifier parentKeys = getParentKeys(insert, converter);

View File

@@ -164,6 +164,32 @@ public class JdbcAggregateChangeExecutorContextUnitTests {
assertThat(content.id).isNull();
}
@Test // GH-537
void batchInsertRootOperation_withGeneratedIds() {
when(accessStrategy.insert(singletonList(InsertSubject.describedBy(root, Identifier.empty())), DummyEntity.class, IdValueSource.GENERATED))
.thenReturn(new Object[] { 123L });
executionContext.executeBatchInsertRoot(new DbAction.BatchInsertRoot<>(singletonList(new DbAction.InsertRoot<>(root, IdValueSource.GENERATED))));
List<DummyEntity> newRoots = executionContext.populateIdsIfNecessary();
assertThat(newRoots).containsExactly(root);
assertThat(root.id).isEqualTo(123L);
}
@Test // GH-537
void batchInsertRootOperation_withoutGeneratedIds() {
when(accessStrategy.insert(singletonList(InsertSubject.describedBy(root, Identifier.empty())), DummyEntity.class, IdValueSource.PROVIDED))
.thenReturn(new Object[] { null });
executionContext.executeBatchInsertRoot(new DbAction.BatchInsertRoot<>(singletonList(new DbAction.InsertRoot<>(root, IdValueSource.PROVIDED))));
List<DummyEntity> newRoots = executionContext.populateIdsIfNecessary();
assertThat(newRoots).containsExactly(root);
assertThat(root.id).isNull();
}
@Test // GH-1201
void updates_whenReferencesWithImmutableIdAreInserted() {
@@ -177,7 +203,8 @@ public class JdbcAggregateChangeExecutorContextUnitTests {
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(rootUpdate, "contentImmutableId", contentImmutableId, null, IdValueSource.GENERATED));
executionContext.executeInsert(
createInsert(rootUpdate, "contentImmutableId", contentImmutableId, null, IdValueSource.GENERATED));
List<DummyEntity> newRoots = executionContext.populateIdsIfNecessary();
assertThat(newRoots).containsExactly(root);
@@ -197,7 +224,6 @@ public class JdbcAggregateChangeExecutorContextUnitTests {
when(accessStrategy.insert(content1, Content.class, createBackRef(123L), IdValueSource.GENERATED)).thenReturn(11L);
executionContext.executeInsert(createInsert(rootUpdate1, "content", content1, null, IdValueSource.GENERATED));
DummyEntity root2 = new DummyEntity();
DbAction.InsertRoot<DummyEntity> rootInsert2 = new DbAction.InsertRoot<>(root2, IdValueSource.GENERATED);
when(accessStrategy.insert(root2, DummyEntity.class, Identifier.empty(), IdValueSource.GENERATED)).thenReturn(456L);

View File

@@ -410,6 +410,18 @@ public interface DbAction<T> {
}
}
/**
* Represents a batch insert statement for a multiple entities that are aggregate roots.
*
* @param <T> type of the entity for which this represents a database interaction.
* @since 3.0
*/
final class BatchInsertRoot<T> extends BatchWithValue<T, InsertRoot<T>, IdValueSource> {
public BatchInsertRoot(List<InsertRoot<T>> actions) {
super(actions, InsertRoot::getIdValueSource);
}
}
/**
* An action depending on another action for providing additional information like the id of a parent entity.
*

View File

@@ -43,7 +43,8 @@ public class SaveBatchingAggregateChange<T> implements BatchingAggregateChange<T
Comparator.comparing(PersistentPropertyPath::getLength);
private final Class<T> entityType;
private final List<DbAction.WithRoot<?>> rootActions = new ArrayList<>();
private final List<DbAction<?>> rootActions = new ArrayList<>();
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 = //
@@ -69,39 +70,59 @@ public class SaveBatchingAggregateChange<T> implements BatchingAggregateChange<T
Assert.notNull(consumer, "Consumer must not be null.");
rootActions.forEach(consumer);
if (insertRootBatchCandidates.size() > 1) {
consumer.accept(new DbAction.BatchInsertRoot<>(insertRootBatchCandidates));
} else {
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))));
insertActions.entrySet().stream().sorted(Map.Entry.comparingByKey(pathLengthComparator)).forEach((entry) -> entry
.getValue().forEach((idValueSource, inserts) -> consumer.accept(new DbAction.BatchInsert<>(inserts))));
}
@Override
public void add(RootAggregateChange<T> aggregateChange) {
aggregateChange.forEachAction(action -> {
if (action instanceof DbAction.WithRoot<?> rootAction) {
if (action instanceof DbAction.UpdateRoot<?> rootAction) {
commitBatchCandidates();
rootActions.add(rootAction);
} else if (action instanceof DbAction.Insert<?>) {
} else if (action instanceof DbAction.InsertRoot<?> rootAction) {
if (!insertRootBatchCandidates.isEmpty() && !insertRootBatchCandidates.get(0).getIdValueSource().equals(rootAction.getIdValueSource())) {
commitBatchCandidates();
}
//noinspection unchecked
insertRootBatchCandidates.add((DbAction.InsertRoot<T>) rootAction);
} else if (action instanceof DbAction.Insert<?> insertAction) {
// noinspection unchecked
addInsert((DbAction.Insert<Object>) action);
addInsert((DbAction.Insert<Object>) insertAction);
} else if (action instanceof DbAction.Delete<?> deleteAction) {
addDelete(deleteAction);
}
});
}
private void commitBatchCandidates() {
if (insertRootBatchCandidates.size() > 1) {
rootActions.add(new DbAction.BatchInsertRoot<>(List.copyOf(insertRootBatchCandidates)));
} else {
rootActions.addAll(insertRootBatchCandidates);
}
insertRootBatchCandidates.clear();
}
private void addInsert(DbAction.Insert<Object> action) {
PersistentPropertyPath<RelationalPersistentProperty> propertyPath = action.getPropertyPath();
insertActions.merge(propertyPath,
new HashMap<>(singletonMap(action.getIdValueSource(), new ArrayList<>(singletonList(action)))),
(map, mapDefaultValue) -> {
map.merge(action.getIdValueSource(), new ArrayList<>(singletonList(action)),
(actions, listDefaultValue) -> {
actions.add(action);
return actions;
});
map.merge(action.getIdValueSource(), new ArrayList<>(singletonList(action)), (actions, listDefaultValue) -> {
actions.add(action);
return actions;
});
return map;
});
}

View File

@@ -52,12 +52,12 @@ class DbActionTestSupport {
@Nullable
static IdValueSource insertIdValueSource(DbAction<?> action) {
if (action instanceof DbAction.InsertRoot) {
return ((DbAction.InsertRoot<?>) action).getIdValueSource();
} else if (action instanceof DbAction.Insert) {
return ((DbAction.Insert<?>) action).getIdValueSource();
if (action instanceof DbAction.WithEntity<?>) {
return ((DbAction.WithEntity<?>) action).getIdValueSource();
} else if (action instanceof DbAction.BatchInsert) {
return ((DbAction.BatchInsert<?>) action).getBatchValue();
} else if (action instanceof DbAction.BatchInsertRoot<?>) {
return ((DbAction.BatchInsertRoot<?>) action).getBatchValue();
} else {
return null;
}

View File

@@ -256,7 +256,7 @@ public class RelationalEntityWriterUnitTests {
DbActionTestSupport::isWithDependsOn, //
DbActionTestSupport::insertIdValueSource) //
.containsExactly( //
tuple(UpdateRoot.class, SingleReferenceEntity.class, "", SingleReferenceEntity.class, false, null), //
tuple(UpdateRoot.class, SingleReferenceEntity.class, "", SingleReferenceEntity.class, false, IdValueSource.PROVIDED), //
tuple(Delete.class, Element.class, "other", null, false, null), //
tuple(Insert.class, Element.class, "other", Element.class, true, IdValueSource.GENERATED) //
);
@@ -372,7 +372,7 @@ public class RelationalEntityWriterUnitTests {
DbActionTestSupport::isWithDependsOn, //
DbActionTestSupport::insertIdValueSource) //
.containsExactly( //
tuple(UpdateRoot.class, CascadingReferenceEntity.class, "", CascadingReferenceEntity.class, false, null), //
tuple(UpdateRoot.class, CascadingReferenceEntity.class, "", CascadingReferenceEntity.class, false, IdValueSource.PROVIDED), //
tuple(Delete.class, Element.class, "other.element", null, false, null),
tuple(Delete.class, CascadingReferenceMiddleElement.class, "other", null, false, null),
tuple(Insert.class, CascadingReferenceMiddleElement.class, "other", CascadingReferenceMiddleElement.class,
@@ -531,7 +531,7 @@ public class RelationalEntityWriterUnitTests {
DbActionTestSupport::extractPath, //
DbActionTestSupport::insertIdValueSource) //
.containsExactly( //
tuple(UpdateRoot.class, MapContainer.class, null, "", null), //
tuple(UpdateRoot.class, MapContainer.class, null, "", IdValueSource.PROVIDED), //
tuple(Delete.class, Element.class, null, "elements", null), //
tuple(Insert.class, Element.class, "one", "elements", IdValueSource.GENERATED) //
);
@@ -554,7 +554,7 @@ public class RelationalEntityWriterUnitTests {
DbActionTestSupport::extractPath, //
DbActionTestSupport::insertIdValueSource) //
.containsExactly( //
tuple(UpdateRoot.class, ListContainer.class, null, "", null), //
tuple(UpdateRoot.class, ListContainer.class, null, "", IdValueSource.PROVIDED), //
tuple(Delete.class, Element.class, null, "elements", null), //
tuple(Insert.class, Element.class, 0, "elements", IdValueSource.GENERATED) //
);
@@ -579,7 +579,7 @@ public class RelationalEntityWriterUnitTests {
DbActionTestSupport::extractPath, //
DbActionTestSupport::insertIdValueSource) //
.containsExactly( //
tuple(UpdateRoot.class, ListMapContainer.class, null, null, "", null), //
tuple(UpdateRoot.class, ListMapContainer.class, null, null, "", IdValueSource.PROVIDED), //
tuple(Delete.class, Element.class, null, null, "maps.elements", null), //
tuple(Delete.class, MapContainer.class, null, null, "maps", null), //
tuple(Insert.class, MapContainer.class, 0, null, "maps", IdValueSource.PROVIDED), //
@@ -607,7 +607,7 @@ public class RelationalEntityWriterUnitTests {
DbActionTestSupport::extractPath, //
DbActionTestSupport::insertIdValueSource) //
.containsExactly( //
tuple(UpdateRoot.class, NoIdListMapContainer.class, null, null, "", null), //
tuple(UpdateRoot.class, NoIdListMapContainer.class, null, null, "", IdValueSource.PROVIDED), //
tuple(Delete.class, NoIdElement.class, null, null, "maps.elements", null), //
tuple(Delete.class, NoIdMapContainer.class, null, null, "maps", null), //
tuple(Insert.class, NoIdMapContainer.class, 0, null, "maps", IdValueSource.NONE), //

View File

@@ -18,17 +18,18 @@ package org.springframework.data.relational.core.conversion;
import static java.util.Collections.*;
import static org.assertj.core.api.Assertions.*;
import lombok.Value;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.assertj.core.groups.Tuple;
import org.junit.jupiter.api.Nested;
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 SaveBatchingAggregateChange}.
*
@@ -46,23 +47,192 @@ class SaveBatchingAggregateChangeTest {
assertThat(extractActions(change)).isEmpty();
}
@Test
void yieldsRootActions() {
@Nested
class RootActionsTests {
@Test
void yieldsUpdateRoot() {
Root root1 = new Root(null, null);
DbAction.InsertRoot<Root> root1Insert = new DbAction.InsertRoot<>(root1, IdValueSource.GENERATED);
RootAggregateChange<Root> aggregateChange1 = MutableAggregateChange.forSave(root1);
aggregateChange1.setRootAction(root1Insert);
Root root2 = new Root(null, null);
DbAction.InsertRoot<Root> root2Insert = new DbAction.InsertRoot<>(root2, IdValueSource.GENERATED);
RootAggregateChange<Root> aggregateChange2 = MutableAggregateChange.forSave(root2);
aggregateChange2.setRootAction(root2Insert);
Root root = new Root(1L, null);
DbAction.UpdateRoot<Root> rootUpdate = new DbAction.UpdateRoot<>(root, null);
RootAggregateChange<Root> aggregateChange = MutableAggregateChange.forSave(root);
aggregateChange.setRootAction(rootUpdate);
BatchingAggregateChange<Root, RootAggregateChange<Root>> change = BatchingAggregateChange.forSave(Root.class);
change.add(aggregateChange1);
change.add(aggregateChange2);
BatchingAggregateChange<Root, RootAggregateChange<Root>> change = BatchingAggregateChange.forSave(Root.class);
change.add(aggregateChange);
assertThat(extractActions(change)).containsExactly(root1Insert, root2Insert);
assertThat(extractActions(change)).containsExactly(rootUpdate);
}
@Test
void yieldsSingleInsertRoot_followedByUpdateRoot_asIndividualActions() {
Root root1 = new Root(1L, null);
DbAction.InsertRoot<Root> root1Insert = new DbAction.InsertRoot<>(root1, IdValueSource.GENERATED);
RootAggregateChange<Root> aggregateChange1 = MutableAggregateChange.forSave(root1);
aggregateChange1.setRootAction(root1Insert);
Root root2 = new Root(1L, null);
DbAction.UpdateRoot<Root> root2Update = new DbAction.UpdateRoot<>(root2, null);
RootAggregateChange<Root> aggregateChange2 = MutableAggregateChange.forSave(root2);
aggregateChange2.setRootAction(root2Update);
BatchingAggregateChange<Root, RootAggregateChange<Root>> change = BatchingAggregateChange.forSave(Root.class);
change.add(aggregateChange1);
change.add(aggregateChange2);
assertThat(extractActions(change)) //
.extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::insertIdValueSource)
.containsExactly( //
Tuple.tuple(DbAction.InsertRoot.class, Root.class, IdValueSource.GENERATED), //
Tuple.tuple(DbAction.UpdateRoot.class, Root.class, IdValueSource.PROVIDED));
}
@Test
void yieldsMultipleMatchingInsertRoot_followedByUpdateRoot_asBatchInsertRootAction() {
Root root1 = new Root(1L, null);
DbAction.InsertRoot<Root> root1Insert = new DbAction.InsertRoot<>(root1, IdValueSource.GENERATED);
RootAggregateChange<Root> aggregateChange1 = MutableAggregateChange.forSave(root1);
aggregateChange1.setRootAction(root1Insert);
Root root2 = new Root(1L, null);
DbAction.InsertRoot<Root> root2Insert = new DbAction.InsertRoot<>(root2, IdValueSource.GENERATED);
RootAggregateChange<Root> aggregateChange2 = MutableAggregateChange.forSave(root2);
aggregateChange2.setRootAction(root2Insert);
Root root3 = new Root(1L, null);
DbAction.UpdateRoot<Root> root3Update = new DbAction.UpdateRoot<>(root3, null);
RootAggregateChange<Root> aggregateChange3 = MutableAggregateChange.forSave(root3);
aggregateChange3.setRootAction(root3Update);
BatchingAggregateChange<Root, RootAggregateChange<Root>> change = BatchingAggregateChange.forSave(Root.class);
change.add(aggregateChange1);
change.add(aggregateChange2);
change.add(aggregateChange3);
List<DbAction<?>> actions = extractActions(change);
assertThat(actions) //
.extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::insertIdValueSource)
.containsExactly( //
Tuple.tuple(DbAction.BatchInsertRoot.class, Root.class, IdValueSource.GENERATED), //
Tuple.tuple(DbAction.UpdateRoot.class, Root.class, IdValueSource.PROVIDED));
assertThat(getBatchWithValueAction(actions, Root.class, DbAction.BatchInsertRoot.class).getActions())
.containsExactly(root1Insert, root2Insert);
}
@Test
void yieldsInsertRoot() {
Root root = new Root(1L, null);
DbAction.InsertRoot<Root> rootInsert = new DbAction.InsertRoot<>(root, IdValueSource.GENERATED);
RootAggregateChange<Root> aggregateChange = MutableAggregateChange.forSave(root);
aggregateChange.setRootAction(rootInsert);
BatchingAggregateChange<Root, RootAggregateChange<Root>> change = BatchingAggregateChange.forSave(Root.class);
change.add(aggregateChange);
assertThat(extractActions(change)).containsExactly(rootInsert);
}
@Test
void yieldsSingleInsertRoot_followedByNonMatchingInsertRoot_asIndividualActions() {
Root root1 = new Root(1L, null);
DbAction.InsertRoot<Root> root1Insert = new DbAction.InsertRoot<>(root1, IdValueSource.GENERATED);
RootAggregateChange<Root> aggregateChange1 = MutableAggregateChange.forSave(root1);
aggregateChange1.setRootAction(root1Insert);
Root root2 = new Root(1L, null);
DbAction.InsertRoot<Root> root2Insert = new DbAction.InsertRoot<>(root2, IdValueSource.PROVIDED);
RootAggregateChange<Root> aggregateChange2 = MutableAggregateChange.forSave(root2);
aggregateChange2.setRootAction(root2Insert);
BatchingAggregateChange<Root, RootAggregateChange<Root>> change = BatchingAggregateChange.forSave(Root.class);
change.add(aggregateChange1);
change.add(aggregateChange2);
assertThat(extractActions(change)).containsExactly(root1Insert, root2Insert);
}
@Test
void yieldsMultipleMatchingInsertRoot_followedByNonMatchingInsertRoot_asBatchInsertRootAction() {
Root root1 = new Root(1L, null);
DbAction.InsertRoot<Root> root1Insert = new DbAction.InsertRoot<>(root1, IdValueSource.GENERATED);
RootAggregateChange<Root> aggregateChange1 = MutableAggregateChange.forSave(root1);
aggregateChange1.setRootAction(root1Insert);
Root root2 = new Root(1L, null);
DbAction.InsertRoot<Root> root2Insert = new DbAction.InsertRoot<>(root2, IdValueSource.GENERATED);
RootAggregateChange<Root> aggregateChange2 = MutableAggregateChange.forSave(root2);
aggregateChange2.setRootAction(root2Insert);
Root root3 = new Root(1L, null);
DbAction.InsertRoot<Root> root3Insert = new DbAction.InsertRoot<>(root3, IdValueSource.PROVIDED);
RootAggregateChange<Root> aggregateChange3 = MutableAggregateChange.forSave(root3);
aggregateChange3.setRootAction(root3Insert);
BatchingAggregateChange<Root, RootAggregateChange<Root>> change = BatchingAggregateChange.forSave(Root.class);
change.add(aggregateChange1);
change.add(aggregateChange2);
change.add(aggregateChange3);
List<DbAction<?>> actions = extractActions(change);
assertThat(actions) //
.extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::insertIdValueSource)
.containsExactly( //
Tuple.tuple(DbAction.BatchInsertRoot.class, Root.class, IdValueSource.GENERATED), //
Tuple.tuple(DbAction.InsertRoot.class, Root.class, IdValueSource.PROVIDED));
assertThat(getBatchWithValueAction(actions, Root.class, DbAction.BatchInsertRoot.class).getActions())
.containsExactly(root1Insert, root2Insert);
}
@Test
void yieldsMultipleMatchingInsertRoot_asBatchInsertRootAction() {
Root root1 = new Root(1L, null);
DbAction.InsertRoot<Root> root1Insert = new DbAction.InsertRoot<>(root1, IdValueSource.GENERATED);
RootAggregateChange<Root> aggregateChange1 = MutableAggregateChange.forSave(root1);
aggregateChange1.setRootAction(root1Insert);
Root root2 = new Root(1L, null);
DbAction.InsertRoot<Root> root2Insert = new DbAction.InsertRoot<>(root2, IdValueSource.GENERATED);
RootAggregateChange<Root> aggregateChange2 = MutableAggregateChange.forSave(root2);
aggregateChange2.setRootAction(root2Insert);
BatchingAggregateChange<Root, RootAggregateChange<Root>> change = BatchingAggregateChange.forSave(Root.class);
change.add(aggregateChange1);
change.add(aggregateChange2);
List<DbAction<?>> actions = extractActions(change);
assertThat(actions) //
.extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::insertIdValueSource)
.containsExactly(Tuple.tuple(DbAction.BatchInsertRoot.class, Root.class, IdValueSource.GENERATED));
assertThat(getBatchWithValueAction(actions, Root.class, DbAction.BatchInsertRoot.class).getActions())
.containsExactly(root1Insert, root2Insert);
}
@Test
void yieldsPreviouslyYieldedInsertRoot_asBatchInsertRootAction_whenAdditionalMatchingInsertRootIsAdded() {
Root root1 = new Root(1L, null);
DbAction.InsertRoot<Root> root1Insert = new DbAction.InsertRoot<>(root1, IdValueSource.GENERATED);
RootAggregateChange<Root> aggregateChange1 = MutableAggregateChange.forSave(root1);
aggregateChange1.setRootAction(root1Insert);
Root root2 = new Root(2L, null);
DbAction.InsertRoot<Root> root2Insert = new DbAction.InsertRoot<>(root2, IdValueSource.GENERATED);
RootAggregateChange<Root> aggregateChange2 = MutableAggregateChange.forSave(root2);
aggregateChange2.setRootAction(root2Insert);
BatchingAggregateChange<Root, RootAggregateChange<Root>> change = BatchingAggregateChange.forSave(Root.class);
change.add(aggregateChange1);
assertThat(extractActions(change)) //
.extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::insertIdValueSource)
.containsExactly(Tuple.tuple(DbAction.InsertRoot.class, Root.class, IdValueSource.GENERATED));
change.add(aggregateChange2);
List<DbAction<?>> actions = extractActions(change);
assertThat(actions) //
.extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::insertIdValueSource)
.containsExactly(Tuple.tuple(DbAction.BatchInsertRoot.class, Root.class, IdValueSource.GENERATED));
assertThat(getBatchWithValueAction(actions, Root.class, DbAction.BatchInsertRoot.class).getActions())
.containsExactly(root1Insert, root2Insert);
}
}
@Test
@@ -177,10 +347,10 @@ class SaveBatchingAggregateChangeTest {
Tuple.tuple(DbAction.InsertRoot.class, Root.class, IdValueSource.GENERATED), //
Tuple.tuple(DbAction.BatchInsert.class, Intermediate.class, IdValueSource.GENERATED)) //
.doesNotContain(Tuple.tuple(DbAction.Insert.class, Intermediate.class));
assertThat(getBatchInsertAction(actions, Intermediate.class, IdValueSource.GENERATED).getActions())
.containsExactly(intermediateInsertGeneratedId);
assertThat(getBatchInsertAction(actions, Intermediate.class, IdValueSource.PROVIDED).getActions())
.containsExactly(intermediateInsertProvidedId);
assertThat(getBatchWithValueAction(actions, Intermediate.class, DbAction.BatchInsert.class, IdValueSource.GENERATED)
.getActions()).containsExactly(intermediateInsertGeneratedId);
assertThat(getBatchWithValueAction(actions, Intermediate.class, DbAction.BatchInsert.class, IdValueSource.PROVIDED)
.getActions()).containsExactly(intermediateInsertProvidedId);
}
@Test
@@ -221,9 +391,9 @@ class SaveBatchingAggregateChangeTest {
.containsSubsequence( //
Tuple.tuple(DbAction.BatchInsert.class, Intermediate.class, IdValueSource.GENERATED),
Tuple.tuple(DbAction.BatchInsert.class, Leaf.class, IdValueSource.GENERATED));
assertThat(getBatchInsertAction(actions, Intermediate.class).getActions()) //
assertThat(getBatchWithValueAction(actions, Intermediate.class, DbAction.BatchInsert.class).getActions()) //
.containsExactly(root1IntermediateInsert, root2IntermediateInsert);
assertThat(getBatchInsertAction(actions, Leaf.class).getActions()) //
assertThat(getBatchWithValueAction(actions, Leaf.class, DbAction.BatchInsert.class).getActions()) //
.containsExactly(root1LeafInsert);
}
@@ -256,33 +426,13 @@ class SaveBatchingAggregateChangeTest {
.containsSubsequence( //
Tuple.tuple(DbAction.BatchInsert.class, Intermediate.class, IdValueSource.GENERATED),
Tuple.tuple(DbAction.BatchInsert.class, Intermediate.class, IdValueSource.GENERATED));
List<DbAction.BatchInsert<Intermediate>> batchInsertActions = getBatchInsertActions(actions, Intermediate.class);
List<DbAction.BatchWithValue<Intermediate, DbAction<Intermediate>, Object>> batchInsertActions = getBatchWithValueActions(actions, Intermediate.class,
DbAction.BatchInsert.class);
assertThat(batchInsertActions).hasSize(2);
assertThat(batchInsertActions.get(0).getActions()).containsExactly(oneInsert);
assertThat(batchInsertActions.get(1).getActions()).containsExactly(twoInsert);
}
private <T> DbAction.BatchInsert<T> getBatchInsertAction(List<DbAction<?>> actions, Class<T> entityType,
IdValueSource idValueSource) {
return getBatchInsertActions(actions, entityType).stream()
.filter(batchInsert -> batchInsert.getBatchValue() == idValueSource).findFirst().orElseThrow(
() -> new RuntimeException(String.format("No BatchInsert with batch value '%s' found!", idValueSource)));
}
private <T> DbAction.BatchInsert<T> getBatchInsertAction(List<DbAction<?>> actions, Class<T> entityType) {
return getBatchInsertActions(actions, entityType).stream().findFirst()
.orElseThrow(() -> new RuntimeException("No BatchInsert action found!"));
}
@SuppressWarnings("unchecked")
private <T> List<DbAction.BatchInsert<T>> getBatchInsertActions(List<DbAction<?>> actions, Class<T> entityType) {
return actions.stream() //
.filter(dbAction -> dbAction instanceof DbAction.BatchInsert) //
.filter(dbAction -> dbAction.getEntityType().equals(entityType)) //
.map(dbAction -> (DbAction.BatchInsert<T>) dbAction).collect(Collectors.toList());
}
private <T> List<DbAction<?>> extractActions(BatchingAggregateChange<T, RootAggregateChange<T>> change) {
List<DbAction<?>> actions = new ArrayList<>();
@@ -290,6 +440,31 @@ class SaveBatchingAggregateChangeTest {
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 RootWithSameLengthReferences {
@Id Long id;