Skip to content

Commit e80798f

Browse files
committed
Address review feedback.
1 parent 12f219f commit e80798f

File tree

7 files changed

+144
-99
lines changed

7 files changed

+144
-99
lines changed

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ <T> List<T> populateIdsIfNecessary() {
224224

225225
StagedValues cascadingValues = new StagedValues();
226226

227-
List<T> roots = new ArrayList<>();
227+
List<T> roots = new ArrayList<>(reverseResults.size());
228228

229229
for (DbActionExecutionResult result : reverseResults) {
230230

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

+44-34
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.data.jdbc.core;
1717

1818
import java.util.ArrayList;
19+
import java.util.Iterator;
1920
import java.util.List;
2021
import java.util.function.Function;
2122
import java.util.stream.Collectors;
@@ -32,7 +33,7 @@
3233
import org.springframework.data.mapping.callback.EntityCallbacks;
3334
import org.springframework.data.relational.core.conversion.AggregateChange;
3435
import org.springframework.data.relational.core.conversion.AggregateChangeWithRoot;
35-
import org.springframework.data.relational.core.conversion.MergedAggregateChange;
36+
import org.springframework.data.relational.core.conversion.BatchingAggregateChange;
3637
import org.springframework.data.relational.core.conversion.MutableAggregateChange;
3738
import org.springframework.data.relational.core.conversion.RelationalEntityDeleteWriter;
3839
import org.springframework.data.relational.core.conversion.RelationalEntityInsertWriter;
@@ -47,8 +48,6 @@
4748
import org.springframework.util.Assert;
4849
import org.springframework.util.ClassUtils;
4950

50-
import static java.util.Collections.*;
51-
5251
/**
5352
* {@link JdbcAggregateOperations} implementation, storing aggregates in and obtaining them from a JDBC data store.
5453
*
@@ -138,14 +137,15 @@ public <T> T save(T instance) {
138137

139138
Assert.notNull(instance, "Aggregate instance must not be null!");
140139

141-
return StreamSupport.stream(saveAll(singletonList(instance)).spliterator(), false).findFirst()
142-
.orElseThrow(() -> new IllegalStateException(
143-
String.format("Unable to retrieve the result of executing aggregate change for instance: %s", instance)));
140+
return performSave(instance, changeCreatorSelectorForSave(instance));
144141
}
145142

146143
@Override
147144
public <T> Iterable<T> saveAll(Iterable<T> instances) {
148-
return performSaveChange(instances, this::changeCreatorSelectorForSave);
145+
146+
Assert.isTrue(instances.iterator().hasNext(), "Aggregate instances must not be empty!");
147+
148+
return performSaveAll(instances);
149149
}
150150

151151
/**
@@ -160,9 +160,7 @@ public <T> T insert(T instance) {
160160

161161
Assert.notNull(instance, "Aggregate instance must not be null!");
162162

163-
return performSaveChange(singletonList(instance), this::changeCreatorSelectorForInsert).stream().findFirst()
164-
.orElseThrow(() -> new IllegalStateException(
165-
String.format("Unable to retrieve the result of executing aggregate change for instance: %s", instance)));
163+
return performSave(instance, entity -> createInsertChange(prepareVersionForInsert(entity)));
166164
}
167165

168166
/**
@@ -177,9 +175,7 @@ public <T> T update(T instance) {
177175

178176
Assert.notNull(instance, "Aggregate instance must not be null!");
179177

180-
return performSaveChange(singletonList(instance), this::changeCreatorSelectorForUpdate).stream().findFirst()
181-
.orElseThrow(() -> new IllegalStateException(
182-
String.format("Unable to retrieve the result of executing aggregate change for instance: %s", instance)));
178+
return performSave(instance, entity -> createUpdateChange(prepareVersionForUpdate(entity)));
183179
}
184180

185181
@Override
@@ -318,36 +314,50 @@ private <T> void deleteTree(Object id, @Nullable T entity, Class<T> domainType)
318314
triggerAfterDelete(entity, id, change);
319315
}
320316

321-
private <T> List<T> performSaveChange(Iterable<T> instances, Function<T, Function<T, AggregateChangeWithRoot<T>>> changeCreatorSelector) {
322-
323-
ArrayList<T> instancesList = new ArrayList<>();
324-
instances.forEach(instancesList::add);
325-
Assert.notEmpty(instancesList, "Aggregate instances must not be empty!");
317+
private <T> T performSave(T instance, Function<T, AggregateChangeWithRoot<T>> changeCreator) {
326318

327319
// noinspection unchecked
328-
MergedAggregateChange<T, AggregateChangeWithRoot<T>> mergedAggregateChange = instancesList.stream() //
329-
.map(instance -> beforeExecute(instance, changeCreatorSelector.apply(instance))) //
330-
.reduce(MutableAggregateChange.mergedSave((Class<T>) ClassUtils.getUserClass(instancesList.get(0))),
331-
MergedAggregateChange::merge, (left, right) -> right);
320+
BatchingAggregateChange<T, AggregateChangeWithRoot<T>> batchingAggregateChange = //
321+
BatchingAggregateChange.forSave((Class<T>) ClassUtils.getUserClass(instance));
322+
batchingAggregateChange.add(beforeExecute(instance, changeCreator));
332323

333-
return executor.executeSave(mergedAggregateChange).stream()
334-
.map(entityAfterExecution -> afterExecute(mergedAggregateChange, entityAfterExecution))
335-
.collect(Collectors.toList());
336-
}
324+
Iterator<T> afterExecutionIterator = executor.executeSave(batchingAggregateChange).iterator();
337325

338-
private <T> Function<T, AggregateChangeWithRoot<T>> changeCreatorSelectorForSave(T instance) {
326+
Assert.isTrue(afterExecutionIterator.hasNext(), "Instances after execution must not be empty!");
339327

340-
return context.getRequiredPersistentEntity(instance.getClass()).isNew(instance)
341-
? changeCreatorSelectorForInsert(instance)
342-
: changeCreatorSelectorForUpdate(instance);
328+
return afterExecute(batchingAggregateChange, afterExecutionIterator.next());
343329
}
344330

345-
private <T> Function<T, AggregateChangeWithRoot<T>> changeCreatorSelectorForInsert(T instance) {
346-
return entity -> createInsertChange(prepareVersionForInsert(entity));
331+
private <T> List<T> performSaveAll(Iterable<T> instances) {
332+
333+
Iterator<T> iterator = instances.iterator();
334+
T firstInstance = iterator.next();
335+
336+
// noinspection unchecked
337+
BatchingAggregateChange<T, AggregateChangeWithRoot<T>> batchingAggregateChange = //
338+
BatchingAggregateChange.forSave((Class<T>) ClassUtils.getUserClass(firstInstance));
339+
batchingAggregateChange.add(beforeExecute(firstInstance, changeCreatorSelectorForSave(firstInstance)));
340+
341+
while (iterator.hasNext()) {
342+
T instance = iterator.next();
343+
batchingAggregateChange.add(beforeExecute(instance, changeCreatorSelectorForSave(instance)));
344+
}
345+
346+
List<T> instancesAfterExecution = executor.executeSave(batchingAggregateChange);
347+
348+
ArrayList<T> results = new ArrayList<>(instancesAfterExecution.size());
349+
for (T instance : instancesAfterExecution) {
350+
results.add(afterExecute(batchingAggregateChange, instance));
351+
}
352+
353+
return results;
347354
}
348355

349-
private <T> Function<T, AggregateChangeWithRoot<T>> changeCreatorSelectorForUpdate(T instance) {
350-
return entity -> createUpdateChange(prepareVersionForUpdate(entity));
356+
private <T> Function<T, AggregateChangeWithRoot<T>> changeCreatorSelectorForSave(T instance) {
357+
358+
return context.getRequiredPersistentEntity(instance.getClass()).isNew(instance)
359+
? entity -> createInsertChange(prepareVersionForInsert(entity))
360+
: entity -> createUpdateChange(prepareVersionForUpdate(entity));
351361
}
352362

353363
private <T> AggregateChangeWithRoot<T> createInsertChange(T instance) {

spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java

+6-4
Original file line numberDiff line numberDiff line change
@@ -601,7 +601,7 @@ void manyInsertsWithNestedEntities() {
601601

602602
List<Root> savedRoots = rootRepository.saveAll(asList(root1, root2));
603603

604-
List<Root> reloadedRoots = rootRepository.findAll();
604+
List<Root> reloadedRoots = rootRepository.findAllByOrderByIdAsc();
605605
assertThat(reloadedRoots).isEqualTo(savedRoots);
606606
assertThat(reloadedRoots).hasSize(2);
607607
assertIsEqualToWithNonNullIds(reloadedRoots.get(0), root1);
@@ -628,7 +628,7 @@ void manyUpdatesWithNestedEntities() {
628628

629629
List<Root> updatedRoots = rootRepository.saveAll(asList(updatedRoot1, updatedRoot2));
630630

631-
List<Root> reloadedRoots = rootRepository.findAll();
631+
List<Root> reloadedRoots = rootRepository.findAllByOrderByIdAsc();
632632
assertThat(reloadedRoots).isEqualTo(updatedRoots);
633633
assertThat(reloadedRoots).containsExactly(updatedRoot1, updatedRoot2);
634634
}
@@ -645,7 +645,7 @@ void manyInsertsAndUpdatesWithNestedEntities() {
645645
Root root2 = createRoot("root2");
646646
List<Root> savedRoots = rootRepository.saveAll(asList(updatedRoot1, root2));
647647

648-
List<Root> reloadedRoots = rootRepository.findAll();
648+
List<Root> reloadedRoots = rootRepository.findAllByOrderByIdAsc();
649649
assertThat(reloadedRoots).isEqualTo(savedRoots);
650650
assertThat(reloadedRoots.get(0)).isEqualTo(updatedRoot1);
651651
assertIsEqualToWithNonNullIds(reloadedRoots.get(1), root2);
@@ -795,7 +795,9 @@ MyEventListener eventListener() {
795795
}
796796
}
797797

798-
interface RootRepository extends ListCrudRepository<Root, Long> {}
798+
interface RootRepository extends ListCrudRepository<Root, Long> {
799+
List<Root> findAllByOrderByIdAsc();
800+
}
799801

800802
@Value
801803
static class Root {
+22-6
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,36 @@
1515
*/
1616
package org.springframework.data.relational.core.conversion;
1717

18+
import org.springframework.util.Assert;
19+
1820
/**
1921
* Represents the changes happening to one or more aggregates (as used in the context of Domain Driven Design) as a
20-
* whole. This change allows additional {@link MutableAggregateChange} of a particular kind to be merged into it to
21-
* broadly represent the changes to multiple aggregates across all such merged changes.
22+
* whole. This change allows additional {@link MutableAggregateChange} of a particular kind to be added to it to
23+
* broadly represent the changes to multiple aggregates across all such added changes.
2224
*
2325
* @author Chirag Tailor
2426
* @since 3.0
2527
*/
26-
public interface MergedAggregateChange<T, C extends MutableAggregateChange<T>> extends AggregateChange<T> {
28+
public interface BatchingAggregateChange<T, C extends MutableAggregateChange<T>> extends AggregateChange<T> {
2729
/**
28-
* Merges a {@code MutableAggregateChange} into this {@code MergedAggregateChange}.
30+
* Adds a {@code MutableAggregateChange} into this {@code BatchingAggregateChange}.
2931
*
3032
* @param aggregateChange must not be {@literal null}.
31-
* @return the change resulting from the merge. Guaranteed to be not {@literal null}.
3233
*/
33-
MergedAggregateChange<T, C> merge(C aggregateChange);
34+
void add(C aggregateChange);
35+
36+
/**
37+
* Factory method to create a {@link BatchingAggregateChange} for saving entities.
38+
*
39+
* @param entityClass aggregate root type.
40+
* @param <T> entity type.
41+
* @return the {@link BatchingAggregateChange} for saving root entities.
42+
* @since 3.0
43+
*/
44+
static <T> BatchingAggregateChange<T, AggregateChangeWithRoot<T>> forSave(Class<T> entityClass) {
45+
46+
Assert.notNull(entityClass, "Entity class must not be null");
47+
48+
return new SaveBatchingAggregateChange<>(entityClass);
49+
}
3450
}

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

+30-8
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.springframework.data.relational.core.conversion;
1717

18+
import org.springframework.lang.Nullable;
1819
import org.springframework.util.Assert;
1920
import org.springframework.util.ClassUtils;
2021

@@ -34,7 +35,7 @@ public interface MutableAggregateChange<T> extends AggregateChange<T> {
3435
* @param entity aggregate root to save.
3536
* @param <T> entity type.
3637
* @return the {@link AggregateChangeWithRoot} for saving the root {@code entity}.
37-
* @since 2.4
38+
* @since 1.2
3839
*/
3940
@SuppressWarnings("unchecked")
4041
static <T> AggregateChangeWithRoot<T> forSave(T entity) {
@@ -44,6 +45,24 @@ static <T> AggregateChangeWithRoot<T> forSave(T entity) {
4445
return new DefaultAggregateChangeWithRoot<>(Kind.SAVE, (Class<T>) ClassUtils.getUserClass(entity));
4546
}
4647

48+
/**
49+
* Factory method to create an {@link AggregateChangeWithRoot} for saving entities.
50+
*
51+
* @param entity aggregate root to save.
52+
* @param previousVersion the previous version assigned to the instance being saved. May be {@literal null}.
53+
* @param <T> entity type.
54+
* @return the {@link AggregateChangeWithRoot} for saving the root {@code entity}.
55+
* @since 2.4
56+
* @deprecated since 3.0, use {@link #forSave(Object)} instead.
57+
*/
58+
@Deprecated
59+
static <T> AggregateChangeWithRoot<T> forSave(T entity, @Nullable Number previousVersion) {
60+
61+
Assert.notNull(entity, "Entity must not be null");
62+
63+
return forSave(entity);
64+
}
65+
4766
/**
4867
* Factory method to create an {@link MutableAggregateChange} for deleting entities.
4968
*
@@ -61,33 +80,36 @@ static <T> MutableAggregateChange<T> forDelete(T entity) {
6180
}
6281

6382
/**
64-
* Factory method to create a {@link MergedAggregateChange} for saving entities.
83+
* Factory method to create an {@link MutableAggregateChange} for deleting entities.
6584
*
6685
* @param entityClass aggregate root type.
6786
* @param <T> entity type.
68-
* @return the {@link MergedAggregateChange} for saving root entities.
69-
* @since 3.0
87+
* @return the {@link MutableAggregateChange} for deleting the root {@code entity}.
88+
* @since 1.2
7089
*/
71-
static <T> MergedAggregateChange<T, AggregateChangeWithRoot<T>> mergedSave(Class<T> entityClass) {
90+
static <T> MutableAggregateChange<T> forDelete(Class<T> entityClass) {
7291

7392
Assert.notNull(entityClass, "Entity class must not be null");
7493

75-
return new SaveMergedAggregateChange<>(entityClass);
94+
return new DefaultAggregateChange<>(Kind.DELETE, entityClass);
7695
}
7796

7897
/**
7998
* Factory method to create an {@link MutableAggregateChange} for deleting entities.
8099
*
81100
* @param entityClass aggregate root type.
101+
* @param previousVersion the previous version assigned to the instance being saved. May be {@literal null}.
82102
* @param <T> entity type.
83103
* @return the {@link MutableAggregateChange} for deleting the root {@code entity}.
84104
* @since 2.4
105+
* @deprecated since 3.0, use {@link #forDelete(Class)} instead.
85106
*/
86-
static <T> MutableAggregateChange<T> forDelete(Class<T> entityClass) {
107+
@Deprecated
108+
static <T> MutableAggregateChange<T> forDelete(Class<T> entityClass, @Nullable Number previousVersion) {
87109

88110
Assert.notNull(entityClass, "Entity class must not be null");
89111

90-
return new DefaultAggregateChange<>(Kind.DELETE, entityClass);
112+
return forDelete(entityClass);
91113
}
92114

93115
/**
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919

2020
import java.util.ArrayList;
2121
import java.util.Comparator;
22-
import java.util.EnumMap;
2322
import java.util.HashMap;
2423
import java.util.List;
2524
import java.util.Map;
@@ -30,27 +29,27 @@
3029
import org.springframework.util.Assert;
3130

3231
/**
33-
* A {@link org.springframework.data.relational.core.conversion.MergedAggregateChange} implementation for save changes
32+
* A {@link BatchingAggregateChange} implementation for save changes
3433
* that can contain actions for any mix of insert and update operations. When consumed, actions are yielded in the
3534
* appropriate entity tree order with inserts carried out from root to leaves and deletes in reverse. All insert
3635
* operations are grouped into batches to offer the ability for an optimized batch operation to be used.
3736
*
3837
* @author Chirag Tailor
3938
* @since 3.0
4039
*/
41-
public class SaveMergedAggregateChange<T> implements MergedAggregateChange<T, AggregateChangeWithRoot<T>> {
40+
public class SaveBatchingAggregateChange<T> implements BatchingAggregateChange<T, AggregateChangeWithRoot<T>> {
4241

4342
private static final Comparator<PersistentPropertyPath<RelationalPersistentProperty>> pathLengthComparator = //
4443
Comparator.comparing(PersistentPropertyPath::getLength);
4544

4645
private final Class<T> entityType;
4746
private final List<DbAction.WithRoot<?>> rootActions = new ArrayList<>();
48-
private final Map<PersistentPropertyPath<RelationalPersistentProperty>, EnumMap<IdValueSource, List<DbAction.Insert<Object>>>> insertActions = //
47+
private final Map<PersistentPropertyPath<RelationalPersistentProperty>, Map<IdValueSource, List<DbAction.Insert<Object>>>> insertActions = //
4948
new HashMap<>();
5049
private final Map<PersistentPropertyPath<RelationalPersistentProperty>, List<DbAction.Delete<?>>> deleteActions = //
5150
new HashMap<>();
5251

53-
public SaveMergedAggregateChange(Class<T> entityType) {
52+
public SaveBatchingAggregateChange(Class<T> entityType) {
5453
this.entityType = entityType;
5554
}
5655

@@ -78,7 +77,7 @@ public void forEachAction(Consumer<? super DbAction<?>> consumer) {
7877
}
7978

8079
@Override
81-
public MergedAggregateChange<T, AggregateChangeWithRoot<T>> merge(AggregateChangeWithRoot<T> aggregateChange) {
80+
public void add(AggregateChangeWithRoot<T> aggregateChange) {
8281

8382
aggregateChange.forEachAction(action -> {
8483
if (action instanceof DbAction.WithRoot<?> rootAction) {
@@ -90,21 +89,20 @@ public MergedAggregateChange<T, AggregateChangeWithRoot<T>> merge(AggregateChang
9089
addDelete(deleteAction);
9190
}
9291
});
93-
return this;
9492
}
9593

9694
private void addInsert(DbAction.Insert<Object> action) {
9795

9896
PersistentPropertyPath<RelationalPersistentProperty> propertyPath = action.getPropertyPath();
9997
insertActions.merge(propertyPath,
100-
new EnumMap<>(singletonMap(action.getIdValueSource(), new ArrayList<>(singletonList(action)))),
101-
(enumMap, enumMapDefaultValue) -> {
102-
enumMap.merge(action.getIdValueSource(), new ArrayList<>(singletonList(action)),
98+
new HashMap<>(singletonMap(action.getIdValueSource(), new ArrayList<>(singletonList(action)))),
99+
(map, mapDefaultValue) -> {
100+
map.merge(action.getIdValueSource(), new ArrayList<>(singletonList(action)),
103101
(actions, listDefaultValue) -> {
104102
actions.add(action);
105103
return actions;
106104
});
107-
return enumMap;
105+
return map;
108106
});
109107
}
110108

0 commit comments

Comments
 (0)