Skip to content

Commit ec3cec3

Browse files
ctailor2schauder
authored andcommitted
Update SaveBatchingAggregateChange to batch InsertRoot actions.
Original pull request #1228
1 parent c116438 commit ec3cec3

File tree

8 files changed

+318
-68
lines changed

8 files changed

+318
-68
lines changed

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/AggregateChangeExecutor.java

+2
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ private void execute(DbAction<?> action, JdbcAggregateChangeExecutionContext exe
8282
try {
8383
if (action instanceof DbAction.InsertRoot) {
8484
executionContext.executeInsertRoot((DbAction.InsertRoot<?>) action);
85+
} else if (action instanceof DbAction.BatchInsertRoot<?>) {
86+
executionContext.executeBatchInsertRoot((DbAction.BatchInsertRoot<?>) action);
8587
} else if (action instanceof DbAction.Insert) {
8688
executionContext.executeInsert((DbAction.Insert<?>) action);
8789
} else if (action instanceof DbAction.BatchInsert) {

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutionContext.java

+14
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,20 @@ <T> void executeInsertRoot(DbAction.InsertRoot<T> insert) {
7575
add(new DbActionExecutionResult(insert, id));
7676
}
7777

78+
<T> void executeBatchInsertRoot(DbAction.BatchInsertRoot<T> batchInsertRoot) {
79+
80+
List<DbAction.InsertRoot<T>> inserts = batchInsertRoot.getActions();
81+
List<InsertSubject<T>> insertSubjects = inserts.stream()
82+
.map(insert -> InsertSubject.describedBy(insert.getEntity(), Identifier.empty())).collect(Collectors.toList());
83+
84+
Object[] ids = accessStrategy.insert(insertSubjects, batchInsertRoot.getEntityType(),
85+
batchInsertRoot.getBatchValue());
86+
87+
for (int i = 0; i < inserts.size(); i++) {
88+
add(new DbActionExecutionResult(inserts.get(i), ids.length > 0 ? ids[i] : null));
89+
}
90+
}
91+
7892
<T> void executeInsert(DbAction.Insert<T> insert) {
7993

8094
Identifier parentKeys = getParentKeys(insert, converter);

spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutorContextUnitTests.java

+28-2
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,32 @@ void batchInsertOperation_withoutGeneratedIds() {
164164
assertThat(content.id).isNull();
165165
}
166166

167+
@Test // GH-537
168+
void batchInsertRootOperation_withGeneratedIds() {
169+
170+
when(accessStrategy.insert(singletonList(InsertSubject.describedBy(root, Identifier.empty())), DummyEntity.class, IdValueSource.GENERATED))
171+
.thenReturn(new Object[] { 123L });
172+
executionContext.executeBatchInsertRoot(new DbAction.BatchInsertRoot<>(singletonList(new DbAction.InsertRoot<>(root, IdValueSource.GENERATED))));
173+
174+
List<DummyEntity> newRoots = executionContext.populateIdsIfNecessary();
175+
176+
assertThat(newRoots).containsExactly(root);
177+
assertThat(root.id).isEqualTo(123L);
178+
}
179+
180+
@Test // GH-537
181+
void batchInsertRootOperation_withoutGeneratedIds() {
182+
183+
when(accessStrategy.insert(singletonList(InsertSubject.describedBy(root, Identifier.empty())), DummyEntity.class, IdValueSource.PROVIDED))
184+
.thenReturn(new Object[] { null });
185+
executionContext.executeBatchInsertRoot(new DbAction.BatchInsertRoot<>(singletonList(new DbAction.InsertRoot<>(root, IdValueSource.PROVIDED))));
186+
187+
List<DummyEntity> newRoots = executionContext.populateIdsIfNecessary();
188+
189+
assertThat(newRoots).containsExactly(root);
190+
assertThat(root.id).isNull();
191+
}
192+
167193
@Test // GH-1201
168194
void updates_whenReferencesWithImmutableIdAreInserted() {
169195

@@ -177,7 +203,8 @@ void updates_whenReferencesWithImmutableIdAreInserted() {
177203
Identifier identifier = Identifier.empty().withPart(SqlIdentifier.quoted("DUMMY_ENTITY"), 123L, Long.class);
178204
when(accessStrategy.insert(contentImmutableId, ContentImmutableId.class, identifier, IdValueSource.GENERATED))
179205
.thenReturn(456L);
180-
executionContext.executeInsert(createInsert(rootUpdate, "contentImmutableId", contentImmutableId, null, IdValueSource.GENERATED));
206+
executionContext.executeInsert(
207+
createInsert(rootUpdate, "contentImmutableId", contentImmutableId, null, IdValueSource.GENERATED));
181208

182209
List<DummyEntity> newRoots = executionContext.populateIdsIfNecessary();
183210
assertThat(newRoots).containsExactly(root);
@@ -197,7 +224,6 @@ void populatesIdsIfNecessaryForAllRootsThatWereProcessed() {
197224
when(accessStrategy.insert(content1, Content.class, createBackRef(123L), IdValueSource.GENERATED)).thenReturn(11L);
198225
executionContext.executeInsert(createInsert(rootUpdate1, "content", content1, null, IdValueSource.GENERATED));
199226

200-
201227
DummyEntity root2 = new DummyEntity();
202228
DbAction.InsertRoot<DummyEntity> rootInsert2 = new DbAction.InsertRoot<>(root2, IdValueSource.GENERATED);
203229
when(accessStrategy.insert(root2, DummyEntity.class, Identifier.empty(), IdValueSource.GENERATED)).thenReturn(456L);

spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DbAction.java

+12
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,18 @@ public BatchInsert(List<Insert<T>> actions) {
410410
}
411411
}
412412

413+
/**
414+
* Represents a batch insert statement for a multiple entities that are aggregate roots.
415+
*
416+
* @param <T> type of the entity for which this represents a database interaction.
417+
* @since 3.0
418+
*/
419+
final class BatchInsertRoot<T> extends BatchWithValue<T, InsertRoot<T>, IdValueSource> {
420+
public BatchInsertRoot(List<InsertRoot<T>> actions) {
421+
super(actions, InsertRoot::getIdValueSource);
422+
}
423+
}
424+
413425
/**
414426
* An action depending on another action for providing additional information like the id of a parent entity.
415427
*

spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/SaveBatchingAggregateChange.java

+33-12
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ public class SaveBatchingAggregateChange<T> implements BatchingAggregateChange<T
4343
Comparator.comparing(PersistentPropertyPath::getLength);
4444

4545
private final Class<T> entityType;
46-
private final List<DbAction.WithRoot<?>> rootActions = new ArrayList<>();
46+
private final List<DbAction<?>> rootActions = new ArrayList<>();
47+
private final List<DbAction.InsertRoot<T>> insertRootBatchCandidates = new ArrayList<>();
4748
private final Map<PersistentPropertyPath<RelationalPersistentProperty>, Map<IdValueSource, List<DbAction.Insert<Object>>>> insertActions = //
4849
new HashMap<>();
4950
private final Map<PersistentPropertyPath<RelationalPersistentProperty>, List<DbAction.Delete<?>>> deleteActions = //
@@ -69,39 +70,59 @@ public void forEachAction(Consumer<? super DbAction<?>> consumer) {
6970
Assert.notNull(consumer, "Consumer must not be null.");
7071

7172
rootActions.forEach(consumer);
73+
if (insertRootBatchCandidates.size() > 1) {
74+
consumer.accept(new DbAction.BatchInsertRoot<>(insertRootBatchCandidates));
75+
} else {
76+
insertRootBatchCandidates.forEach(consumer);
77+
}
7278
deleteActions.entrySet().stream().sorted(Map.Entry.comparingByKey(pathLengthComparator.reversed()))
7379
.forEach((entry) -> entry.getValue().forEach(consumer));
74-
insertActions.entrySet().stream().sorted(Map.Entry.comparingByKey(pathLengthComparator))
75-
.forEach((entry) -> entry.getValue()
76-
.forEach((idValueSource, inserts) -> consumer.accept(new DbAction.BatchInsert<>(inserts))));
80+
insertActions.entrySet().stream().sorted(Map.Entry.comparingByKey(pathLengthComparator)).forEach((entry) -> entry
81+
.getValue().forEach((idValueSource, inserts) -> consumer.accept(new DbAction.BatchInsert<>(inserts))));
7782
}
7883

7984
@Override
8085
public void add(RootAggregateChange<T> aggregateChange) {
8186

8287
aggregateChange.forEachAction(action -> {
83-
if (action instanceof DbAction.WithRoot<?> rootAction) {
88+
if (action instanceof DbAction.UpdateRoot<?> rootAction) {
89+
commitBatchCandidates();
8490
rootActions.add(rootAction);
85-
} else if (action instanceof DbAction.Insert<?>) {
91+
} else if (action instanceof DbAction.InsertRoot<?> rootAction) {
92+
if (!insertRootBatchCandidates.isEmpty() && !insertRootBatchCandidates.get(0).getIdValueSource().equals(rootAction.getIdValueSource())) {
93+
commitBatchCandidates();
94+
}
95+
//noinspection unchecked
96+
insertRootBatchCandidates.add((DbAction.InsertRoot<T>) rootAction);
97+
} else if (action instanceof DbAction.Insert<?> insertAction) {
8698
// noinspection unchecked
87-
addInsert((DbAction.Insert<Object>) action);
99+
addInsert((DbAction.Insert<Object>) insertAction);
88100
} else if (action instanceof DbAction.Delete<?> deleteAction) {
89101
addDelete(deleteAction);
90102
}
91103
});
92104
}
93105

106+
private void commitBatchCandidates() {
107+
108+
if (insertRootBatchCandidates.size() > 1) {
109+
rootActions.add(new DbAction.BatchInsertRoot<>(List.copyOf(insertRootBatchCandidates)));
110+
} else {
111+
rootActions.addAll(insertRootBatchCandidates);
112+
}
113+
insertRootBatchCandidates.clear();
114+
}
115+
94116
private void addInsert(DbAction.Insert<Object> action) {
95117

96118
PersistentPropertyPath<RelationalPersistentProperty> propertyPath = action.getPropertyPath();
97119
insertActions.merge(propertyPath,
98120
new HashMap<>(singletonMap(action.getIdValueSource(), new ArrayList<>(singletonList(action)))),
99121
(map, mapDefaultValue) -> {
100-
map.merge(action.getIdValueSource(), new ArrayList<>(singletonList(action)),
101-
(actions, listDefaultValue) -> {
102-
actions.add(action);
103-
return actions;
104-
});
122+
map.merge(action.getIdValueSource(), new ArrayList<>(singletonList(action)), (actions, listDefaultValue) -> {
123+
actions.add(action);
124+
return actions;
125+
});
105126
return map;
106127
});
107128
}

spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/DbActionTestSupport.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,12 @@ static Class<?> actualEntityType(DbAction a) {
5252
@Nullable
5353
static IdValueSource insertIdValueSource(DbAction<?> action) {
5454

55-
if (action instanceof DbAction.InsertRoot) {
56-
return ((DbAction.InsertRoot<?>) action).getIdValueSource();
57-
} else if (action instanceof DbAction.Insert) {
58-
return ((DbAction.Insert<?>) action).getIdValueSource();
55+
if (action instanceof DbAction.WithEntity<?>) {
56+
return ((DbAction.WithEntity<?>) action).getIdValueSource();
5957
} else if (action instanceof DbAction.BatchInsert) {
6058
return ((DbAction.BatchInsert<?>) action).getBatchValue();
59+
} else if (action instanceof DbAction.BatchInsertRoot<?>) {
60+
return ((DbAction.BatchInsertRoot<?>) action).getBatchValue();
6161
} else {
6262
return null;
6363
}

spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityWriterUnitTests.java

+6-6
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ public void newReferenceTriggersDeletePlusInsert() {
256256
DbActionTestSupport::isWithDependsOn, //
257257
DbActionTestSupport::insertIdValueSource) //
258258
.containsExactly( //
259-
tuple(UpdateRoot.class, SingleReferenceEntity.class, "", SingleReferenceEntity.class, false, null), //
259+
tuple(UpdateRoot.class, SingleReferenceEntity.class, "", SingleReferenceEntity.class, false, IdValueSource.PROVIDED), //
260260
tuple(Delete.class, Element.class, "other", null, false, null), //
261261
tuple(Insert.class, Element.class, "other", Element.class, true, IdValueSource.GENERATED) //
262262
);
@@ -372,7 +372,7 @@ public void cascadingReferencesTriggerCascadingActionsForUpdate() {
372372
DbActionTestSupport::isWithDependsOn, //
373373
DbActionTestSupport::insertIdValueSource) //
374374
.containsExactly( //
375-
tuple(UpdateRoot.class, CascadingReferenceEntity.class, "", CascadingReferenceEntity.class, false, null), //
375+
tuple(UpdateRoot.class, CascadingReferenceEntity.class, "", CascadingReferenceEntity.class, false, IdValueSource.PROVIDED), //
376376
tuple(Delete.class, Element.class, "other.element", null, false, null),
377377
tuple(Delete.class, CascadingReferenceMiddleElement.class, "other", null, false, null),
378378
tuple(Insert.class, CascadingReferenceMiddleElement.class, "other", CascadingReferenceMiddleElement.class,
@@ -531,7 +531,7 @@ public void mapTriggersDeletePlusInsert() {
531531
DbActionTestSupport::extractPath, //
532532
DbActionTestSupport::insertIdValueSource) //
533533
.containsExactly( //
534-
tuple(UpdateRoot.class, MapContainer.class, null, "", null), //
534+
tuple(UpdateRoot.class, MapContainer.class, null, "", IdValueSource.PROVIDED), //
535535
tuple(Delete.class, Element.class, null, "elements", null), //
536536
tuple(Insert.class, Element.class, "one", "elements", IdValueSource.GENERATED) //
537537
);
@@ -554,7 +554,7 @@ public void listTriggersDeletePlusInsert() {
554554
DbActionTestSupport::extractPath, //
555555
DbActionTestSupport::insertIdValueSource) //
556556
.containsExactly( //
557-
tuple(UpdateRoot.class, ListContainer.class, null, "", null), //
557+
tuple(UpdateRoot.class, ListContainer.class, null, "", IdValueSource.PROVIDED), //
558558
tuple(Delete.class, Element.class, null, "elements", null), //
559559
tuple(Insert.class, Element.class, 0, "elements", IdValueSource.GENERATED) //
560560
);
@@ -579,7 +579,7 @@ public void multiLevelQualifiedReferencesWithId() {
579579
DbActionTestSupport::extractPath, //
580580
DbActionTestSupport::insertIdValueSource) //
581581
.containsExactly( //
582-
tuple(UpdateRoot.class, ListMapContainer.class, null, null, "", null), //
582+
tuple(UpdateRoot.class, ListMapContainer.class, null, null, "", IdValueSource.PROVIDED), //
583583
tuple(Delete.class, Element.class, null, null, "maps.elements", null), //
584584
tuple(Delete.class, MapContainer.class, null, null, "maps", null), //
585585
tuple(Insert.class, MapContainer.class, 0, null, "maps", IdValueSource.PROVIDED), //
@@ -607,7 +607,7 @@ public void multiLevelQualifiedReferencesWithOutId() {
607607
DbActionTestSupport::extractPath, //
608608
DbActionTestSupport::insertIdValueSource) //
609609
.containsExactly( //
610-
tuple(UpdateRoot.class, NoIdListMapContainer.class, null, null, "", null), //
610+
tuple(UpdateRoot.class, NoIdListMapContainer.class, null, null, "", IdValueSource.PROVIDED), //
611611
tuple(Delete.class, NoIdElement.class, null, null, "maps.elements", null), //
612612
tuple(Delete.class, NoIdMapContainer.class, null, null, "maps", null), //
613613
tuple(Insert.class, NoIdMapContainer.class, 0, null, "maps", IdValueSource.NONE), //

0 commit comments

Comments
 (0)