DATAJDBC-227 - Polishing.

Return List<DbChange<?>> in RelationalEntityWriters so AggregateChange can be assembled in less places to reduce the number of abstractions per method. Change ResultOrException in FunctionCollector to static class so instances no longer retain a reference to their enclosing class. Fix DbAction generics in AggregateChange. Replace simple constructors with Lombok usage. Javadoc, typos, formatting.

Original pull request: #79.
This commit is contained in:
Mark Paluch
2018-07-19 15:15:10 +02:00
parent bdefd3da9a
commit 12d539a017
10 changed files with 267 additions and 113 deletions

View File

@@ -39,61 +39,109 @@ public class CascadingDataAccessStrategy implements DataAccessStrategy {
this.strategies = new ArrayList<>(strategies);
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#insert(java.lang.Object, java.lang.Class, java.util.Map)
*/
@Override
public <T> void insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) {
collectVoid(das -> das.insert(instance, domainType, additionalParameters));
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#update(java.lang.Object, java.lang.Class)
*/
@Override
public <S> boolean update(S instance, Class<S> domainType) {
return collect(das -> das.update(instance, domainType));
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#delete(java.lang.Object, java.lang.Class)
*/
@Override
public void delete(Object id, Class<?> domainType) {
collectVoid(das -> das.delete(id, domainType));
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#delete(java.lang.Object, org.springframework.data.mapping.PersistentPropertyPath)
*/
@Override
public void delete(Object rootId, PersistentPropertyPath<RelationalPersistentProperty> propertyPath) {
collectVoid(das -> das.delete(rootId, propertyPath));
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#deleteAll(java.lang.Class)
*/
@Override
public <T> void deleteAll(Class<T> domainType) {
collectVoid(das -> das.deleteAll(domainType));
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#deleteAll(org.springframework.data.mapping.PersistentPropertyPath)
*/
@Override
public void deleteAll(PersistentPropertyPath<RelationalPersistentProperty> propertyPath) {
collectVoid(das -> das.deleteAll(propertyPath));
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#count(java.lang.Class)
*/
@Override
public long count(Class<?> domainType) {
return collect(das -> das.count(domainType));
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#findById(java.lang.Object, java.lang.Class)
*/
@Override
public <T> T findById(Object id, Class<T> domainType) {
return collect(das -> das.findById(id, domainType));
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#findAll(java.lang.Class)
*/
@Override
public <T> Iterable<T> findAll(Class<T> domainType) {
return collect(das -> das.findAll(domainType));
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#findAllById(java.lang.Iterable, java.lang.Class)
*/
@Override
public <T> Iterable<T> findAllById(Iterable<?> ids, Class<T> domainType) {
return collect(das -> das.findAllById(ids, domainType));
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#findAllByProperty(java.lang.Object, org.springframework.data.relational.core.mapping.RelationalPersistentProperty)
*/
@Override
public <T> Iterable<T> findAllByProperty(Object rootId, RelationalPersistentProperty property) {
return collect(das -> das.findAllByProperty(rootId, property));
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#existsById(java.lang.Object, java.lang.Class)
*/
@Override
public <T> boolean existsById(Object id, Class<T> domainType) {
return collect(das -> das.existsById(id, domainType));
@@ -112,5 +160,4 @@ public class CascadingDataAccessStrategy implements DataAccessStrategy {
return null;
});
}
}

View File

@@ -33,7 +33,7 @@ public interface DataAccessStrategy {
/**
* Inserts a the data of a single entity. Referenced entities don't get handled.
*
*
* @param instance the instance to be stored. Must not be {@code null}.
* @param domainType the type of the instance. Must not be {@code null}.
* @param additionalParameters name-value pairs of additional parameters. Especially ids of parent entities that need
@@ -48,7 +48,7 @@ public interface DataAccessStrategy {
* @param instance the instance to save. Must not be {@code null}.
* @param domainType the type of the instance to save. Must not be {@code null}.
* @param <T> the type of the instance to save.
* @return wether the update actually updated a row.
* @return whether the update actually updated a row.
*/
<T> boolean update(T instance, Class<T> domainType);
@@ -64,7 +64,7 @@ public interface DataAccessStrategy {
/**
* Deletes all entities reachable via {@literal propertyPath} from the instance identified by {@literal rootId}.
*
*
* @param rootId Id of the root object on which the {@literal propertyPath} is based. Must not be {@code null}.
* @param propertyPath Leading from the root object to the entities to be deleted. Must not be {@code null}.
*/
@@ -72,7 +72,7 @@ public interface DataAccessStrategy {
/**
* Deletes all entities of the given domain type.
*
*
* @param domainType the domain type for which to delete all entries. Must not be {@code null}.
* @param <T> type of the domain type.
*/
@@ -105,7 +105,7 @@ public interface DataAccessStrategy {
<T> T findById(Object id, Class<T> domainType);
/**
* Loads alls entities of the given type.
* Loads all entities of the given type.
*
* @param domainType the type of entities to load. Must not be {@code null}.
* @param <T> the type of entities to load.
@@ -116,7 +116,7 @@ public interface DataAccessStrategy {
/**
* Loads all entities that match one of the ids passed as an argument. It is not guaranteed that the number of ids
* passed in matches the number of entities returned.
*
*
* @param ids the Ids of the entities to load. Must not be {@code null}.
* @param domainType the type of entities to laod. Must not be {@code null}.
* @param <T> type of entities to load.
@@ -134,7 +134,7 @@ public interface DataAccessStrategy {
/**
* returns if a row with the given id exists for the given type.
*
*
* @param id the id of the entity for which to check. Must not be {@code null}.
* @param domainType the type of the entity to check for. Must not be {@code null}.
* @param <T> the type of the entity.

View File

@@ -19,6 +19,7 @@ import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
@@ -83,6 +84,7 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
KeyHolder holder = new GeneratedKeyHolder();
RelationalPersistentEntity<T> persistentEntity = getRequiredPersistentEntity(domainType);
Map<String, Object> parameters = new LinkedHashMap<>(additionalParameters);
MapSqlParameterSource parameterSource = getPropertyMap(instance, persistentEntity);
@@ -93,14 +95,14 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
Assert.notNull(idProperty, "Since we have a non-null idValue, we must have an idProperty as well.");
additionalParameters.put(idProperty.getColumnName(),
parameters.put(idProperty.getColumnName(),
converter.writeValue(idValue, ClassTypeInformation.from(idProperty.getColumnType())));
}
additionalParameters.forEach(parameterSource::addValue);
parameters.forEach(parameterSource::addValue);
operations.update( //
sql(domainType).getInsert(additionalParameters.keySet()), //
sql(domainType).getInsert(parameters.keySet()), //
parameterSource, //
holder //
);
@@ -345,23 +347,23 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
} catch (InvalidDataAccessApiUsageException e) {
// Postgres returns a value for each column
Map<String, Object> keys = holder.getKeys();
return Optional.ofNullable( //
keys == null || persistentEntity.getIdProperty() == null //
? null //
: keys.get(persistentEntity.getIdColumn()));
if (keys == null || persistentEntity.getIdProperty() == null) {
return Optional.empty();
}
return Optional.ofNullable(keys.get(persistentEntity.getIdColumn()));
}
}
public EntityRowMapper<?> getEntityRowMapper(Class<?> domainType) {
private EntityRowMapper<?> getEntityRowMapper(Class<?> domainType) {
return new EntityRowMapper<>(getRequiredPersistentEntity(domainType), context, converter, accessStrategy);
}
@SuppressWarnings("unchecked")
private RowMapper<?> getMapEntityRowMapper(RelationalPersistentProperty property) {
String keyColumn = property.getKeyColumn();
Assert.notNull(keyColumn, () -> "keyColumn must not be null for " + property);
Assert.notNull(keyColumn, () -> "KeyColumn must not be null for " + property);
return new MapEntityRowMapper<>(getEntityRowMapper(property.getActualType()), keyColumn);
}

View File

@@ -15,6 +15,9 @@
*/
package org.springframework.data.jdbc.core;
import lombok.RequiredArgsConstructor;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -40,39 +43,55 @@ import org.springframework.lang.Nullable;
* interactions.
*
* @author Jens Schauder
* @author Mark Paluch
* @since 1.0
*/
@RequiredArgsConstructor
class DefaultJdbcInterpreter implements Interpreter {
private final RelationalMappingContext context;
private final DataAccessStrategy accessStrategy;
DefaultJdbcInterpreter(RelationalMappingContext context, DataAccessStrategy accessStrategy) {
this.context = context;
this.accessStrategy = accessStrategy;
}
/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.conversion.Interpreter#interpret(org.springframework.data.relational.core.conversion.DbAction.Insert)
*/
@Override
public <T> void interpret(Insert<T> insert) {
accessStrategy.insert(insert.getEntity(), insert.getEntityType(), createAdditionalColumnValues(insert));
}
/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.conversion.Interpreter#interpret(org.springframework.data.relational.core.conversion.DbAction.InsertRoot)
*/
@Override
public <T> void interpret(InsertRoot<T> insert) {
accessStrategy.insert(insert.getEntity(), insert.getEntityType(), new HashMap<>());
accessStrategy.insert(insert.getEntity(), insert.getEntityType(), Collections.emptyMap());
}
/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.conversion.Interpreter#interpret(org.springframework.data.relational.core.conversion.DbAction.Update)
*/
@Override
public <T> void interpret(Update<T> update) {
accessStrategy.update(update.getEntity(), update.getEntityType());
}
/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.conversion.Interpreter#interpret(org.springframework.data.relational.core.conversion.DbAction.UpdateRoot)
*/
@Override
public <T> void interpret(UpdateRoot<T> update) {
accessStrategy.update(update.getEntity(), update.getEntityType());
}
/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.conversion.Interpreter#interpret(org.springframework.data.relational.core.conversion.DbAction.Merge)
*/
@Override
public <T> void interpret(Merge<T> merge) {
@@ -82,21 +101,37 @@ class DefaultJdbcInterpreter implements Interpreter {
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.conversion.Interpreter#interpret(org.springframework.data.relational.core.conversion.DbAction.Delete)
*/
@Override
public <T> void interpret(Delete<T> delete) {
accessStrategy.delete(delete.getRootId(), delete.getPropertyPath());
}
/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.conversion.Interpreter#interpret(org.springframework.data.relational.core.conversion.DbAction.DeleteRoot)
*/
@Override
public <T> void interpret(DeleteRoot<T> delete) {
accessStrategy.delete(delete.getRootId(), delete.getEntityType());
}
/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.conversion.Interpreter#interpret(org.springframework.data.relational.core.conversion.DbAction.DeleteAll)
*/
@Override
public <T> void interpret(DeleteAll<T> delete) {
accessStrategy.deleteAll(delete.getPropertyPath());
}
/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.conversion.Interpreter#interpret(org.springframework.data.relational.core.conversion.DbAction.DeleteAllRoot)
*/
@Override
public <T> void interpret(DeleteAllRoot<T> deleteAllRoot) {
accessStrategy.deleteAll(deleteAllRoot.getEntityType());
@@ -111,7 +146,8 @@ class DefaultJdbcInterpreter implements Interpreter {
return additionalColumnValues;
}
private <T> void addDependingOnInformation(DbAction.WithDependingOn<T> action, Map<String, Object> additionalColumnValues) {
private <T> void addDependingOnInformation(DbAction.WithDependingOn<T> action,
Map<String, Object> additionalColumnValues) {
DbAction.WithEntity<?> dependingOn = action.getDependingOn();
@@ -125,7 +161,8 @@ class DefaultJdbcInterpreter implements Interpreter {
}
@Nullable
private Object getIdFromEntityDependingOn(DbAction.WithEntity<?> dependingOn, RelationalPersistentEntity<?> persistentEntity) {
private Object getIdFromEntityDependingOn(DbAction.WithEntity<?> dependingOn,
RelationalPersistentEntity<?> persistentEntity) {
return persistentEntity.getIdentifierAccessor(dependingOn.getEntity()).getIdentifier();
}

View File

@@ -36,7 +36,7 @@ import org.springframework.dao.DataAccessException;
* @author Jens Schauder
* @since 1.0
*/
class FunctionCollector<T> implements Collector<DataAccessStrategy, FunctionCollector<T>.ResultOrException, T> {
class FunctionCollector<T> implements Collector<DataAccessStrategy, FunctionCollector.ResultOrException<T>, T> {
private final Function<DataAccessStrategy, T> method;
@@ -44,13 +44,21 @@ class FunctionCollector<T> implements Collector<DataAccessStrategy, FunctionColl
this.method = method;
}
/*
* (non-Javadoc)
* @see java.util.stream.Collector#supplier()
*/
@Override
public Supplier<ResultOrException> supplier() {
public Supplier<ResultOrException<T>> supplier() {
return ResultOrException::new;
}
/*
* (non-Javadoc)
* @see java.util.stream.Collector#accumulator()
*/
@Override
public BiConsumer<ResultOrException, DataAccessStrategy> accumulator() {
public BiConsumer<ResultOrException<T>, DataAccessStrategy> accumulator() {
return (roe, das) -> {
@@ -65,16 +73,24 @@ class FunctionCollector<T> implements Collector<DataAccessStrategy, FunctionColl
};
}
/*
* (non-Javadoc)
* @see java.util.stream.Collector#combiner()
*/
@Override
public BinaryOperator<ResultOrException> combiner() {
public BinaryOperator<ResultOrException<T>> combiner() {
return (roe1, roe2) -> {
throw new UnsupportedOperationException("Can't combine method calls");
};
}
/*
* (non-Javadoc)
* @see java.util.stream.Collector#finisher()
*/
@Override
public Function<ResultOrException, T> finisher() {
public Function<ResultOrException<T>, T> finisher() {
return roe -> {
@@ -86,6 +102,10 @@ class FunctionCollector<T> implements Collector<DataAccessStrategy, FunctionColl
};
}
/*
* (non-Javadoc)
* @see java.util.stream.Collector#characteristics()
*/
@Override
public Set<Characteristics> characteristics() {
return Collections.emptySet();
@@ -95,7 +115,7 @@ class FunctionCollector<T> implements Collector<DataAccessStrategy, FunctionColl
* Stores intermediate results. I.e. a list of exceptions caught so far, any actual result and the fact, if there
* actually is an result.
*/
class ResultOrException {
static class ResultOrException<T> {
private T result;
private final List<Exception> exceptions = new LinkedList<>();

View File

@@ -124,12 +124,20 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
this.namespaceStrategy = namespaceStrategy;
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#insert(java.lang.Object, java.lang.Class, java.util.Map)
*/
@Override
public <T> void insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) {
sqlSession().insert(namespace(domainType) + ".insert",
new MyBatisContext(null, instance, domainType, additionalParameters));
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#update(java.lang.Object, java.lang.Class)
*/
@Override
public <S> boolean update(S instance, Class<S> domainType) {
@@ -137,6 +145,10 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
new MyBatisContext(null, instance, domainType, Collections.emptyMap())) != 0;
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#delete(java.lang.Object, java.lang.Class)
*/
@Override
public void delete(Object id, Class<?> domainType) {
@@ -144,6 +156,10 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
new MyBatisContext(id, null, domainType, Collections.emptyMap()));
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#delete(java.lang.Object, org.springframework.data.mapping.PersistentPropertyPath)
*/
@Override
public void delete(Object rootId, PersistentPropertyPath<RelationalPersistentProperty> propertyPath) {
@@ -153,6 +169,10 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
Collections.emptyMap()));
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#deleteAll(java.lang.Class)
*/
@Override
public <T> void deleteAll(Class<T> domainType) {
@@ -162,6 +182,10 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
);
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#deleteAll(org.springframework.data.mapping.PersistentPropertyPath)
*/
@Override
public void deleteAll(PersistentPropertyPath<RelationalPersistentProperty> propertyPath) {
@@ -174,24 +198,40 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
);
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#findById(java.lang.Object, java.lang.Class)
*/
@Override
public <T> T findById(Object id, Class<T> domainType) {
return sqlSession().selectOne(namespace(domainType) + ".findById",
new MyBatisContext(id, null, domainType, Collections.emptyMap()));
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#findAll(java.lang.Class)
*/
@Override
public <T> Iterable<T> findAll(Class<T> domainType) {
return sqlSession().selectList(namespace(domainType) + ".findAll",
new MyBatisContext(null, null, domainType, Collections.emptyMap()));
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#findAllById(java.lang.Iterable, java.lang.Class)
*/
@Override
public <T> Iterable<T> findAllById(Iterable<?> ids, Class<T> domainType) {
return sqlSession().selectList(namespace(domainType) + ".findAllById",
new MyBatisContext(ids, null, domainType, Collections.emptyMap()));
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#findAllByProperty(java.lang.Object, org.springframework.data.relational.core.mapping.RelationalPersistentProperty)
*/
@Override
public <T> Iterable<T> findAllByProperty(Object rootId, RelationalPersistentProperty property) {
return sqlSession().selectList(
@@ -199,12 +239,20 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
new MyBatisContext(rootId, null, property.getType(), Collections.emptyMap()));
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#existsById(java.lang.Object, java.lang.Class)
*/
@Override
public <T> boolean existsById(Object id, Class<T> domainType) {
return sqlSession().selectOne(namespace(domainType) + ".existsById",
new MyBatisContext(id, null, domainType, Collections.emptyMap()));
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#count(java.lang.Class)
*/
@Override
public long count(Class<?> domainType) {
return sqlSession().selectOne(namespace(domainType) + ".count",
@@ -219,7 +267,7 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
return this.sqlSession;
}
private String toDashPath(PersistentPropertyPath<RelationalPersistentProperty> propertyPath) {
private static String toDashPath(PersistentPropertyPath<RelationalPersistentProperty> propertyPath) {
return propertyPath.toDotPath().replaceAll("\\.", "-");
}
}

View File

@@ -25,6 +25,7 @@ import java.util.List;
* Represents the change happening to the aggregate (as used in the context of Domain Driven Design) as a whole.
*
* @author Jens Schauder
* @author Mark Paluch
* @since 1.0
*/
@RequiredArgsConstructor
@@ -39,13 +40,13 @@ public class AggregateChange<T> {
/** Aggregate root, to which the change applies, if available */
private final T entity;
private final List<DbAction> actions = new ArrayList<>();
private final List<DbAction<?>> actions = new ArrayList<>();
public void executeWith(Interpreter interpreter) {
actions.forEach(a -> a.executeWith(interpreter));
}
public void addAction(DbAction action) {
public void addAction(DbAction<?> action) {
actions.add(action);
}

View File

@@ -30,6 +30,7 @@ import org.springframework.util.Assert;
* be executed against the database to recreate the appropriate state in the database.
*
* @author Jens Schauder
* @author Mark Paluch
* @since 1.0
*/
public class RelationalEntityDeleteWriter implements EntityWriter<Object, AggregateChange<?>> {
@@ -47,7 +48,7 @@ public class RelationalEntityDeleteWriter implements EntityWriter<Object, Aggreg
* Fills the provided {@link AggregateChange} with the necessary {@link DbAction}s to delete the aggregate root
* identified by {@code id}. If {@code id} is {@code null} it is interpreted as "Delete all aggregates of the type
* indicated by the aggregateChange".
*
*
* @param id May be {@code null}.
* @param aggregateChange Must not be {@code null}.
*/
@@ -55,49 +56,50 @@ public class RelationalEntityDeleteWriter implements EntityWriter<Object, Aggreg
public void write(@Nullable Object id, AggregateChange<?> aggregateChange) {
if (id == null) {
deleteAll(aggregateChange);
deleteAll(aggregateChange.getEntityType()).forEach(aggregateChange::addAction);
} else {
deleteById(id, aggregateChange);
deleteById(id, aggregateChange).forEach(aggregateChange::addAction);
}
}
private void deleteAll(AggregateChange<?> aggregateChange) {
private List<DbAction<?>> deleteAll(Class<?> entityType) {
List<DbAction> actions = new ArrayList<>();
List<DbAction<?>> actions = new ArrayList<>();
context.findPersistentPropertyPaths(aggregateChange.getEntityType(), PersistentProperty::isEntity)
context.findPersistentPropertyPaths(entityType, PersistentProperty::isEntity)
.forEach(p -> actions.add(new DbAction.DeleteAll<>(p)));
Collections.reverse(actions);
actions.forEach(aggregateChange::addAction);
DbAction.DeleteAllRoot<?> result = new DbAction.DeleteAllRoot<>(entityType);
actions.add(result);
DbAction.DeleteAllRoot<?> result = new DbAction.DeleteAllRoot<>(aggregateChange.getEntityType());
aggregateChange.addAction(result);
return actions;
}
private <T> void deleteById(Object id, AggregateChange<T> aggregateChange) {
private <T> List<DbAction<?>> deleteById(Object id, AggregateChange<T> aggregateChange) {
deleteReferencedEntities(id, aggregateChange);
List<DbAction<?>> actions = new ArrayList<>(deleteReferencedEntities(id, aggregateChange));
actions.add(new DbAction.DeleteRoot<>(aggregateChange.getEntityType(), id));
aggregateChange.addAction(new DbAction.DeleteRoot<>(aggregateChange.getEntityType(), id));
return actions;
}
/**
* add {@link DbAction.Delete} actions to the {@link AggregateChange} for deleting all referenced entities.
* Add {@link DbAction.Delete} actions to the {@link AggregateChange} for deleting all referenced entities.
*
* @param id id of the aggregate root, of which the referenced entities get deleted.
* @param aggregateChange the change object to which the actions should get added. Must not be {@code null}
*/
private void deleteReferencedEntities(Object id, AggregateChange<?> aggregateChange) {
private List<DbAction<?>> deleteReferencedEntities(Object id, AggregateChange<?> aggregateChange) {
List<DbAction> actions = new ArrayList<>();
List<DbAction<?>> actions = new ArrayList<>();
context.findPersistentPropertyPaths(aggregateChange.getEntityType(), PersistentProperty::isEntity)
.forEach(p -> actions.add(new DbAction.Delete<>(id, p)));
Collections.reverse(actions);
actions.forEach(aggregateChange::addAction);
return actions;
}
}

View File

@@ -15,8 +15,8 @@
*/
package org.springframework.data.relational.core.conversion;
import lombok.NonNull;
import lombok.Value;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.ArrayList;
import java.util.Collection;
@@ -31,6 +31,7 @@ import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.mapping.PersistentPropertyPaths;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.data.util.Pair;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -38,6 +39,7 @@ import org.springframework.util.Assert;
* Converts an aggregate represented by its root into an {@link AggregateChange}.
*
* @author Jens Schauder
* @author Mark Paluch
* @since 1.0
*/
public class RelationalEntityWriter implements EntityWriter<Object, AggregateChange<?>> {
@@ -48,10 +50,16 @@ public class RelationalEntityWriter implements EntityWriter<Object, AggregateCha
this.context = context;
}
/*
* (non-Javadoc)
* @see org.springframework.data.convert.EntityWriter#write(java.lang.Object, java.lang.Object)
*/
@Override
public void write(Object root, AggregateChange<?> aggregateChange) {
new WritingContext(root, aggregateChange).write();
List<DbAction<?>> actions = new WritingContext(root, aggregateChange).write();
actions.forEach(aggregateChange::addAction);
}
/**
@@ -60,7 +68,8 @@ public class RelationalEntityWriter implements EntityWriter<Object, AggregateCha
private class WritingContext {
private final Object root;
private final AggregateChange<?> aggregateChange;
private final Object entity;
private final Class<?> entityType;
private final PersistentPropertyPaths<?, RelationalPersistentProperty> paths;
private final Map<PathNode, DbAction> previousActions = new HashMap<>();
private Map<PersistentPropertyPath<RelationalPersistentProperty>, List<PathNode>> nodesCache = new HashMap<>();
@@ -68,89 +77,99 @@ public class RelationalEntityWriter implements EntityWriter<Object, AggregateCha
WritingContext(Object root, AggregateChange<?> aggregateChange) {
this.root = root;
this.aggregateChange = aggregateChange;
paths = context.findPersistentPropertyPaths(aggregateChange.getEntityType(), PersistentProperty::isEntity);
this.entity = aggregateChange.getEntity();
this.entityType = aggregateChange.getEntityType();
this.paths = context.findPersistentPropertyPaths(entityType, PersistentProperty::isEntity);
}
private void write() {
private List<DbAction<?>> write() {
List<DbAction<?>> actions = new ArrayList<>();
if (isNew(root)) {
setRootAction(new DbAction.InsertRoot<>(aggregateChange.getEntity()));
insertReferenced();
actions.add(setRootAction(new DbAction.InsertRoot<>(entity)));
actions.addAll(insertReferenced());
} else {
deleteReferenced();
setRootAction(new DbAction.UpdateRoot<>(aggregateChange.getEntity()));
insertReferenced();
actions.addAll(deleteReferenced());
actions.add(setRootAction(new DbAction.UpdateRoot<>(entity)));
actions.addAll(insertReferenced());
}
return actions;
}
//// Operations on all paths
private void insertReferenced() {
private List<DbAction<?>> insertReferenced() {
paths.forEach(this::insertAll);
List<DbAction<?>> actions = new ArrayList<>();
paths.forEach(path -> actions.addAll(insertAll(path)));
return actions;
}
private void insertAll(PersistentPropertyPath<RelationalPersistentProperty> path) {
private List<DbAction<?>> insertAll(PersistentPropertyPath<RelationalPersistentProperty> path) {
List<DbAction<?>> actions = new ArrayList<>();
from(path).forEach(node -> {
DbAction.Insert<Object> insert;
if (node.path.getRequiredLeafProperty().isQualified()) {
KeyValue value = (KeyValue) node.getValue();
insert = new DbAction.Insert<>(value.value, path, getAction(node.parent));
insert.getAdditionalValues().put(node.path.getRequiredLeafProperty().getKeyColumn(), value.key);
Pair<Object, Object> value = (Pair) node.getValue();
insert = new DbAction.Insert<>(value.getSecond(), path, getAction(node.parent));
insert.getAdditionalValues().put(node.path.getRequiredLeafProperty().getKeyColumn(), value.getFirst());
} else {
insert = new DbAction.Insert<>(node.getValue(), path, getAction(node.parent));
}
previousActions.put(node, insert);
aggregateChange.addAction(insert);
actions.add(insert);
});
return actions;
}
private void deleteReferenced() {
private List<DbAction<?>> deleteReferenced() {
ArrayList<DbAction> deletes = new ArrayList<>();
List<DbAction<?>> deletes = new ArrayList<>();
paths.forEach(path -> deletes.add(0, deleteReferenced(path)));
deletes.forEach(aggregateChange::addAction);
return deletes;
}
/// Operations on a single path
private DbAction.Delete<?> deleteReferenced(PersistentPropertyPath<RelationalPersistentProperty> path) {
Object id = context.getRequiredPersistentEntity(aggregateChange.getEntityType())
.getIdentifierAccessor(aggregateChange.getEntity()).getIdentifier();
Object id = context.getRequiredPersistentEntity(entityType).getIdentifierAccessor(entity).getIdentifier();
return new DbAction.Delete<>(id, path);
}
//// methods not directly related to the creation of DbActions
private void setRootAction(DbAction<?> insert) {
private DbAction<?> setRootAction(DbAction<?> dbAction) {
previousActions.put(null, insert);
aggregateChange.addAction(insert);
previousActions.put(null, dbAction);
return dbAction;
}
@Nullable
private DbAction.WithEntity<?> getAction(@Nullable PathNode parent) {
DbAction action = previousActions.get(parent);
if (action != null) {
Assert.isInstanceOf(DbAction.WithEntity.class, action,
"dependsOn action is not a WithEntity, but " + action.getClass().getSimpleName());
return (DbAction.WithEntity<?>) action;
}
return null;
}
@@ -161,11 +180,12 @@ public class RelationalEntityWriter implements EntityWriter<Object, AggregateCha
private List<WritingContext.PathNode> from(PersistentPropertyPath<RelationalPersistentProperty> path) {
List<WritingContext.PathNode> nodes = new ArrayList<>();
if (path.getLength() == 1) {
Object value = context //
.getRequiredPersistentEntity(aggregateChange.getEntityType()) //
.getPropertyAccessor(aggregateChange.getEntity()) //
.getRequiredPersistentEntity(entityType) //
.getPropertyAccessor(entity) //
.getProperty(path.getRequiredLeafProperty());
nodes.addAll(createNodes(path, null, value));
@@ -185,7 +205,6 @@ public class RelationalEntityWriter implements EntityWriter<Object, AggregateCha
nodesCache.put(path, nodes);
return nodes;
}
private List<PathNode> createNodes(PersistentPropertyPath<RelationalPersistentProperty> path,
@@ -200,12 +219,12 @@ public class RelationalEntityWriter implements EntityWriter<Object, AggregateCha
if (path.getRequiredLeafProperty().isQualified()) {
if (path.getRequiredLeafProperty().isMap()) {
((Map<?, ?>) value).forEach((k, v) -> nodes.add(new PathNode(path, parentNode, new KeyValue(k, v))));
((Map<?, ?>) value).forEach((k, v) -> nodes.add(new PathNode(path, parentNode, Pair.of(k, v))));
} else {
List listValue = (List) value;
for (int k = 0; k < listValue.size(); k++) {
nodes.add(new PathNode(path, parentNode, new KeyValue(k, listValue.get(k))));
nodes.add(new PathNode(path, parentNode, Pair.of(k, listValue.get(k))));
}
}
} else if (path.getRequiredLeafProperty().isCollectionLike()) { // collection value
@@ -218,37 +237,16 @@ public class RelationalEntityWriter implements EntityWriter<Object, AggregateCha
}
/**
* represents a single entity in an aggregate along with its property path from the root entity and the chain of
* Represents a single entity in an aggregate along with its property path from the root entity and the chain of
* objects to traverse a long this path.
*/
@RequiredArgsConstructor
@Getter
private class PathNode {
private final PersistentPropertyPath<RelationalPersistentProperty> path;
@Nullable private final PathNode parent;
private final @Nullable PathNode parent;
private final Object value;
private PathNode(PersistentPropertyPath<RelationalPersistentProperty> path, @Nullable PathNode parent,
Object value) {
this.path = path;
this.parent = parent;
this.value = value;
}
public Object getValue() {
return value;
}
}
}
/**
* Holds key and value of a {@link Map.Entry} but without any ties to {@link Map} implementations.
*/
@Value
private static class KeyValue {
@NonNull Object key;
@NonNull Object value;
}
}