Skip to content

Commit 508791b

Browse files
ctailor2schauder
authored andcommitted
Capture execution results for update db actions to fix root references with immutably typed ids.
+ Added #getIdValueSource to DbAction.WithEntity since it now applies to all such DbAction. + Removed DbAction.WithGeneratedId in favor of DbAction.WithEntity where it was used. Removes unused DbAction.Update. Passes the root from BeforeSaveCallback through to InsertRoot,UpdateRoot actions. + Isolate aggregate change #getEntity #setEntity behaviors into a new interface for "save" changes. + Extract #setEntity from DbAction.WithEntity to a new DbAction interface just for root actions. + JdbcAggregateChangeExecutionContext#populateIdsIfNecessary returns the root entity from DbAction.InsertRoot or DbAction.UpdateRoot regardless of the immutability of its @id property. Closes #1199 Closes #1201 Original pull request #1208
1 parent 0500442 commit 508791b

28 files changed

+772
-404
lines changed

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

+10-10
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@
1818
import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
1919
import org.springframework.data.jdbc.core.convert.JdbcConverter;
2020
import org.springframework.data.relational.core.conversion.AggregateChange;
21+
import org.springframework.data.relational.core.conversion.AggregateChangeWithRoot;
2122
import org.springframework.data.relational.core.conversion.DbAction;
2223
import org.springframework.data.relational.core.conversion.DbActionExecutionException;
2324
import org.springframework.data.relational.core.conversion.MutableAggregateChange;
24-
import org.springframework.lang.Nullable;
2525

2626
/**
2727
* Executes an {@link MutableAggregateChange}.
@@ -42,20 +42,22 @@ class AggregateChangeExecutor {
4242
this.accessStrategy = accessStrategy;
4343
}
4444

45-
@Nullable
46-
<T> T execute(AggregateChange<T> aggregateChange) {
45+
<T> T execute(AggregateChangeWithRoot<T> aggregateChange) {
4746

4847
JdbcAggregateChangeExecutionContext executionContext = new JdbcAggregateChangeExecutionContext(converter,
4948
accessStrategy);
5049

5150
aggregateChange.forEachAction(action -> execute(action, executionContext));
5251

53-
T root = executionContext.populateIdsIfNecessary();
54-
if (root == null) {
55-
root = aggregateChange.getEntity();
56-
}
52+
return executionContext.populateIdsIfNecessary();
53+
}
5754

58-
return root;
55+
<T> void execute(AggregateChange<T> aggregateChange) {
56+
57+
JdbcAggregateChangeExecutionContext executionContext = new JdbcAggregateChangeExecutionContext(converter,
58+
accessStrategy);
59+
60+
aggregateChange.forEachAction(action -> execute(action, executionContext));
5961
}
6062

6163
private void execute(DbAction<?> action, JdbcAggregateChangeExecutionContext executionContext) {
@@ -69,8 +71,6 @@ private void execute(DbAction<?> action, JdbcAggregateChangeExecutionContext exe
6971
executionContext.executeInsertBatch((DbAction.InsertBatch<?>) action);
7072
} else if (action instanceof DbAction.UpdateRoot) {
7173
executionContext.executeUpdateRoot((DbAction.UpdateRoot<?>) action);
72-
} else if (action instanceof DbAction.Update) {
73-
executionContext.executeUpdate((DbAction.Update<?>) action);
7474
} else if (action instanceof DbAction.Delete) {
7575
executionContext.executeDelete((DbAction.Delete<?>) action);
7676
} else if (action instanceof DbAction.DeleteAll) {

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

+26-44
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,7 @@
1515
*/
1616
package org.springframework.data.jdbc.core;
1717

18-
import java.util.ArrayList;
19-
import java.util.Arrays;
20-
import java.util.Collections;
21-
import java.util.HashMap;
22-
import java.util.HashSet;
23-
import java.util.LinkedHashMap;
24-
import java.util.List;
25-
import java.util.Map;
26-
import java.util.Set;
18+
import java.util.*;
2719
import java.util.function.BiConsumer;
2820
import java.util.stream.Collectors;
2921

@@ -40,6 +32,7 @@
4032
import org.springframework.data.mapping.context.MappingContext;
4133
import org.springframework.data.relational.core.conversion.DbAction;
4234
import org.springframework.data.relational.core.conversion.DbActionExecutionResult;
35+
import org.springframework.data.relational.core.conversion.IdValueSource;
4336
import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension;
4437
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
4538
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
@@ -108,15 +101,7 @@ <T> void executeUpdateRoot(DbAction.UpdateRoot<T> update) {
108101
} else {
109102
updateWithoutVersion(update);
110103
}
111-
}
112-
113-
<T> void executeUpdate(DbAction.Update<T> update) {
114-
115-
if (!accessStrategy.update(update.getEntity(), update.getEntityType())) {
116-
117-
throw new IncorrectUpdateSemanticsDataAccessException(
118-
String.format(UPDATE_FAILED, update.getEntity(), getIdFrom(update)));
119-
}
104+
add(new DbActionExecutionResult(update));
120105
}
121106

122107
<T> void executeDeleteRoot(DbAction.DeleteRoot<T> delete) {
@@ -203,11 +188,12 @@ private DbAction.WithEntity<?> getIdOwningAction(DbAction.WithEntity<?> action,
203188

204189
private Object getPotentialGeneratedIdFrom(DbAction.WithEntity<?> idOwningAction) {
205190

206-
if (idOwningAction instanceof DbAction.WithGeneratedId) {
191+
if (IdValueSource.GENERATED.equals(idOwningAction.getIdValueSource())) {
207192

208-
Object generatedId;
209193
DbActionExecutionResult dbActionExecutionResult = results.get(idOwningAction);
210-
generatedId = dbActionExecutionResult == null ? null : dbActionExecutionResult.getId();
194+
Object generatedId = Optional.ofNullable(dbActionExecutionResult) //
195+
.map(DbActionExecutionResult::getGeneratedId) //
196+
.orElse(null);
211197

212198
if (generatedId != null) {
213199
return generatedId;
@@ -227,12 +213,8 @@ private Object getIdFrom(DbAction.WithEntity<?> idOwningAction) {
227213
return identifier;
228214
}
229215

230-
@SuppressWarnings("unchecked")
231-
@Nullable
232216
<T> T populateIdsIfNecessary() {
233217

234-
T newRoot = null;
235-
236218
// have the results so that the inserts on the leaves come first.
237219
List<DbActionExecutionResult> reverseResults = new ArrayList<>(results.values());
238220
Collections.reverse(reverseResults);
@@ -241,33 +223,29 @@ <T> T populateIdsIfNecessary() {
241223

242224
for (DbActionExecutionResult result : reverseResults) {
243225

244-
DbAction<?> action = result.getAction();
226+
DbAction.WithEntity<?> action = result.getAction();
245227

246-
if (!(action instanceof DbAction.WithGeneratedId)) {
247-
continue;
228+
Object newEntity = setIdAndCascadingProperties(action, result.getGeneratedId(), cascadingValues);
229+
230+
if (action instanceof DbAction.InsertRoot || action instanceof DbAction.UpdateRoot) {
231+
//noinspection unchecked
232+
return (T) newEntity;
248233
}
249234

250-
DbAction.WithEntity<?> withEntity = (DbAction.WithGeneratedId<?>) action;
251-
Object newEntity = setIdAndCascadingProperties(withEntity, result.getId(), cascadingValues);
252-
253235
// the id property was immutable so we have to propagate changes up the tree
254-
if (newEntity != withEntity.getEntity()) {
255-
256-
if (action instanceof DbAction.Insert) {
257-
DbAction.Insert<?> insert = (DbAction.Insert<?>) action;
258-
259-
Pair<?, ?> qualifier = insert.getQualifier();
236+
if (newEntity != action.getEntity() && action instanceof DbAction.Insert) {
237+
DbAction.Insert<?> insert = (DbAction.Insert<?>) action;
260238

261-
cascadingValues.stage(insert.getDependingOn(), insert.getPropertyPath(),
262-
qualifier == null ? null : qualifier.getSecond(), newEntity);
239+
Pair<?, ?> qualifier = insert.getQualifier();
263240

264-
} else if (action instanceof DbAction.InsertRoot) {
265-
newRoot = (T) newEntity;
266-
}
241+
cascadingValues.stage(insert.getDependingOn(), insert.getPropertyPath(),
242+
qualifier == null ? null : qualifier.getSecond(), newEntity);
267243
}
268244
}
269245

270-
return newRoot;
246+
throw new IllegalStateException(
247+
String.format("Cannot retrieve the resulting instance unless a %s or %s action was successfully executed.",
248+
DbAction.InsertRoot.class.getName(), DbAction.UpdateRoot.class.getName()));
271249
}
272250

273251
private <S> Object setIdAndCascadingProperties(DbAction.WithEntity<S> action, @Nullable Object generatedId,
@@ -279,7 +257,7 @@ private <S> Object setIdAndCascadingProperties(DbAction.WithEntity<S> action, @N
279257
.getRequiredPersistentEntity(action.getEntityType());
280258
PersistentPropertyAccessor<S> propertyAccessor = converter.getPropertyAccessor(persistentEntity, originalEntity);
281259

282-
if (generatedId != null && persistentEntity.hasIdProperty()) {
260+
if (IdValueSource.GENERATED.equals(action.getIdValueSource())) {
283261
propertyAccessor.setProperty(persistentEntity.getRequiredIdProperty(), generatedId);
284262
}
285263

@@ -301,6 +279,10 @@ private PersistentPropertyPath<?> getRelativePath(DbAction<?> action, Persistent
301279
return pathToValue;
302280
}
303281

282+
if (action instanceof DbAction.UpdateRoot) {
283+
return pathToValue;
284+
}
285+
304286
throw new IllegalArgumentException(String.format("DbAction of type %s is not supported.", action.getClass()));
305287
}
306288

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

+14-20
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.springframework.data.mapping.IdentifierAccessor;
3232
import org.springframework.data.mapping.callback.EntityCallbacks;
3333
import org.springframework.data.relational.core.conversion.AggregateChange;
34+
import org.springframework.data.relational.core.conversion.AggregateChangeWithRoot;
3435
import org.springframework.data.relational.core.conversion.MutableAggregateChange;
3536
import org.springframework.data.relational.core.conversion.RelationalEntityDeleteWriter;
3637
import org.springframework.data.relational.core.conversion.RelationalEntityInsertWriter;
@@ -61,8 +62,6 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
6162
private final RelationalMappingContext context;
6263

6364
private final RelationalEntityDeleteWriter jdbcEntityDeleteWriter;
64-
private final RelationalEntityInsertWriter jdbcEntityInsertWriter;
65-
private final RelationalEntityUpdateWriter jdbcEntityUpdateWriter;
6665

6766
private final DataAccessStrategy accessStrategy;
6867
private final AggregateChangeExecutor executor;
@@ -92,8 +91,6 @@ public JdbcAggregateTemplate(ApplicationContext publisher, RelationalMappingCont
9291
this.accessStrategy = dataAccessStrategy;
9392
this.converter = converter;
9493

95-
this.jdbcEntityInsertWriter = new RelationalEntityInsertWriter(context);
96-
this.jdbcEntityUpdateWriter = new RelationalEntityUpdateWriter(context);
9794
this.jdbcEntityDeleteWriter = new RelationalEntityDeleteWriter(context);
9895

9996
this.executor = new AggregateChangeExecutor(converter, accessStrategy);
@@ -122,8 +119,6 @@ public JdbcAggregateTemplate(ApplicationEventPublisher publisher, RelationalMapp
122119
this.accessStrategy = dataAccessStrategy;
123120
this.converter = converter;
124121

125-
this.jdbcEntityInsertWriter = new RelationalEntityInsertWriter(context);
126-
this.jdbcEntityUpdateWriter = new RelationalEntityUpdateWriter(context);
127122
this.jdbcEntityDeleteWriter = new RelationalEntityDeleteWriter(context);
128123
this.executor = new AggregateChangeExecutor(converter, accessStrategy);
129124
}
@@ -146,7 +141,7 @@ public <T> T save(T instance) {
146141

147142
RelationalPersistentEntity<?> persistentEntity = context.getRequiredPersistentEntity(instance.getClass());
148143

149-
Function<T, MutableAggregateChange<T>> changeCreator = persistentEntity.isNew(instance)
144+
Function<T, AggregateChangeWithRoot<T>> changeCreator = persistentEntity.isNew(instance)
150145
? entity -> createInsertChange(prepareVersionForInsert(entity))
151146
: entity -> createUpdateChange(prepareVersionForUpdate(entity));
152147

@@ -286,18 +281,18 @@ public void deleteAll(Class<?> domainType) {
286281
executor.execute(change);
287282
}
288283

289-
private <T> T store(T aggregateRoot, Function<T, MutableAggregateChange<T>> changeCreator,
284+
private <T> T store(T aggregateRoot, Function<T, AggregateChangeWithRoot<T>> changeCreator,
290285
RelationalPersistentEntity<?> persistentEntity) {
291286

292287
Assert.notNull(aggregateRoot, "Aggregate instance must not be null!");
293288

294289
aggregateRoot = triggerBeforeConvert(aggregateRoot);
295290

296-
MutableAggregateChange<T> change = changeCreator.apply(aggregateRoot);
291+
AggregateChangeWithRoot<T> change = changeCreator.apply(aggregateRoot);
297292

298-
aggregateRoot = triggerBeforeSave(change.getEntity(), change);
293+
aggregateRoot = triggerBeforeSave(change.getRoot(), change);
299294

300-
change.setEntity(aggregateRoot);
295+
change.setRoot(aggregateRoot);
301296

302297
T entityAfterExecution = executor.execute(change);
303298

@@ -313,25 +308,24 @@ private <T> void deleteTree(Object id, @Nullable T entity, Class<T> domainType)
313308
MutableAggregateChange<T> change = createDeletingChange(id, entity, domainType);
314309

315310
entity = triggerBeforeDelete(entity, id, change);
316-
change.setEntity(entity);
317311

318312
executor.execute(change);
319313

320314
triggerAfterDelete(entity, id, change);
321315
}
322316

323-
private <T> MutableAggregateChange<T> createInsertChange(T instance) {
317+
private <T> AggregateChangeWithRoot<T> createInsertChange(T instance) {
324318

325-
MutableAggregateChange<T> aggregateChange = MutableAggregateChange.forSave(instance);
326-
jdbcEntityInsertWriter.write(instance, aggregateChange);
319+
AggregateChangeWithRoot<T> aggregateChange = MutableAggregateChange.forSave(instance);
320+
new RelationalEntityInsertWriter<T>(context).write(instance, aggregateChange);
327321
return aggregateChange;
328322
}
329323

330-
private <T> MutableAggregateChange<T> createUpdateChange(EntityAndPreviousVersion<T> entityAndVersion) {
324+
private <T> AggregateChangeWithRoot<T> createUpdateChange(EntityAndPreviousVersion<T> entityAndVersion) {
331325

332-
MutableAggregateChange<T> aggregateChange = MutableAggregateChange.forSave(entityAndVersion.entity,
326+
AggregateChangeWithRoot<T> aggregateChange = MutableAggregateChange.forSave(entityAndVersion.entity,
333327
entityAndVersion.version);
334-
jdbcEntityUpdateWriter.write(entityAndVersion.entity, aggregateChange);
328+
new RelationalEntityUpdateWriter<T>(context).write(entityAndVersion.entity, aggregateChange);
335329
return aggregateChange;
336330
}
337331

@@ -383,14 +377,14 @@ private <T> MutableAggregateChange<T> createDeletingChange(Object id, @Nullable
383377
previousVersion = RelationalEntityVersionUtils.getVersionNumberFromEntity(entity, persistentEntity, converter);
384378
}
385379
}
386-
MutableAggregateChange<T> aggregateChange = MutableAggregateChange.forDelete(domainType, entity, previousVersion);
380+
MutableAggregateChange<T> aggregateChange = MutableAggregateChange.forDelete(domainType, previousVersion);
387381
jdbcEntityDeleteWriter.write(id, aggregateChange);
388382
return aggregateChange;
389383
}
390384

391385
private MutableAggregateChange<?> createDeletingChange(Class<?> domainType) {
392386

393-
MutableAggregateChange<?> aggregateChange = MutableAggregateChange.forDelete(domainType, null);
387+
MutableAggregateChange<?> aggregateChange = MutableAggregateChange.forDelete(domainType);
394388
jdbcEntityDeleteWriter.write(null, aggregateChange);
395389
return aggregateChange;
396390
}

0 commit comments

Comments
 (0)