Skip to content

Commit 1314081

Browse files
committed
DATAJDBC-291 - Polishing.
Introduce factory methods for AggregateChange save/delete object creation. Replace computeIfAbsent pattern with appropriate methods. Add indirection for staged values traversal. Rename CascadingValuesLookup to StagedValues. Javadoc, formatting. Original pull request: #173.
1 parent feb60c8 commit 1314081

File tree

5 files changed

+162
-133
lines changed

5 files changed

+162
-133
lines changed

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

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
import org.springframework.data.mapping.IdentifierAccessor;
2727
import org.springframework.data.mapping.callback.EntityCallbacks;
2828
import org.springframework.data.relational.core.conversion.AggregateChange;
29-
import org.springframework.data.relational.core.conversion.AggregateChange.Kind;
3029
import org.springframework.data.relational.core.conversion.Interpreter;
3130
import org.springframework.data.relational.core.conversion.RelationalConverter;
3231
import org.springframework.data.relational.core.conversion.RelationalEntityDeleteWriter;
@@ -331,33 +330,30 @@ private <T> void deleteTree(Object id, @Nullable T entity, Class<T> domainType)
331330
triggerAfterDelete(entity, id, change);
332331
}
333332

334-
@SuppressWarnings({ "unchecked", "rawtypes" })
335333
private <T> AggregateChange<T> createInsertChange(T instance) {
336334

337-
AggregateChange<T> aggregateChange = new AggregateChange(Kind.SAVE, instance.getClass(), instance);
335+
AggregateChange<T> aggregateChange = AggregateChange.forSave(instance);
338336
jdbcEntityInsertWriter.write(instance, aggregateChange);
339337
return aggregateChange;
340338
}
341339

342-
@SuppressWarnings({ "unchecked", "rawtypes" })
343340
private <T> AggregateChange<T> createUpdateChange(T instance) {
344341

345-
AggregateChange<T> aggregateChange = new AggregateChange(Kind.SAVE, instance.getClass(), instance);
342+
AggregateChange<T> aggregateChange = AggregateChange.forSave(instance);
346343
jdbcEntityUpdateWriter.write(instance, aggregateChange);
347344
return aggregateChange;
348345
}
349346

350-
@SuppressWarnings({ "unchecked", "rawtypes" })
351347
private <T> AggregateChange<T> createDeletingChange(Object id, @Nullable T entity, Class<T> domainType) {
352348

353-
AggregateChange<T> aggregateChange = new AggregateChange(Kind.DELETE, domainType, entity);
349+
AggregateChange<T> aggregateChange = AggregateChange.forDelete(domainType, entity);
354350
jdbcEntityDeleteWriter.write(id, aggregateChange);
355351
return aggregateChange;
356352
}
357353

358354
private AggregateChange<?> createDeletingChange(Class<?> domainType) {
359355

360-
AggregateChange<?> aggregateChange = new AggregateChange<>(Kind.DELETE, domainType, null);
356+
AggregateChange<?> aggregateChange = AggregateChange.forDelete(domainType, null);
361357
jdbcEntityDeleteWriter.write(null, aggregateChange);
362358
return aggregateChange;
363359
}

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

Lines changed: 110 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.util.List;
2626
import java.util.Map;
2727
import java.util.Set;
28+
import java.util.function.BiConsumer;
2829

2930
import org.springframework.data.mapping.PersistentProperty;
3031
import org.springframework.data.mapping.PersistentPropertyAccessor;
@@ -34,6 +35,7 @@
3435
import org.springframework.data.util.Pair;
3536
import org.springframework.lang.Nullable;
3637
import org.springframework.util.Assert;
38+
import org.springframework.util.ClassUtils;
3739

3840
/**
3941
* Represents the change happening to the aggregate (as used in the context of Domain Driven Design) as a whole.
@@ -59,57 +61,101 @@ public AggregateChange(Kind kind, Class<T> entityType, @Nullable T entity) {
5961
this.entity = entity;
6062
}
6163

64+
/**
65+
* Factory method to create an {@link AggregateChange} for saving entities.
66+
*
67+
* @param entity aggregate root to save.
68+
* @param <T> entity type.
69+
* @return the {@link AggregateChange} for saving the root {@code entity}.
70+
* @since 1.2
71+
*/
72+
@SuppressWarnings("unchecked")
73+
public static <T> AggregateChange<T> forSave(T entity) {
74+
75+
Assert.notNull(entity, "Entity must not be null");
76+
return new AggregateChange<>(Kind.SAVE, (Class<T>) ClassUtils.getUserClass(entity), entity);
77+
}
78+
79+
/**
80+
* Factory method to create an {@link AggregateChange} for deleting entities.
81+
*
82+
* @param entity aggregate root to delete.
83+
* @param <T> entity type.
84+
* @return the {@link AggregateChange} for deleting the root {@code entity}.
85+
* @since 1.2
86+
*/
87+
@SuppressWarnings("unchecked")
88+
public static <T> AggregateChange<T> forDelete(T entity) {
89+
90+
Assert.notNull(entity, "Entity must not be null");
91+
return forDelete((Class<T>) ClassUtils.getUserClass(entity), entity);
92+
}
93+
94+
/**
95+
* Factory method to create an {@link AggregateChange} for deleting entities.
96+
*
97+
* @param entityClass aggregate root type.
98+
* @param entity aggregate root to delete.
99+
* @param <T> entity type.
100+
* @return the {@link AggregateChange} for deleting the root {@code entity}.
101+
* @since 1.2
102+
*/
103+
public static <T> AggregateChange<T> forDelete(Class<T> entityClass, @Nullable T entity) {
104+
105+
Assert.notNull(entityClass, "Entity class must not be null");
106+
return new AggregateChange<>(Kind.DELETE, entityClass, entity);
107+
}
108+
62109
public void setEntity(@Nullable T aggregateRoot) {
110+
// TODO: Check instanceOf compatibility to ensure type contract.
63111
entity = aggregateRoot;
64112
}
65113

66114
public void executeWith(Interpreter interpreter, RelationalMappingContext context, RelationalConverter converter) {
67115

68116
actions.forEach(action -> action.executeWith(interpreter));
69117

70-
T newRoot = setGeneratedIds(context, converter);
118+
T newRoot = populateIdsIfNecessary(context, converter);
71119

72120
if (newRoot != null) {
73121
entity = newRoot;
74122
}
75123
}
76124

77-
@SuppressWarnings("unchecked")
78125
@Nullable
79-
private T setGeneratedIds(RelationalMappingContext context, RelationalConverter converter) {
126+
private T populateIdsIfNecessary(RelationalMappingContext context, RelationalConverter converter) {
80127

81128
T newRoot = null;
82129

83130
// have the actions so that the inserts on the leaves come first.
84131
ArrayList<DbAction<?>> reverseActions = new ArrayList<>(actions);
85132
Collections.reverse(reverseActions);
86133

87-
CascadingValuesLookup cascadingValues = new CascadingValuesLookup();
134+
StagedValues cascadingValues = new StagedValues();
88135

89136
for (DbAction<?> action : reverseActions) {
90137

91-
if (action instanceof DbAction.WithGeneratedId) {
92-
93-
DbAction.WithGeneratedId<?> withGeneratedId = (DbAction.WithGeneratedId<?>) action;
94-
Object generatedId = withGeneratedId.getGeneratedId();
138+
if (!(action instanceof DbAction.WithGeneratedId)) {
139+
continue;
140+
}
95141

96-
Object newEntity = setIdAndCascadingProperties(context, converter, withGeneratedId, generatedId,
97-
cascadingValues);
142+
DbAction.WithGeneratedId<?> withGeneratedId = (DbAction.WithGeneratedId<?>) action;
143+
Object generatedId = withGeneratedId.getGeneratedId();
144+
Object newEntity = setIdAndCascadingProperties(context, converter, withGeneratedId, generatedId, cascadingValues);
98145

99-
// the id property was immutable so we have to propagate changes up the tree
100-
if (newEntity != ((DbAction.WithGeneratedId<?>) action).getEntity()) {
146+
// the id property was immutable so we have to propagate changes up the tree
147+
if (newEntity != ((DbAction.WithGeneratedId<?>) action).getEntity()) {
101148

102-
if (action instanceof DbAction.Insert) {
103-
DbAction.Insert insert = (DbAction.Insert) action;
149+
if (action instanceof DbAction.Insert) {
150+
DbAction.Insert insert = (DbAction.Insert) action;
104151

105-
Pair qualifier = insert.getQualifier();
152+
Pair qualifier = insert.getQualifier();
106153

107-
cascadingValues.add(insert.dependingOn, insert.propertyPath, newEntity,
108-
qualifier == null ? null : qualifier.getSecond());
154+
cascadingValues.stage(insert.dependingOn, insert.propertyPath,
155+
qualifier == null ? null : qualifier.getSecond(), newEntity);
109156

110-
} else if (action instanceof DbAction.InsertRoot) {
111-
newRoot = (T) newEntity;
112-
}
157+
} else if (action instanceof DbAction.InsertRoot) {
158+
newRoot = entityType.cast(newEntity);
113159
}
114160
}
115161
}
@@ -119,7 +165,7 @@ private T setGeneratedIds(RelationalMappingContext context, RelationalConverter
119165

120166
@SuppressWarnings("unchecked")
121167
private <S> Object setIdAndCascadingProperties(RelationalMappingContext context, RelationalConverter converter,
122-
DbAction.WithGeneratedId<S> action, @Nullable Object generatedId, CascadingValuesLookup cascadingValues) {
168+
DbAction.WithGeneratedId<S> action, @Nullable Object generatedId, StagedValues cascadingValues) {
123169

124170
S originalEntity = action.getEntity();
125171

@@ -132,19 +178,14 @@ private <S> Object setIdAndCascadingProperties(RelationalMappingContext context,
132178
}
133179

134180
// set values of changed immutables referenced by this entity
135-
Map<PersistentPropertyPath, Object> cascadingValue = cascadingValues.get(action);
136-
for (Map.Entry<PersistentPropertyPath, Object> pathValuePair : cascadingValue.entrySet()) {
137-
propertyAccessor.setProperty(getRelativePath(action, pathValuePair), pathValuePair.getValue());
138-
}
181+
cascadingValues.forEachPath(action, (persistentPropertyPath, o) -> propertyAccessor
182+
.setProperty(getRelativePath(action, persistentPropertyPath), o));
139183

140184
return propertyAccessor.getBean();
141185
}
142186

143187
@SuppressWarnings("unchecked")
144-
private PersistentPropertyPath getRelativePath(DbAction action,
145-
Map.Entry<PersistentPropertyPath, Object> pathValuePair) {
146-
147-
PersistentPropertyPath pathToValue = pathValuePair.getKey();
188+
private PersistentPropertyPath getRelativePath(DbAction action, PersistentPropertyPath pathToValue) {
148189

149190
if (action instanceof DbAction.Insert) {
150191
return pathToValue.getExtensionForBaseOf(((DbAction.Insert) action).propertyPath);
@@ -178,12 +219,13 @@ public enum Kind {
178219
}
179220

180221
/**
181-
* Gathers and holds information about immutable properties in an aggregate that need updating.
222+
* Accumulates information about staged immutable objects in an aggregate that require updating because their state
223+
* changed because of {@link DbAction} execution.
182224
*/
183-
private static class CascadingValuesLookup {
225+
private static class StagedValues {
184226

185-
static final List<MultiValueAggregator> aggregators = Arrays.asList(new SetAggregator(), new MapAggregator(),
186-
new ListAggregator(), new SingleElementAggregator());
227+
static final List<MultiValueAggregator> aggregators = Arrays.asList(SetAggregator.INSTANCE, MapAggregator.INSTANCE,
228+
ListAggregator.INSTANCE, SingleElementAggregator.INSTANCE);
187229

188230
Map<DbAction, Map<PersistentPropertyPath, Object>> values = new HashMap<>();
189231

@@ -194,27 +236,22 @@ private static class CascadingValuesLookup {
194236
* @param action The action responsible for persisting the entity that needs the added value set. Must not be
195237
* {@literal null}.
196238
* @param path The path to the property in which to set the value. Must not be {@literal null}.
197-
* @param value The value to be set. Must not be {@literal null}.
198239
* @param qualifier If {@code path} is a qualified multivalued properties this parameter contains the qualifier. May
199240
* be {@literal null}.
241+
* @param value The value to be set. Must not be {@literal null}.
200242
*/
201243
@SuppressWarnings("unchecked")
202-
public <T> void add(DbAction<?> action, PersistentPropertyPath path, Object value, @Nullable Object qualifier) {
244+
<T> void stage(DbAction<?> action, PersistentPropertyPath path, @Nullable Object qualifier, Object value) {
203245

204246
MultiValueAggregator<T> aggregator = getAggregatorFor(path);
205247

206-
Map<PersistentPropertyPath, Object> valuesForPath = this.values.get(action);
207-
if (valuesForPath == null) {
208-
valuesForPath = new HashMap<>();
209-
values.put(action, valuesForPath);
210-
}
248+
Map<PersistentPropertyPath, Object> valuesForPath = this.values.computeIfAbsent(action,
249+
dbAction -> new HashMap<>());
211250

212-
T currentValue = (T) valuesForPath.get(path);
213-
if (currentValue == null) {
214-
currentValue = aggregator.createEmptyInstance();
215-
}
251+
T currentValue = (T) valuesForPath.computeIfAbsent(path,
252+
persistentPropertyPath -> aggregator.createEmptyInstance());
216253

217-
Object newValue = aggregator.add(currentValue, value, qualifier);
254+
Object newValue = aggregator.add(currentValue, qualifier, value);
218255

219256
valuesForPath.put(path, newValue);
220257
}
@@ -231,8 +268,17 @@ private MultiValueAggregator getAggregatorFor(PersistentPropertyPath path) {
231268
throw new IllegalStateException(String.format("Can't handle path %s", path));
232269
}
233270

234-
public Map<PersistentPropertyPath, Object> get(DbAction<?> action) {
235-
return values.getOrDefault(action, Collections.emptyMap());
271+
/**
272+
* Performs the given action for each entry in this the staging area that are provided by {@link DbAction} until all
273+
* {@link PersistentPropertyPath} have been processed or the action throws an exception. The {@link BiConsumer
274+
* action} is called with each applicable {@link PersistentPropertyPath} and {@code value} that is assignable to the
275+
* property.
276+
*
277+
* @param dbAction
278+
* @param action
279+
*/
280+
void forEachPath(DbAction<?> dbAction, BiConsumer<PersistentPropertyPath, Object> action) {
281+
values.getOrDefault(dbAction, Collections.emptyMap()).forEach(action);
236282
}
237283
}
238284

@@ -249,11 +295,13 @@ default boolean handles(PersistentProperty property) {
249295
@Nullable
250296
T createEmptyInstance();
251297

252-
T add(@Nullable T aggregate, Object value, @Nullable Object qualifier);
298+
T add(@Nullable T aggregate, @Nullable Object qualifier, Object value);
253299

254300
}
255301

256-
static private class SetAggregator implements MultiValueAggregator<Set> {
302+
private enum SetAggregator implements MultiValueAggregator<Set> {
303+
304+
INSTANCE;
257305

258306
@Override
259307
public Class<Set> handledType() {
@@ -267,7 +315,7 @@ public Set createEmptyInstance() {
267315

268316
@SuppressWarnings("unchecked")
269317
@Override
270-
public Set add(@Nullable Set set, Object value, @Nullable Object qualifier) {
318+
public Set add(@Nullable Set set, @Nullable Object qualifier, Object value) {
271319

272320
Assert.notNull(set, "Set must not be null");
273321

@@ -276,7 +324,9 @@ public Set add(@Nullable Set set, Object value, @Nullable Object qualifier) {
276324
}
277325
}
278326

279-
static private class ListAggregator implements MultiValueAggregator<List> {
327+
private enum ListAggregator implements MultiValueAggregator<List> {
328+
329+
INSTANCE;
280330

281331
@Override
282332
public boolean handles(PersistentProperty property) {
@@ -290,7 +340,7 @@ public List createEmptyInstance() {
290340

291341
@SuppressWarnings("unchecked")
292342
@Override
293-
public List add(@Nullable List list, Object value, @Nullable Object qualifier) {
343+
public List add(@Nullable List list, @Nullable Object qualifier, Object value) {
294344

295345
Assert.notNull(list, "List must not be null.");
296346

@@ -305,7 +355,9 @@ public List add(@Nullable List list, Object value, @Nullable Object qualifier) {
305355
}
306356
}
307357

308-
static private class MapAggregator implements MultiValueAggregator<Map> {
358+
private enum MapAggregator implements MultiValueAggregator<Map> {
359+
360+
INSTANCE;
309361

310362
@Override
311363
public Class<Map> handledType() {
@@ -319,7 +371,7 @@ public Map createEmptyInstance() {
319371

320372
@SuppressWarnings("unchecked")
321373
@Override
322-
public Map add(@Nullable Map map, Object value, @Nullable Object qualifier) {
374+
public Map add(@Nullable Map map, @Nullable Object qualifier, Object value) {
323375

324376
Assert.notNull(map, "Map must not be null.");
325377

@@ -328,7 +380,9 @@ public Map add(@Nullable Map map, Object value, @Nullable Object qualifier) {
328380
}
329381
}
330382

331-
static private class SingleElementAggregator implements MultiValueAggregator<Object> {
383+
private enum SingleElementAggregator implements MultiValueAggregator<Object> {
384+
385+
INSTANCE;
332386

333387
@Override
334388
@Nullable
@@ -337,7 +391,7 @@ public Object createEmptyInstance() {
337391
}
338392

339393
@Override
340-
public Object add(@Nullable Object __null, Object value, @Nullable Object qualifier) {
394+
public Object add(@Nullable Object __null, @Nullable Object qualifier, Object value) {
341395
return value;
342396
}
343397
}

0 commit comments

Comments
 (0)