DATAJDBC-453 - DbActions and AggregateChange are effectively immutable.

Removed the Interpreter and replaced it with AggregateChangeExecutor and  AggregateChangeExecutionContext.
The latter handles the mutable data like ids and versions.

Original pull request: #197.
This commit is contained in:
Jens Schauder
2020-01-15 14:12:00 +01:00
committed by Mark Paluch
parent 6a1ef7d69c
commit 51b6784579
42 changed files with 1656 additions and 1523 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 the original author or authors.
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,346 +15,75 @@
*/
package org.springframework.data.jdbc.core;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiConsumer;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.relational.core.conversion.AggregateChange;
import org.springframework.data.relational.core.conversion.DbAction;
import org.springframework.data.relational.core.conversion.Interpreter;
import org.springframework.data.relational.core.conversion.RelationalConverter;
import org.springframework.data.relational.core.conversion.RelationalEntityVersionUtils;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.data.util.Pair;
import org.springframework.data.relational.core.conversion.DbActionExecutionException;
import org.springframework.data.relational.core.conversion.MutableAggregateChange;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Executes an {@link AggregateChange} by handing the included {@link DbAction} instances to the interpreter. In a
* second step ids generated by the {@link Interpreter} get propagated to the entities contained in the actions.
* Executes an {@link MutableAggregateChange}.
*
* @author Jens Schauder
* @author Mark Paluch
* @author Tyler Van Gorder
* @since 1.2
* @since 2.0
*/
class AggregateChangeExecutor {
private final Interpreter interpreter;
private final RelationalConverter converter;
private final MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> context;
private final JdbcConverter converter;
private final DataAccessStrategy accessStrategy;
AggregateChangeExecutor(Interpreter interpreter, RelationalConverter converter) {
AggregateChangeExecutor(JdbcConverter converter, DataAccessStrategy accessStrategy) {
this.interpreter = interpreter;
this.converter = converter;
this.context = converter.getMappingContext();
this.accessStrategy = accessStrategy;
}
<T> void execute(AggregateChange<T> aggregateChange) {
@Nullable
<T> T execute(AggregateChange<T> aggregateChange) {
List<DbAction<?>> actions = new ArrayList<>();
JdbcAggregateChangeExecutionContext executionContext = new JdbcAggregateChangeExecutionContext(converter,
accessStrategy);
aggregateChange.forEachAction(action -> {
aggregateChange.forEachAction(action -> execute(action, executionContext));
action.executeWith(interpreter);
actions.add(action);
});
T root = populateIdsIfNecessary(actions);
T root = executionContext.populateIdsIfNecessary();
root = root == null ? aggregateChange.getEntity() : root;
if (root != null) {
root = populateRootVersionIfNecessary(root, actions);
aggregateChange.setEntity(root);
}
}
private <T> T populateRootVersionIfNecessary(T newRoot, List<DbAction<?>> actions) {
// Does the root entity have a version attribute?
RelationalPersistentEntity<T> persistentEntity = (RelationalPersistentEntity<T>) context
.getRequiredPersistentEntity(newRoot.getClass());
if (!persistentEntity.hasVersionProperty()) {
return newRoot;
root = executionContext.populateRootVersionIfNecessary(root);
}
// Find the root action
Optional<DbAction<?>> rootAction = actions.parallelStream() //
.filter(action -> action instanceof DbAction.WithVersion) //
.findFirst();
if (!rootAction.isPresent()) {
// This really should never happen.
return newRoot;
}
DbAction.WithVersion versionAction = (DbAction.WithVersion) rootAction.get();
return RelationalEntityVersionUtils.setVersionNumberOnEntity(newRoot, versionAction.getNextVersion(),
persistentEntity, converter);
}
@SuppressWarnings("unchecked")
@Nullable
private <T> T populateIdsIfNecessary(List<DbAction<?>> actions) {
T newRoot = null;
// have the actions so that the inserts on the leaves come first.
List<DbAction<?>> reverseActions = new ArrayList<>(actions);
Collections.reverse(reverseActions);
AggregateChangeExecutor.StagedValues cascadingValues = new AggregateChangeExecutor.StagedValues();
for (DbAction<?> action : reverseActions) {
if (!(action instanceof DbAction.WithGeneratedId)) {
continue;
}
DbAction.WithGeneratedId<?> withGeneratedId = (DbAction.WithGeneratedId<?>) action;
Object generatedId = withGeneratedId.getGeneratedId();
Object newEntity = setIdAndCascadingProperties(withGeneratedId, generatedId, cascadingValues);
// the id property was immutable so we have to propagate changes up the tree
if (newEntity != ((DbAction.WithGeneratedId<?>) action).getEntity()) {
if (action instanceof DbAction.Insert) {
DbAction.Insert<?> insert = (DbAction.Insert<?>) action;
Pair<?, ?> qualifier = insert.getQualifier();
cascadingValues.stage(insert.getDependingOn(), insert.getPropertyPath(),
qualifier == null ? null : qualifier.getSecond(), newEntity);
} else if (action instanceof DbAction.InsertRoot) {
newRoot = (T) newEntity;
}
}
}
return newRoot;
}
private <S> Object setIdAndCascadingProperties(DbAction.WithGeneratedId<S> action, @Nullable Object generatedId,
AggregateChangeExecutor.StagedValues cascadingValues) {
S originalEntity = action.getEntity();
RelationalPersistentEntity<S> persistentEntity = (RelationalPersistentEntity<S>) context
.getRequiredPersistentEntity(action.getEntityType());
PersistentPropertyAccessor<S> propertyAccessor = converter.getPropertyAccessor(persistentEntity, originalEntity);
if (generatedId != null && persistentEntity.hasIdProperty()) {
propertyAccessor.setProperty(persistentEntity.getRequiredIdProperty(), generatedId);
}
// set values of changed immutables referenced by this entity
cascadingValues.forEachPath(action, (persistentPropertyPath, o) -> propertyAccessor
.setProperty(getRelativePath(action, persistentPropertyPath), o));
return propertyAccessor.getBean();
}
@SuppressWarnings("unchecked")
private PersistentPropertyPath<?> getRelativePath(DbAction<?> action, PersistentPropertyPath<?> pathToValue) {
if (action instanceof DbAction.Insert) {
return pathToValue.getExtensionForBaseOf(((DbAction.Insert) action).getPropertyPath());
}
if (action instanceof DbAction.InsertRoot) {
return pathToValue;
}
throw new IllegalArgumentException(String.format("DbAction of type %s is not supported.", action.getClass()));
}
/**
* Accumulates information about staged immutable objects in an aggregate that require updating because their state
* changed because of {@link DbAction} execution.
*/
private static class StagedValues {
static final List<MultiValueAggregator> aggregators = Arrays.asList(SetAggregator.INSTANCE, MapAggregator.INSTANCE,
ListAggregator.INSTANCE, SingleElementAggregator.INSTANCE);
Map<DbAction, Map<PersistentPropertyPath, Object>> values = new HashMap<>();
/**
* Adds a value that needs to be set in an entity higher up in the tree of entities in the aggregate. If the
* attribute to be set is multivalued this method expects only a single element.
*
* @param action The action responsible for persisting the entity that needs the added value set. Must not be
* {@literal null}.
* @param path The path to the property in which to set the value. Must not be {@literal null}.
* @param qualifier If {@code path} is a qualified multivalued properties this parameter contains the qualifier. May
* be {@literal null}.
* @param value The value to be set. Must not be {@literal null}.
*/
@SuppressWarnings("unchecked")
<T> void stage(DbAction<?> action, PersistentPropertyPath path, @Nullable Object qualifier, Object value) {
MultiValueAggregator<T> aggregator = getAggregatorFor(path);
Map<PersistentPropertyPath, Object> valuesForPath = this.values.computeIfAbsent(action,
dbAction -> new HashMap<>());
T currentValue = (T) valuesForPath.computeIfAbsent(path,
persistentPropertyPath -> aggregator.createEmptyInstance());
Object newValue = aggregator.add(currentValue, qualifier, value);
valuesForPath.put(path, newValue);
}
private MultiValueAggregator getAggregatorFor(PersistentPropertyPath path) {
PersistentProperty property = path.getRequiredLeafProperty();
for (MultiValueAggregator aggregator : aggregators) {
if (aggregator.handles(property)) {
return aggregator;
}
}
throw new IllegalStateException(String.format("Can't handle path %s", path));
}
/**
* Performs the given action for each entry in this the staging area that are provided by {@link DbAction} until all
* {@link PersistentPropertyPath} have been processed or the action throws an exception. The {@link BiConsumer
* action} is called with each applicable {@link PersistentPropertyPath} and {@code value} that is assignable to the
* property.
*/
void forEachPath(DbAction<?> dbAction, BiConsumer<PersistentPropertyPath, Object> action) {
values.getOrDefault(dbAction, Collections.emptyMap()).forEach(action);
}
}
interface MultiValueAggregator<T> {
default Class<? super T> handledType() {
return Object.class;
}
default boolean handles(PersistentProperty property) {
return handledType().isAssignableFrom(property.getType());
}
@Nullable
T createEmptyInstance();
T add(@Nullable T aggregate, @Nullable Object qualifier, Object value);
return root;
}
private enum SetAggregator implements MultiValueAggregator<Set> {
private void execute(DbAction<?> action, JdbcAggregateChangeExecutionContext executionContext) {
INSTANCE;
@Override
public Class<Set> handledType() {
return Set.class;
}
@Override
public Set createEmptyInstance() {
return new HashSet();
}
@SuppressWarnings("unchecked")
@Override
public Set add(@Nullable Set set, @Nullable Object qualifier, Object value) {
Assert.notNull(set, "Set must not be null");
set.add(value);
return set;
}
}
private enum ListAggregator implements MultiValueAggregator<List> {
INSTANCE;
@Override
public boolean handles(PersistentProperty property) {
return property.isCollectionLike();
}
@Override
public List createEmptyInstance() {
return new ArrayList();
}
@SuppressWarnings("unchecked")
@Override
public List add(@Nullable List list, @Nullable Object qualifier, Object value) {
Assert.notNull(list, "List must not be null.");
int index = (int) qualifier;
if (index >= list.size()) {
list.add(value);
try {
if (action instanceof DbAction.InsertRoot) {
executionContext.executeInsertRoot((DbAction.InsertRoot<?>) action);
} else if (action instanceof DbAction.Insert) {
executionContext.executeInsert((DbAction.Insert<?>) action);
} else if (action instanceof DbAction.UpdateRoot) {
executionContext.executeUpdateRoot((DbAction.UpdateRoot<?>) action);
} else if (action instanceof DbAction.Update) {
executionContext.executeUpdate((DbAction.Update<?>) action);
} else if (action instanceof DbAction.Delete) {
executionContext.executeDelete((DbAction.Delete<?>) action);
} else if (action instanceof DbAction.DeleteAll) {
executionContext.executeDeleteAll((DbAction.DeleteAll<?>) action);
} else if (action instanceof DbAction.DeleteRoot) {
executionContext.executeDeleteRoot((DbAction.DeleteRoot<?>) action);
} else if (action instanceof DbAction.DeleteAllRoot) {
executionContext.executeDeleteAllRoot((DbAction.DeleteAllRoot<?>) action);
} else {
list.add(index, value);
throw new RuntimeException("unexpected action");
}
return list;
} catch (Exception e) {
throw new DbActionExecutionException(action, e);
}
}
private enum MapAggregator implements MultiValueAggregator<Map> {
INSTANCE;
@Override
public Class<Map> handledType() {
return Map.class;
}
@Override
public Map createEmptyInstance() {
return new HashMap();
}
@SuppressWarnings("unchecked")
@Override
public Map add(@Nullable Map map, @Nullable Object qualifier, Object value) {
Assert.notNull(map, "Map must not be null.");
map.put(qualifier, value);
return map;
}
}
private enum SingleElementAggregator implements MultiValueAggregator<Object> {
INSTANCE;
@Override
@Nullable
public Object createEmptyInstance() {
return null;
}
@Override
public Object add(@Nullable Object __null, @Nullable Object qualifier, Object value) {
return value;
}
}
}

View File

@@ -1,285 +0,0 @@
/*
* Copyright 2017-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jdbc.core;
import lombok.RequiredArgsConstructor;
import java.util.Map;
import org.springframework.dao.IncorrectUpdateSemanticsDataAccessException;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.relational.core.conversion.DbAction;
import org.springframework.data.relational.core.conversion.DbAction.Delete;
import org.springframework.data.relational.core.conversion.DbAction.DeleteAll;
import org.springframework.data.relational.core.conversion.DbAction.DeleteAllRoot;
import org.springframework.data.relational.core.conversion.DbAction.DeleteRoot;
import org.springframework.data.relational.core.conversion.DbAction.Insert;
import org.springframework.data.relational.core.conversion.DbAction.InsertRoot;
import org.springframework.data.relational.core.conversion.DbAction.Merge;
import org.springframework.data.relational.core.conversion.DbAction.Update;
import org.springframework.data.relational.core.conversion.DbAction.UpdateRoot;
import org.springframework.data.relational.core.conversion.Interpreter;
import org.springframework.data.relational.core.conversion.RelationalEntityVersionUtils;
import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.data.relational.domain.Identifier;
import org.springframework.util.Assert;
/**
* {@link Interpreter} for {@link DbAction}s using a {@link DataAccessStrategy} for performing actual database
* interactions.
*
* @author Jens Schauder
* @author Mark Paluch
* @author Myeonghyeon Lee
* @author Tyler Van Gorder
*/
@RequiredArgsConstructor
class DefaultJdbcInterpreter implements Interpreter {
public static final String UPDATE_FAILED = "Failed to update entity [%s]. Id [%s] not found in database.";
public static final String UPDATE_FAILED_OPTIMISTIC_LOCKING = "Failed to update entity [%s]. The entity was updated since it was rea or it isn't in the database at all.";
private final JdbcConverter converter;
private final RelationalMappingContext context;
private final DataAccessStrategy 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) {
Object id = accessStrategy.insert(insert.getEntity(), insert.getEntityType(), getParentKeys(insert));
insert.setGeneratedId(id);
}
/*
* (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) {
RelationalPersistentEntity<T> persistentEntity = getRequiredPersistentEntity(insert.getEntityType());
Object id;
if (persistentEntity.hasVersionProperty()) {
T rootEntity = RelationalEntityVersionUtils.setVersionNumberOnEntity(insert.getEntity(), 1, persistentEntity,
converter);
id = accessStrategy.insert(rootEntity, insert.getEntityType(), Identifier.empty());
insert.setNextVersion(1);
} else {
id = accessStrategy.insert(insert.getEntity(), insert.getEntityType(), Identifier.empty());
}
insert.setGeneratedId(id);
}
/*
* (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) {
if (!accessStrategy.update(update.getEntity(), update.getEntityType())) {
throw new IncorrectUpdateSemanticsDataAccessException(
String.format(UPDATE_FAILED, update.getEntity(), getIdFrom(update)));
}
}
/*
* (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) {
RelationalPersistentEntity<T> persistentEntity = getRequiredPersistentEntity(update.getEntityType());
if (persistentEntity.hasVersionProperty()) {
updateWithVersion(update, persistentEntity);
} else {
updateWithoutVersion(update);
}
}
private <T> void updateWithoutVersion(UpdateRoot<T> update) {
if (!accessStrategy.update(update.getEntity(), update.getEntityType())) {
throw new IncorrectUpdateSemanticsDataAccessException(
String.format(UPDATE_FAILED, update.getEntity(), getIdFrom(update)));
}
}
private <T> void updateWithVersion(UpdateRoot<T> update, RelationalPersistentEntity<T> persistentEntity) {
// If the root aggregate has a version property, increment it.
Number previousVersion = RelationalEntityVersionUtils.getVersionNumberFromEntity(update.getEntity(),
persistentEntity, converter);
Assert.notNull(previousVersion, "The root aggregate cannot be updated because the version property is null.");
update.setNextVersion(previousVersion.longValue() + 1);
T rootEntity = RelationalEntityVersionUtils.setVersionNumberOnEntity(update.getEntity(), update.getNextVersion(),
persistentEntity, converter);
if (!accessStrategy.updateWithVersion(rootEntity, update.getEntityType(), previousVersion)) {
throw new OptimisticLockingFailureException(String.format(UPDATE_FAILED_OPTIMISTIC_LOCKING, update.getEntity()));
}
}
/*
* (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) {
// temporary implementation
if (!accessStrategy.update(merge.getEntity(), merge.getEntityType())) {
accessStrategy.insert(merge.getEntity(), merge.getEntityType(), getParentKeys(merge));
}
}
/*
* (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) {
if (delete.getPreviousVersion() != null) {
RelationalPersistentEntity<T> persistentEntity = getRequiredPersistentEntity(delete.getEntityType());
if (persistentEntity.hasVersionProperty()) {
accessStrategy.deleteWithVersion(delete.getId(), delete.getEntityType(), delete.getPreviousVersion());
return;
}
}
accessStrategy.delete(delete.getId(), 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());
}
private Identifier getParentKeys(DbAction.WithDependingOn<?> action) {
Object id = getParentId(action);
JdbcIdentifierBuilder identifier = JdbcIdentifierBuilder //
.forBackReferences(converter, new PersistentPropertyPathExtension(context, action.getPropertyPath()), id);
for (Map.Entry<PersistentPropertyPath<RelationalPersistentProperty>, Object> qualifier : action.getQualifiers()
.entrySet()) {
identifier = identifier.withQualifier(new PersistentPropertyPathExtension(context, qualifier.getKey()),
qualifier.getValue());
}
return identifier.build();
}
private Object getParentId(DbAction.WithDependingOn<?> action) {
PersistentPropertyPathExtension path = new PersistentPropertyPathExtension(context, action.getPropertyPath());
PersistentPropertyPathExtension idPath = path.getIdDefiningParentPath();
DbAction.WithEntity<?> idOwningAction = getIdOwningAction(action, idPath);
return getIdFrom(idOwningAction);
}
private DbAction.WithEntity<?> getIdOwningAction(DbAction.WithEntity<?> action,
PersistentPropertyPathExtension idPath) {
if (!(action instanceof DbAction.WithDependingOn)) {
Assert.state(idPath.getLength() == 0,
"When the id path is not empty the id providing action should be of type WithDependingOn");
return action;
}
DbAction.WithDependingOn<?> withDependingOn = (DbAction.WithDependingOn<?>) action;
if (idPath.matches(withDependingOn.getPropertyPath())) {
return action;
}
return getIdOwningAction(withDependingOn.getDependingOn(), idPath);
}
private Object getIdFrom(DbAction.WithEntity<?> idOwningAction) {
if (idOwningAction instanceof DbAction.WithGeneratedId) {
Object generatedId = ((DbAction.WithGeneratedId<?>) idOwningAction).getGeneratedId();
if (generatedId != null) {
return generatedId;
}
}
RelationalPersistentEntity<?> persistentEntity = context
.getRequiredPersistentEntity(idOwningAction.getEntityType());
Object identifier = persistentEntity.getIdentifierAccessor(idOwningAction.getEntity()).getIdentifier();
Assert.state(identifier != null, "Couldn't obtain a required id value");
return identifier;
}
@SuppressWarnings("unchecked")
private <T> RelationalPersistentEntity<T> getRequiredPersistentEntity(Class<T> type) {
return (RelationalPersistentEntity<T>) context.getRequiredPersistentEntity(type);
}
}

View File

@@ -0,0 +1,547 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jdbc.core;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import org.springframework.dao.IncorrectUpdateSemanticsDataAccessException;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.relational.core.conversion.AggregateChangeExecutionContext;
import org.springframework.data.relational.core.conversion.DbAction;
import org.springframework.data.relational.core.conversion.DbActionExecutionResult;
import org.springframework.data.relational.core.conversion.RelationalEntityVersionUtils;
import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.data.relational.domain.Identifier;
import org.springframework.data.util.Pair;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* @author Jens Schauder
*/
class JdbcAggregateChangeExecutionContext implements AggregateChangeExecutionContext {
private static final String UPDATE_FAILED = "Failed to update entity [%s]. Id [%s] not found in database.";
private static final String UPDATE_FAILED_OPTIMISTIC_LOCKING = "Failed to update entity [%s]. The entity was updated since it was rea or it isn't in the database at all.";
private final MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> context;
private final JdbcConverter converter;
private final DataAccessStrategy accessStrategy;
private final Map<DbAction<?>, DbActionExecutionResult> results = new LinkedHashMap<>();
@Nullable private Long version;
JdbcAggregateChangeExecutionContext(JdbcConverter converter, DataAccessStrategy accessStrategy) {
this.converter = converter;
this.context = converter.getMappingContext();
this.accessStrategy = accessStrategy;
}
<T> void executeInsertRoot(DbAction.InsertRoot<T> insert) {
RelationalPersistentEntity<T> persistentEntity = getRequiredPersistentEntity(insert.getEntityType());
Object id;
if (persistentEntity.hasVersionProperty()) {
T rootEntity = RelationalEntityVersionUtils.setVersionNumberOnEntity(insert.getEntity(), 1, persistentEntity,
converter);
id = accessStrategy.insert(rootEntity, insert.getEntityType(), Identifier.empty());
setNewVersion(1);
} else {
id = accessStrategy.insert(insert.getEntity(), insert.getEntityType(), Identifier.empty());
}
add(new DbActionExecutionResult(insert, id));
}
<T> void executeInsert(DbAction.Insert<T> insert) {
Identifier parentKeys = getParentKeys(insert, converter);
Object id = accessStrategy.insert(insert.getEntity(), insert.getEntityType(), parentKeys);
add(new DbActionExecutionResult(insert, id));
}
<T> void executeUpdateRoot(DbAction.UpdateRoot<T> update) {
RelationalPersistentEntity<T> persistentEntity = getRequiredPersistentEntity(update.getEntityType());
if (persistentEntity.hasVersionProperty()) {
updateWithVersion(update, persistentEntity);
} else {
updateWithoutVersion(update);
}
}
<T> void executeUpdate(DbAction.Update<T> update) {
if (!accessStrategy.update(update.getEntity(), update.getEntityType())) {
throw new IncorrectUpdateSemanticsDataAccessException(
String.format(UPDATE_FAILED, update.getEntity(), getIdFrom(update)));
}
}
<T> void executeDeleteRoot(DbAction.DeleteRoot<T> delete) {
if (delete.getPreviousVersion() != null) {
RelationalPersistentEntity<T> persistentEntity = getRequiredPersistentEntity(delete.getEntityType());
if (persistentEntity.hasVersionProperty()) {
accessStrategy.deleteWithVersion(delete.getId(), delete.getEntityType(), delete.getPreviousVersion());
return;
}
}
accessStrategy.delete(delete.getId(), delete.getEntityType());
}
<T> void executeDelete(DbAction.Delete<T> delete) {
accessStrategy.delete(delete.getRootId(), delete.getPropertyPath());
}
<T> void executeDeleteAllRoot(DbAction.DeleteAllRoot<T> deleteAllRoot) {
accessStrategy.deleteAll(deleteAllRoot.getEntityType());
}
<T> void executeDeleteAll(DbAction.DeleteAll<T> delete) {
accessStrategy.deleteAll(delete.getPropertyPath());
}
<T> void executeMerge(DbAction.Merge<T> merge) {
// temporary implementation
if (!accessStrategy.update(merge.getEntity(), merge.getEntityType())) {
Object id = accessStrategy.insert(merge.getEntity(), merge.getEntityType(), getParentKeys(merge, converter));
add(new DbActionExecutionResult(merge, id));
} else {
add(new DbActionExecutionResult());
}
}
private void add(DbActionExecutionResult result) {
results.put(result.getAction(), result);
}
private Identifier getParentKeys(DbAction.WithDependingOn<?> action, JdbcConverter converter) {
Object id = getParentId(action);
JdbcIdentifierBuilder identifier = JdbcIdentifierBuilder //
.forBackReferences(converter, new PersistentPropertyPathExtension(context, action.getPropertyPath()), id);
for (Map.Entry<PersistentPropertyPath<RelationalPersistentProperty>, Object> qualifier : action.getQualifiers()
.entrySet()) {
identifier = identifier.withQualifier(new PersistentPropertyPathExtension(context, qualifier.getKey()),
qualifier.getValue());
}
return identifier.build();
}
private Object getParentId(DbAction.WithDependingOn<?> action) {
PersistentPropertyPathExtension path = new PersistentPropertyPathExtension(context, action.getPropertyPath());
PersistentPropertyPathExtension idPath = path.getIdDefiningParentPath();
DbAction.WithEntity<?> idOwningAction = getIdOwningAction(action, idPath);
return getPotentialGeneratedIdFrom(idOwningAction);
}
private DbAction.WithEntity<?> getIdOwningAction(DbAction.WithEntity<?> action,
PersistentPropertyPathExtension idPath) {
if (!(action instanceof DbAction.WithDependingOn)) {
Assert.state(idPath.getLength() == 0,
"When the id path is not empty the id providing action should be of type WithDependingOn");
return action;
}
DbAction.WithDependingOn<?> withDependingOn = (DbAction.WithDependingOn<?>) action;
if (idPath.matches(withDependingOn.getPropertyPath())) {
return action;
}
return getIdOwningAction(withDependingOn.getDependingOn(), idPath);
}
private Object getPotentialGeneratedIdFrom(DbAction.WithEntity<?> idOwningAction) {
if (idOwningAction instanceof DbAction.WithGeneratedId) {
Object generatedId;
DbActionExecutionResult dbActionExecutionResult = results.get(idOwningAction);
generatedId = dbActionExecutionResult == null ? null : dbActionExecutionResult.getId();
if (generatedId != null) {
return generatedId;
}
}
return getIdFrom(idOwningAction);
}
private Object getIdFrom(DbAction.WithEntity<?> idOwningAction) {
RelationalPersistentEntity<?> persistentEntity = getRequiredPersistentEntity(idOwningAction.getEntityType());
Object identifier = persistentEntity.getIdentifierAccessor(idOwningAction.getEntity()).getIdentifier();
Assert.state(identifier != null, "Couldn't obtain a required id value");
return identifier;
}
private void setNewVersion(long version) {
Assert.isNull(this.version, "A new version was set a second time.");
this.version = version;
}
private long getNewVersion() {
Assert.notNull(version, "A new version was requested, but none was set.");
return version;
}
private boolean hasNewVersion() {
return version != null;
}
<T> T populateRootVersionIfNecessary(T newRoot) {
if (!hasNewVersion()) {
return newRoot;
}
// Does the root entity have a version attribute?
RelationalPersistentEntity<T> persistentEntity = (RelationalPersistentEntity<T>) context
.getRequiredPersistentEntity(newRoot.getClass());
return RelationalEntityVersionUtils.setVersionNumberOnEntity(newRoot, getNewVersion(), persistentEntity, converter);
}
@SuppressWarnings("unchecked")
@Nullable
<T> T populateIdsIfNecessary() {
T newRoot = null;
// have the results so that the inserts on the leaves come first.
List<DbActionExecutionResult> reverseResults = new ArrayList<>(results.values());
Collections.reverse(reverseResults);
StagedValues cascadingValues = new StagedValues();
for (DbActionExecutionResult result : reverseResults) {
DbAction<?> action = result.getAction();
if (!(action instanceof DbAction.WithGeneratedId)) {
continue;
}
DbAction.WithEntity<?> withEntity = (DbAction.WithGeneratedId<?>) action;
Object newEntity = setIdAndCascadingProperties(withEntity, result.getId(), cascadingValues);
// the id property was immutable so we have to propagate changes up the tree
if (newEntity != withEntity.getEntity()) {
if (action instanceof DbAction.Insert) {
DbAction.Insert<?> insert = (DbAction.Insert<?>) action;
Pair<?, ?> qualifier = insert.getQualifier();
cascadingValues.stage(insert.getDependingOn(), insert.getPropertyPath(),
qualifier == null ? null : qualifier.getSecond(), newEntity);
} else if (action instanceof DbAction.InsertRoot) {
newRoot = (T) newEntity;
}
}
}
return newRoot;
}
private <S> Object setIdAndCascadingProperties(DbAction.WithEntity<S> action, @Nullable Object generatedId,
StagedValues cascadingValues) {
S originalEntity = action.getEntity();
RelationalPersistentEntity<S> persistentEntity = (RelationalPersistentEntity<S>) context
.getRequiredPersistentEntity(action.getEntityType());
PersistentPropertyAccessor<S> propertyAccessor = converter.getPropertyAccessor(persistentEntity, originalEntity);
if (generatedId != null && persistentEntity.hasIdProperty()) {
propertyAccessor.setProperty(persistentEntity.getRequiredIdProperty(), generatedId);
}
// set values of changed immutables referenced by this entity
cascadingValues.forEachPath(action, (persistentPropertyPath, o) -> propertyAccessor
.setProperty(getRelativePath(action, persistentPropertyPath), o));
return propertyAccessor.getBean();
}
@SuppressWarnings("unchecked")
private PersistentPropertyPath<?> getRelativePath(DbAction<?> action, PersistentPropertyPath<?> pathToValue) {
if (action instanceof DbAction.Insert) {
return pathToValue.getExtensionForBaseOf(((DbAction.Insert) action).getPropertyPath());
}
if (action instanceof DbAction.InsertRoot) {
return pathToValue;
}
throw new IllegalArgumentException(String.format("DbAction of type %s is not supported.", action.getClass()));
}
private <T> RelationalPersistentEntity<T> getRequiredPersistentEntity(Class<T> type) {
return (RelationalPersistentEntity<T>) context.getRequiredPersistentEntity(type);
}
private <T> void updateWithoutVersion(DbAction.UpdateRoot<T> update) {
if (!accessStrategy.update(update.getEntity(), update.getEntityType())) {
throw new IncorrectUpdateSemanticsDataAccessException(
String.format(UPDATE_FAILED, update.getEntity(), getIdFrom(update)));
}
}
private <T> void updateWithVersion(DbAction.UpdateRoot<T> update, RelationalPersistentEntity<T> persistentEntity) {
// If the root aggregate has a version property, increment it.
Number previousVersion = RelationalEntityVersionUtils.getVersionNumberFromEntity(update.getEntity(),
persistentEntity, converter);
Assert.notNull(previousVersion, "The root aggregate cannot be updated because the version property is null.");
setNewVersion(previousVersion.longValue() + 1);
T rootEntity = RelationalEntityVersionUtils.setVersionNumberOnEntity(update.getEntity(), getNewVersion(),
persistentEntity, converter);
if (!accessStrategy.updateWithVersion(rootEntity, update.getEntityType(), previousVersion)) {
throw new OptimisticLockingFailureException(String.format(UPDATE_FAILED_OPTIMISTIC_LOCKING, update.getEntity()));
}
}
/**
* Accumulates information about staged immutable objects in an aggregate that require updating because their state
* changed because of {@link DbAction} execution.
*/
private static class StagedValues {
static final List<MultiValueAggregator> aggregators = Arrays.asList(SetAggregator.INSTANCE, MapAggregator.INSTANCE,
ListAggregator.INSTANCE, SingleElementAggregator.INSTANCE);
Map<DbAction, Map<PersistentPropertyPath, Object>> values = new HashMap<>();
/**
* Adds a value that needs to be set in an entity higher up in the tree of entities in the aggregate. If the
* attribute to be set is multivalued this method expects only a single element.
*
* @param action The action responsible for persisting the entity that needs the added value set. Must not be
* {@literal null}.
* @param path The path to the property in which to set the value. Must not be {@literal null}.
* @param qualifier If {@code path} is a qualified multivalued properties this parameter contains the qualifier. May
* be {@literal null}.
* @param value The value to be set. Must not be {@literal null}.
*/
@SuppressWarnings("unchecked")
<T> void stage(DbAction<?> action, PersistentPropertyPath path, @Nullable Object qualifier, Object value) {
MultiValueAggregator<T> aggregator = getAggregatorFor(path);
Map<PersistentPropertyPath, Object> valuesForPath = this.values.computeIfAbsent(action,
dbAction -> new HashMap<>());
T currentValue = (T) valuesForPath.computeIfAbsent(path,
persistentPropertyPath -> aggregator.createEmptyInstance());
Object newValue = aggregator.add(currentValue, qualifier, value);
valuesForPath.put(path, newValue);
}
private MultiValueAggregator getAggregatorFor(PersistentPropertyPath path) {
PersistentProperty property = path.getRequiredLeafProperty();
for (MultiValueAggregator aggregator : aggregators) {
if (aggregator.handles(property)) {
return aggregator;
}
}
throw new IllegalStateException(String.format("Can't handle path %s", path));
}
/**
* Performs the given action for each entry in this the staging area that are provided by {@link DbAction} until all
* {@link PersistentPropertyPath} have been processed or the action throws an exception. The {@link BiConsumer
* action} is called with each applicable {@link PersistentPropertyPath} and {@code value} that is assignable to the
* property.
*/
void forEachPath(DbAction<?> dbAction, BiConsumer<PersistentPropertyPath, Object> action) {
values.getOrDefault(dbAction, Collections.emptyMap()).forEach(action);
}
}
interface MultiValueAggregator<T> {
default Class<? super T> handledType() {
return Object.class;
}
default boolean handles(PersistentProperty property) {
return handledType().isAssignableFrom(property.getType());
}
@Nullable
T createEmptyInstance();
T add(@Nullable T aggregate, @Nullable Object qualifier, Object value);
}
private enum SetAggregator implements MultiValueAggregator<Set> {
INSTANCE;
@Override
public Class<Set> handledType() {
return Set.class;
}
@Override
public Set createEmptyInstance() {
return new HashSet();
}
@SuppressWarnings("unchecked")
@Override
public Set add(@Nullable Set set, @Nullable Object qualifier, Object value) {
Assert.notNull(set, "Set must not be null");
set.add(value);
return set;
}
}
private enum ListAggregator implements MultiValueAggregator<List> {
INSTANCE;
@Override
public boolean handles(PersistentProperty property) {
return property.isCollectionLike();
}
@Override
public List createEmptyInstance() {
return new ArrayList();
}
@SuppressWarnings("unchecked")
@Override
public List add(@Nullable List list, @Nullable Object qualifier, Object value) {
Assert.notNull(list, "List must not be null.");
int index = (int) qualifier;
if (index >= list.size()) {
list.add(value);
} else {
list.add(index, value);
}
return list;
}
}
private enum MapAggregator implements MultiValueAggregator<Map> {
INSTANCE;
@Override
public Class<Map> handledType() {
return Map.class;
}
@Override
public Map createEmptyInstance() {
return new HashMap();
}
@SuppressWarnings("unchecked")
@Override
public Map add(@Nullable Map map, @Nullable Object qualifier, Object value) {
Assert.notNull(map, "Map must not be null.");
map.put(qualifier, value);
return map;
}
}
private enum SingleElementAggregator implements MultiValueAggregator<Object> {
INSTANCE;
@Override
@Nullable
public Object createEmptyInstance() {
return null;
}
@Override
public Object add(@Nullable Object __null, @Nullable Object qualifier, Object value) {
return value;
}
}
}

View File

@@ -32,7 +32,7 @@ import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.mapping.IdentifierAccessor;
import org.springframework.data.mapping.callback.EntityCallbacks;
import org.springframework.data.relational.core.conversion.AggregateChange;
import org.springframework.data.relational.core.conversion.Interpreter;
import org.springframework.data.relational.core.conversion.MutableAggregateChange;
import org.springframework.data.relational.core.conversion.RelationalEntityDeleteWriter;
import org.springframework.data.relational.core.conversion.RelationalEntityInsertWriter;
import org.springframework.data.relational.core.conversion.RelationalEntityUpdateWriter;
@@ -55,7 +55,6 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
private final ApplicationEventPublisher publisher;
private final RelationalMappingContext context;
private final Interpreter interpreter;
private final RelationalEntityDeleteWriter jdbcEntityDeleteWriter;
private final RelationalEntityInsertWriter jdbcEntityInsertWriter;
@@ -90,9 +89,8 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
this.jdbcEntityInsertWriter = new RelationalEntityInsertWriter(context);
this.jdbcEntityUpdateWriter = new RelationalEntityUpdateWriter(context);
this.jdbcEntityDeleteWriter = new RelationalEntityDeleteWriter(context);
this.interpreter = new DefaultJdbcInterpreter(converter, context, accessStrategy);
this.executor = new AggregateChangeExecutor(interpreter, converter);
this.executor = new AggregateChangeExecutor(converter, accessStrategy);
setEntityCallbacks(EntityCallbacks.create(publisher));
}
@@ -120,8 +118,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
this.jdbcEntityInsertWriter = new RelationalEntityInsertWriter(context);
this.jdbcEntityUpdateWriter = new RelationalEntityUpdateWriter(context);
this.jdbcEntityDeleteWriter = new RelationalEntityDeleteWriter(context);
this.interpreter = new DefaultJdbcInterpreter(converter, context, accessStrategy);
this.executor = new AggregateChangeExecutor(interpreter, converter);
this.executor = new AggregateChangeExecutor(converter, accessStrategy);
}
/**
@@ -146,7 +143,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
RelationalPersistentEntity<?> persistentEntity = context.getRequiredPersistentEntity(instance.getClass());
Function<T, AggregateChange<T>> changeCreator = persistentEntity.isNew(instance) ? this::createInsertChange
Function<T, MutableAggregateChange<T>> changeCreator = persistentEntity.isNew(instance) ? this::createInsertChange
: this::createUpdateChange;
return store(instance, changeCreator, persistentEntity);
@@ -322,35 +319,35 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
Assert.notNull(domainType, "Domain type must not be null!");
AggregateChange<?> change = createDeletingChange(domainType);
MutableAggregateChange<?> change = createDeletingChange(domainType);
executor.execute(change);
}
private <T> T store(T aggregateRoot, Function<T, AggregateChange<T>> changeCreator,
private <T> T store(T aggregateRoot, Function<T, MutableAggregateChange<T>> changeCreator,
RelationalPersistentEntity<?> persistentEntity) {
Assert.notNull(aggregateRoot, "Aggregate instance must not be null!");
aggregateRoot = triggerBeforeConvert(aggregateRoot);
AggregateChange<T> change = changeCreator.apply(aggregateRoot);
MutableAggregateChange<T> change = changeCreator.apply(aggregateRoot);
aggregateRoot = triggerBeforeSave(aggregateRoot, change);
change.setEntity(aggregateRoot);
executor.execute(change);
T entityAfterExecution = executor.execute(change);
Object identifier = persistentEntity.getIdentifierAccessor(change.getEntity()).getIdentifier();
Object identifier = persistentEntity.getIdentifierAccessor(entityAfterExecution).getIdentifier();
Assert.notNull(identifier, "After saving the identifier must not be null!");
return triggerAfterSave(change.getEntity(), change);
return triggerAfterSave(entityAfterExecution, change);
}
private <T> void deleteTree(Object id, @Nullable T entity, Class<T> domainType) {
AggregateChange<T> change = createDeletingChange(id, entity, domainType);
MutableAggregateChange<T> change = createDeletingChange(id, entity, domainType);
entity = triggerBeforeDelete(entity, id, change);
change.setEntity(entity);
@@ -360,30 +357,30 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
triggerAfterDelete(entity, id, change);
}
private <T> AggregateChange<T> createInsertChange(T instance) {
private <T> MutableAggregateChange<T> createInsertChange(T instance) {
AggregateChange<T> aggregateChange = AggregateChange.forSave(instance);
MutableAggregateChange<T> aggregateChange = MutableAggregateChange.forSave(instance);
jdbcEntityInsertWriter.write(instance, aggregateChange);
return aggregateChange;
}
private <T> AggregateChange<T> createUpdateChange(T instance) {
private <T> MutableAggregateChange<T> createUpdateChange(T instance) {
AggregateChange<T> aggregateChange = AggregateChange.forSave(instance);
MutableAggregateChange<T> aggregateChange = MutableAggregateChange.forSave(instance);
jdbcEntityUpdateWriter.write(instance, aggregateChange);
return aggregateChange;
}
private <T> AggregateChange<T> createDeletingChange(Object id, @Nullable T entity, Class<T> domainType) {
private <T> MutableAggregateChange<T> createDeletingChange(Object id, @Nullable T entity, Class<T> domainType) {
AggregateChange<T> aggregateChange = AggregateChange.forDelete(domainType, entity);
MutableAggregateChange<T> aggregateChange = MutableAggregateChange.forDelete(domainType, entity);
jdbcEntityDeleteWriter.write(id, aggregateChange);
return aggregateChange;
}
private AggregateChange<?> createDeletingChange(Class<?> domainType) {
private MutableAggregateChange<?> createDeletingChange(Class<?> domainType) {
AggregateChange<?> aggregateChange = AggregateChange.forDelete(domainType, null);
MutableAggregateChange<?> aggregateChange = MutableAggregateChange.forDelete(domainType, null);
jdbcEntityDeleteWriter.write(null, aggregateChange);
return aggregateChange;
}
@@ -424,7 +421,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
return entityCallbacks.callback(AfterSaveCallback.class, aggregateRoot);
}
private <T> void triggerAfterDelete(@Nullable T aggregateRoot, Object id, AggregateChange<T> change) {
private <T> void triggerAfterDelete(@Nullable T aggregateRoot, Object id, MutableAggregateChange<T> change) {
publisher.publishEvent(new AfterDeleteEvent<>(Identifier.of(id), aggregateRoot, change));
@@ -434,7 +431,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
}
@Nullable
private <T> T triggerBeforeDelete(@Nullable T aggregateRoot, Object id, AggregateChange<T> change) {
private <T> T triggerBeforeDelete(@Nullable T aggregateRoot, Object id, MutableAggregateChange<T> change) {
publisher.publishEvent(new BeforeDeleteEvent<>(Identifier.of(id), aggregateRoot, change));

View File

@@ -325,7 +325,7 @@ public class BasicJdbcConverter extends BasicRelationalConverter implements Jdbc
public <T> T mapRow(RelationalPersistentEntity<T> entity, ResultSet resultSet, Object key) {
return new ReadingContext<T>(
new PersistentPropertyPathExtension(
(MappingContext<RelationalPersistentEntity<?>, RelationalPersistentProperty>) getMappingContext(), entity),
getMappingContext(), entity),
resultSet, Identifier.empty(), key).mapRow();
}
@@ -356,7 +356,7 @@ public class BasicJdbcConverter extends BasicRelationalConverter implements Jdbc
this.resultSet = resultSet;
this.rootPath = rootPath;
this.path = new PersistentPropertyPathExtension(
(MappingContext<RelationalPersistentEntity<?>, RelationalPersistentProperty>) getMappingContext(),
getMappingContext(),
this.entity);
this.identifier = identifier;
this.key = key;
@@ -431,7 +431,7 @@ public class BasicJdbcConverter extends BasicRelationalConverter implements Jdbc
? this.identifier.withPart(rootPath.getQualifierColumn(), key, Object.class) //
: Identifier.of(rootPath.extendBy(property).getReverseColumnName(), id, Object.class);
PersistentPropertyPath<RelationalPersistentProperty> propertyPath = path.extendBy(property)
PersistentPropertyPath<? extends RelationalPersistentProperty> propertyPath = path.extendBy(property)
.getRequiredPersistentPropertyPath();
return relationResolver.findAllByPath(identifier, propertyPath);

View File

@@ -25,8 +25,8 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.data.relational.domain.Identifier;
import org.springframework.data.relational.core.sql.SqlIdentifier;
import org.springframework.data.relational.domain.Identifier;
/**
* Delegates each methods to the {@link DataAccessStrategy}s passed to the constructor in turn until the first that does
@@ -169,7 +169,7 @@ public class CascadingDataAccessStrategy implements DataAccessStrategy {
*/
@Override
public Iterable<Object> findAllByPath(Identifier identifier,
PersistentPropertyPath<RelationalPersistentProperty> path) {
PersistentPropertyPath<? extends RelationalPersistentProperty> path) {
return collect(das -> das.findAllByPath(identifier, path));
}

View File

@@ -23,8 +23,8 @@ import org.springframework.data.domain.Sort;
import org.springframework.data.jdbc.core.JdbcAggregateOperations;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.data.relational.domain.Identifier;
import org.springframework.data.relational.core.sql.SqlIdentifier;
import org.springframework.data.relational.domain.Identifier;
import org.springframework.lang.Nullable;
/**
@@ -193,11 +193,11 @@ public interface DataAccessStrategy extends RelationResolver {
*/
@Override
default Iterable<Object> findAllByPath(Identifier identifier,
PersistentPropertyPath<RelationalPersistentProperty> path) {
PersistentPropertyPath<? extends RelationalPersistentProperty> path) {
Object rootId = identifier.toMap().get(path.getRequiredLeafProperty().getReverseColumnName());
return findAllByProperty(rootId, path.getRequiredLeafProperty());
};
}
/**
* Finds all entities reachable via {@literal property} from the instance identified by {@literal rootId}.

View File

@@ -319,7 +319,7 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
@Override
@SuppressWarnings("unchecked")
public Iterable<Object> findAllByPath(Identifier identifier,
PersistentPropertyPath<RelationalPersistentProperty> propertyPath) {
PersistentPropertyPath<? extends RelationalPersistentProperty> propertyPath) {
Assert.notNull(identifier, "identifier must not be null.");
Assert.notNull(propertyPath, "propertyPath must not be null.");

View File

@@ -21,8 +21,8 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.data.relational.domain.Identifier;
import org.springframework.data.relational.core.sql.SqlIdentifier;
import org.springframework.data.relational.domain.Identifier;
import org.springframework.util.Assert;
/**
@@ -165,7 +165,7 @@ public class DelegatingDataAccessStrategy implements DataAccessStrategy {
*/
@Override
public Iterable<Object> findAllByPath(Identifier identifier,
PersistentPropertyPath<RelationalPersistentProperty> path) {
PersistentPropertyPath<? extends RelationalPersistentProperty> path) {
return delegate.findAllByPath(identifier, path);
}

View File

@@ -35,5 +35,6 @@ public interface RelationResolver {
* @param path the path from the aggregate root to the entities to be resolved. Must not be {@literal null}.
* @return guaranteed to be not {@literal null}.
*/
Iterable<Object> findAllByPath(Identifier identifier, PersistentPropertyPath<RelationalPersistentProperty> path);
Iterable<Object> findAllByPath(Identifier identifier,
PersistentPropertyPath<? extends RelationalPersistentProperty> path);
}

View File

@@ -27,7 +27,6 @@ import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.SqlSessionTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jdbc.core.convert.CascadingDataAccessStrategy;
@@ -300,7 +299,7 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
@Override
public Iterable<Object> findAllByPath(Identifier identifier,
PersistentPropertyPath<RelationalPersistentProperty> path) {
PersistentPropertyPath<? extends RelationalPersistentProperty> path) {
String statementName = namespace(path.getBaseProperty().getOwner().getType()) + ".findAllByPath-"
+ path.toDotPath();

View File

@@ -19,6 +19,7 @@ import static java.util.Arrays.*;
import static java.util.Collections.*;
import static org.assertj.core.api.Assertions.*;
import static org.assertj.core.api.SoftAssertions.*;
import static org.mockito.Mockito.*;
import lombok.AllArgsConstructor;
import lombok.Value;
@@ -31,15 +32,15 @@ import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.mapping.PersistentPropertyPaths;
import org.springframework.data.relational.core.conversion.AggregateChange;
import org.springframework.data.relational.core.conversion.BasicRelationalConverter;
import org.springframework.data.relational.core.conversion.DbAction;
import org.springframework.data.relational.core.conversion.Interpreter;
import org.springframework.data.relational.core.conversion.RelationalConverter;
import org.springframework.data.relational.core.conversion.MutableAggregateChange;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Embedded;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
@@ -47,12 +48,13 @@ import org.springframework.data.relational.core.mapping.RelationalPersistentProp
import org.springframework.lang.Nullable;
/**
* Unit tests for the {@link AggregateChange} testing the setting of generated ids in aggregates consisting of immutable
* entities.
* Unit tests for the {@link MutableAggregateChange} testing the setting of generated ids in aggregates consisting of
* immutable entities.
*
* @author Jens Schauder
* @author Myeonghyeon-Lee
*/
@Ignore
public class AggregateChangeIdGenerationImmutableUnitTests {
DummyEntity entity = new DummyEntity();
@@ -65,20 +67,20 @@ public class AggregateChangeIdGenerationImmutableUnitTests {
ContentNoId contentNoId2 = new ContentNoId();
RelationalMappingContext context = new RelationalMappingContext();
RelationalConverter converter = new BasicRelationalConverter(context);
JdbcConverter converter = mock(JdbcConverter.class);
DbAction.WithEntity<?> rootInsert = new DbAction.InsertRoot<>(entity);
AggregateChangeExecutor executor = new AggregateChangeExecutor(new IdSettingInterpreter(), converter);
private DataAccessStrategy accessStrategy = mock(DataAccessStrategy.class);
AggregateChangeExecutor executor = new AggregateChangeExecutor(converter, accessStrategy);
@Test // DATAJDBC-291
public void singleRoot() {
AggregateChange<DummyEntity> aggregateChange = AggregateChange.forSave(entity);
MutableAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
executor.execute(aggregateChange);
entity = aggregateChange.getEntity();
entity = executor.execute(aggregateChange);
assertThat(entity.rootId).isEqualTo(1);
}
@@ -88,13 +90,11 @@ public class AggregateChangeIdGenerationImmutableUnitTests {
entity = entity.withSingle(content);
AggregateChange<DummyEntity> aggregateChange = AggregateChange.forSave(entity);
MutableAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
aggregateChange.addAction(createInsert("single", content, null));
executor.execute(aggregateChange);
entity = aggregateChange.getEntity();
entity = executor.execute(aggregateChange);
assertSoftly(softly -> {
@@ -108,14 +108,12 @@ public class AggregateChangeIdGenerationImmutableUnitTests {
entity = entity.withContentList(asList(content, content2));
AggregateChange<DummyEntity> aggregateChange = AggregateChange.forSave(entity);
MutableAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
aggregateChange.addAction(createInsert("contentList", content, 0));
aggregateChange.addAction(createInsert("contentList", content2, 1));
executor.execute(aggregateChange);
entity = aggregateChange.getEntity();
entity = executor.execute(aggregateChange);
assertSoftly(softly -> {
@@ -129,14 +127,12 @@ public class AggregateChangeIdGenerationImmutableUnitTests {
entity = entity.withContentMap(createContentMap("a", content, "b", content2));
AggregateChange<DummyEntity> aggregateChange = AggregateChange.forSave(entity);
MutableAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
aggregateChange.addAction(createInsert("contentMap", content, "a"));
aggregateChange.addAction(createInsert("contentMap", content2, "b"));
executor.execute(aggregateChange);
entity = aggregateChange.getEntity();
entity = executor.execute(aggregateChange);
assertThat(entity.rootId).isEqualTo(1);
assertThat(entity.contentMap.values()).extracting(c -> c.id).containsExactly(2, 3);
@@ -151,14 +147,12 @@ public class AggregateChangeIdGenerationImmutableUnitTests {
DbAction.Insert<?> parentInsert = createInsert("single", content, null);
DbAction.Insert<?> insert = createDeepInsert("single", tag1, null, parentInsert);
AggregateChange<DummyEntity> aggregateChange = AggregateChange.forSave(entity);
MutableAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
aggregateChange.addAction(parentInsert);
aggregateChange.addAction(insert);
executor.execute(aggregateChange);
entity = aggregateChange.getEntity();
entity = executor.execute(aggregateChange);
assertThat(entity.rootId).isEqualTo(1);
assertThat(entity.single.id).isEqualTo(2);
@@ -175,15 +169,13 @@ public class AggregateChangeIdGenerationImmutableUnitTests {
DbAction.Insert<?> insert1 = createDeepInsert("tagList", tag1, 0, parentInsert);
DbAction.Insert<?> insert2 = createDeepInsert("tagList", tag2, 1, parentInsert);
AggregateChange<DummyEntity> aggregateChange = AggregateChange.forSave(entity);
MutableAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
aggregateChange.addAction(parentInsert);
aggregateChange.addAction(insert1);
aggregateChange.addAction(insert2);
executor.execute(aggregateChange);
entity = aggregateChange.getEntity();
entity = executor.execute(aggregateChange);
assertSoftly(softly -> {
@@ -203,15 +195,13 @@ public class AggregateChangeIdGenerationImmutableUnitTests {
DbAction.Insert<?> insert1 = createDeepInsert("tagSet", tag1, null, parentInsert);
DbAction.Insert<?> insert2 = createDeepInsert("tagSet", tag2, null, parentInsert);
AggregateChange<DummyEntity> aggregateChange = AggregateChange.forSave(entity);
MutableAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
aggregateChange.addAction(parentInsert);
aggregateChange.addAction(insert1);
aggregateChange.addAction(insert2);
executor.execute(aggregateChange);
entity = aggregateChange.getEntity();
entity = executor.execute(aggregateChange);
assertSoftly(softly -> {
@@ -238,16 +228,14 @@ public class AggregateChangeIdGenerationImmutableUnitTests {
DbAction.Insert<?> insert1 = createDeepInsert("single", tag1, null, parentInsert1);
DbAction.Insert<?> insert2 = createDeepInsert("single", tag2, null, parentInsert2);
AggregateChange<DummyEntity> aggregateChange = AggregateChange.forSave(entity);
MutableAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
aggregateChange.addAction(parentInsert1);
aggregateChange.addAction(parentInsert2);
aggregateChange.addAction(insert1);
aggregateChange.addAction(insert2);
executor.execute(aggregateChange);
entity = aggregateChange.getEntity();
entity = executor.execute(aggregateChange);
assertSoftly(softly -> {
@@ -271,7 +259,7 @@ public class AggregateChangeIdGenerationImmutableUnitTests {
DbAction.Insert<?> insert2 = createDeepInsert("tagList", tag2, 0, parentInsert2);
DbAction.Insert<?> insert3 = createDeepInsert("tagList", tag3, 1, parentInsert2);
AggregateChange<DummyEntity> aggregateChange = AggregateChange.forSave(entity);
MutableAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
aggregateChange.addAction(parentInsert1);
aggregateChange.addAction(parentInsert2);
@@ -279,9 +267,7 @@ public class AggregateChangeIdGenerationImmutableUnitTests {
aggregateChange.addAction(insert2);
aggregateChange.addAction(insert3);
executor.execute(aggregateChange);
entity = aggregateChange.getEntity();
entity = executor.execute(aggregateChange);
assertSoftly(softly -> {
@@ -308,7 +294,7 @@ public class AggregateChangeIdGenerationImmutableUnitTests {
DbAction.Insert<?> insert2 = createDeepInsert("tagMap", tag2, "222", parentInsert2);
DbAction.Insert<?> insert3 = createDeepInsert("tagMap", tag3, "333", parentInsert2);
AggregateChange<DummyEntity> aggregateChange = AggregateChange.forSave(entity);
MutableAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
aggregateChange.addAction(parentInsert1);
aggregateChange.addAction(parentInsert2);
@@ -316,9 +302,7 @@ public class AggregateChangeIdGenerationImmutableUnitTests {
aggregateChange.addAction(insert2);
aggregateChange.addAction(insert3);
executor.execute(aggregateChange);
entity = aggregateChange.getEntity();
entity = executor.execute(aggregateChange);
assertSoftly(softly -> {
@@ -349,16 +333,14 @@ public class AggregateChangeIdGenerationImmutableUnitTests {
DbAction.Insert<?> insert1 = createDeepInsert("single", tag1, null, parentInsert1);
DbAction.Insert<?> insert2 = createDeepInsert("single", tag2, null, parentInsert2);
AggregateChange<DummyEntity> aggregateChange = AggregateChange.forSave(entity);
MutableAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
aggregateChange.addAction(parentInsert1);
aggregateChange.addAction(parentInsert2);
aggregateChange.addAction(insert1);
aggregateChange.addAction(insert2);
executor.execute(aggregateChange);
entity = aggregateChange.getEntity();
entity = executor.execute(aggregateChange);
assertSoftly(softly -> {
@@ -377,13 +359,11 @@ public class AggregateChangeIdGenerationImmutableUnitTests {
DbAction.Insert<?> parentInsert = createInsert("embedded.single", tag1, null);
AggregateChange<DummyEntity> aggregateChange = AggregateChange.forSave(entity);
MutableAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
aggregateChange.addAction(parentInsert);
executor.execute(aggregateChange);
entity = aggregateChange.getEntity();
entity = executor.execute(aggregateChange);
assertThat(entity.rootId).isEqualTo(1);
assertThat(entity.embedded.single.id).isEqualTo(2);
@@ -412,18 +392,20 @@ public class AggregateChangeIdGenerationImmutableUnitTests {
DbAction.Insert<?> createInsert(String propertyName, Object value, @Nullable Object key) {
DbAction.Insert<Object> insert = new DbAction.Insert<>(value,
context.getPersistentPropertyPath(propertyName, DummyEntity.class), rootInsert);
insert.getQualifiers().put(toPath(propertyName), key);
context.getPersistentPropertyPath(propertyName, DummyEntity.class), rootInsert,
singletonMap(toPath(propertyName), key));
return insert;
}
DbAction.Insert<?> createDeepInsert(String propertyName, Object value, Object key,
@Nullable DbAction.Insert<?> parentInsert) {
PersistentPropertyPath<RelationalPersistentProperty> propertyPath = toPath(
parentInsert.getPropertyPath().toDotPath() + "." + propertyName);
DbAction.Insert<Object> insert = new DbAction.Insert<>(value, propertyPath, parentInsert);
insert.getQualifiers().put(propertyPath, key);
DbAction.Insert<Object> insert = new DbAction.Insert<>(value, propertyPath, parentInsert,
singletonMap(propertyPath, key));
return insert;
}
@@ -514,57 +496,4 @@ public class AggregateChangeIdGenerationImmutableUnitTests {
this.name = name;
}
}
private static class IdSettingInterpreter implements Interpreter {
int id = 0;
@Override
public <T> void interpret(DbAction.Insert<T> insert) {
if (insert.getEntityType().getSimpleName().endsWith("NoId")) {
return;
}
insert.setGeneratedId(++id);
}
@Override
public <T> void interpret(DbAction.InsertRoot<T> insert) {
insert.setGeneratedId(++id);
}
@Override
public <T> void interpret(DbAction.Update<T> update) {
throw new UnsupportedOperationException();
}
@Override
public <T> void interpret(DbAction.UpdateRoot<T> update) {
throw new UnsupportedOperationException();
}
@Override
public <T> void interpret(DbAction.Merge<T> update) {
throw new UnsupportedOperationException();
}
@Override
public <T> void interpret(DbAction.Delete<T> delete) {
throw new UnsupportedOperationException();
}
@Override
public <T> void interpret(DbAction.DeleteRoot<T> deleteRoot) {
throw new UnsupportedOperationException();
}
@Override
public <T> void interpret(DbAction.DeleteAll<T> delete) {
throw new UnsupportedOperationException();
}
@Override
public <T> void interpret(DbAction.DeleteAllRoot<T> DeleteAllRoot) {
throw new UnsupportedOperationException();
}
}
}

View File

@@ -15,7 +15,9 @@
*/
package org.springframework.data.jdbc.core;
import static java.util.Collections.*;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.util.ArrayList;
import java.util.HashMap;
@@ -26,20 +28,22 @@ import java.util.Set;
import org.assertj.core.api.SoftAssertions;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.core.convert.BasicJdbcConverter;
import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.mapping.PersistentPropertyPaths;
import org.springframework.data.relational.core.conversion.AggregateChange;
import org.springframework.data.relational.core.conversion.BasicRelationalConverter;
import org.springframework.data.relational.core.conversion.DbAction;
import org.springframework.data.relational.core.conversion.Interpreter;
import org.springframework.data.relational.core.conversion.RelationalConverter;
import org.springframework.data.relational.core.conversion.MutableAggregateChange;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.lang.Nullable;
/**
* Unit tests for the {@link AggregateChange}.
* Unit tests for the {@link MutableAggregateChange}.
*
* @author Jens Schauder
* @author Myeonghyeon-Lee
@@ -54,15 +58,17 @@ public class AggregateChangeIdGenerationUnitTests {
Tag tag3 = new Tag();
RelationalMappingContext context = new RelationalMappingContext();
RelationalConverter converter = new BasicRelationalConverter(context);
JdbcConverter converter = new BasicJdbcConverter(context, (identifier, path) -> {
throw new UnsupportedOperationException();
});
DbAction.WithEntity<?> rootInsert = new DbAction.InsertRoot<>(entity);
AggregateChangeExecutor executor = new AggregateChangeExecutor(new IdSettingInterpreter(), converter);
DataAccessStrategy accessStrategy = mock(DataAccessStrategy.class, new IncrementingIds());
AggregateChangeExecutor executor = new AggregateChangeExecutor(converter, accessStrategy);
@Test // DATAJDBC-291
public void singleRoot() {
AggregateChange<DummyEntity> aggregateChange = AggregateChange.forSave(entity);
MutableAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
executor.execute(aggregateChange);
@@ -75,7 +81,7 @@ public class AggregateChangeIdGenerationUnitTests {
entity.single = content;
AggregateChange<DummyEntity> aggregateChange = AggregateChange.forSave(entity);
MutableAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
aggregateChange.addAction(createInsert("single", content, null));
@@ -94,7 +100,7 @@ public class AggregateChangeIdGenerationUnitTests {
entity.contentList.add(content);
entity.contentList.add(content2);
AggregateChange<DummyEntity> aggregateChange = AggregateChange.forSave(entity);
MutableAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
aggregateChange.addAction(createInsert("contentList", content, 0));
aggregateChange.addAction(createInsert("contentList", content2, 1));
@@ -114,7 +120,7 @@ public class AggregateChangeIdGenerationUnitTests {
entity.contentMap.put("a", content);
entity.contentMap.put("b", content2);
AggregateChange<DummyEntity> aggregateChange = AggregateChange.forSave(entity);
MutableAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
aggregateChange.addAction(createInsert("contentMap", content, "a"));
aggregateChange.addAction(createInsert("contentMap", content2, "b"));
@@ -134,7 +140,7 @@ public class AggregateChangeIdGenerationUnitTests {
DbAction.Insert<?> parentInsert = createInsert("single", content, null);
DbAction.Insert<?> insert = createDeepInsert("single", tag1, null, parentInsert);
AggregateChange<DummyEntity> aggregateChange = AggregateChange.forSave(entity);
MutableAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
aggregateChange.addAction(parentInsert);
aggregateChange.addAction(insert);
@@ -157,7 +163,7 @@ public class AggregateChangeIdGenerationUnitTests {
DbAction.Insert<?> insert1 = createDeepInsert("tagList", tag1, 0, parentInsert);
DbAction.Insert<?> insert2 = createDeepInsert("tagList", tag2, 1, parentInsert);
AggregateChange<DummyEntity> aggregateChange = AggregateChange.forSave(entity);
MutableAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
aggregateChange.addAction(parentInsert);
aggregateChange.addAction(insert1);
@@ -184,7 +190,7 @@ public class AggregateChangeIdGenerationUnitTests {
DbAction.Insert<?> insert1 = createDeepInsert("tagSet", tag1, null, parentInsert);
DbAction.Insert<?> insert2 = createDeepInsert("tagSet", tag2, null, parentInsert);
AggregateChange<DummyEntity> aggregateChange = AggregateChange.forSave(entity);
MutableAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
aggregateChange.addAction(parentInsert);
aggregateChange.addAction(insert1);
@@ -218,7 +224,7 @@ public class AggregateChangeIdGenerationUnitTests {
DbAction.Insert<?> insert1 = createDeepInsert("single", tag1, null, parentInsert1);
DbAction.Insert<?> insert2 = createDeepInsert("single", tag2, null, parentInsert2);
AggregateChange<DummyEntity> aggregateChange = AggregateChange.forSave(entity);
MutableAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
aggregateChange.addAction(parentInsert1);
aggregateChange.addAction(parentInsert2);
@@ -251,7 +257,7 @@ public class AggregateChangeIdGenerationUnitTests {
DbAction.Insert<?> insert2 = createDeepInsert("tagList", tag2, 0, parentInsert2);
DbAction.Insert<?> insert3 = createDeepInsert("tagList", tag3, 1, parentInsert2);
AggregateChange<DummyEntity> aggregateChange = AggregateChange.forSave(entity);
MutableAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
aggregateChange.addAction(parentInsert1);
aggregateChange.addAction(parentInsert2);
@@ -289,7 +295,7 @@ public class AggregateChangeIdGenerationUnitTests {
DbAction.Insert<?> insert2 = createDeepInsert("tagMap", tag2, "222", parentInsert2);
DbAction.Insert<?> insert3 = createDeepInsert("tagMap", tag3, "333", parentInsert2);
AggregateChange<DummyEntity> aggregateChange = AggregateChange.forSave(entity);
MutableAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.addAction(rootInsert);
aggregateChange.addAction(parentInsert1);
aggregateChange.addAction(parentInsert2);
@@ -318,11 +324,8 @@ public class AggregateChangeIdGenerationUnitTests {
DbAction.Insert<?> createInsert(String propertyName, Object value, @Nullable Object key) {
DbAction.Insert<Object> insert = new DbAction.Insert<>(value,
context.getPersistentPropertyPath(propertyName, DummyEntity.class), rootInsert);
insert.getQualifiers().put(toPath(propertyName), key);
return insert;
return new DbAction.Insert<>(value, context.getPersistentPropertyPath(propertyName, DummyEntity.class), rootInsert,
key == null ? emptyMap() : singletonMap(toPath(propertyName), key));
}
DbAction.Insert<?> createDeepInsert(String propertyName, Object value, @Nullable Object key,
@@ -330,9 +333,9 @@ public class AggregateChangeIdGenerationUnitTests {
PersistentPropertyPath<RelationalPersistentProperty> propertyPath = toPath(
parentInsert.getPropertyPath().toDotPath() + "." + propertyName);
DbAction.Insert<Object> insert = new DbAction.Insert<>(value, propertyPath, parentInsert);
insert.getQualifiers().put(propertyPath, key);
return insert;
return new DbAction.Insert<>(value, propertyPath, parentInsert,
key == null ? emptyMap() : singletonMap(propertyPath, key));
}
PersistentPropertyPath<RelationalPersistentProperty> toPath(String path) {
@@ -374,53 +377,28 @@ public class AggregateChangeIdGenerationUnitTests {
@Id Integer id;
}
private static class IdSettingInterpreter implements Interpreter {
int id = 0;
private static class IncrementingIds implements Answer {
long id = 1;
@Override
public <T> void interpret(DbAction.Insert<T> insert) {
insert.setGeneratedId(++id);
public Object answer(InvocationOnMock invocation) throws Throwable {
if (!invocation.getMethod().getReturnType().equals(Object.class)) {
throw new UnsupportedOperationException("This mock does not support this invocation: " + invocation);
}
return id++;
}
@Override
public <T> void interpret(DbAction.InsertRoot<T> insert) {
insert.setGeneratedId(++id);
private DbAction<?> findAction(Object[] arguments) {
}
for (Object argument : arguments) {
@Override
public <T> void interpret(DbAction.Update<T> update) {
throw new UnsupportedOperationException();
}
@Override
public <T> void interpret(DbAction.UpdateRoot<T> update) {
throw new UnsupportedOperationException();
}
@Override
public <T> void interpret(DbAction.Merge<T> update) {
throw new UnsupportedOperationException();
}
@Override
public <T> void interpret(DbAction.Delete<T> delete) {
throw new UnsupportedOperationException();
}
@Override
public <T> void interpret(DbAction.DeleteRoot<T> deleteRoot) {
throw new UnsupportedOperationException();
}
@Override
public <T> void interpret(DbAction.DeleteAll<T> delete) {
throw new UnsupportedOperationException();
}
@Override
public <T> void interpret(DbAction.DeleteAllRoot<T> DeleteAllRoot) {
throw new UnsupportedOperationException();
if (argument instanceof DbAction) {
return (DbAction<?>) argument;
}
}
return null;
}
}
}

View File

@@ -1,200 +0,0 @@
/*
* Copyright 2017-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jdbc.core;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import static org.springframework.data.jdbc.core.PropertyPathTestingUtils.*;
import static org.springframework.data.relational.core.sql.SqlIdentifier.*;
import java.util.List;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.dao.IncorrectUpdateSemanticsDataAccessException;
import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.core.convert.BasicJdbcConverter;
import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.relational.core.conversion.DbAction.Insert;
import org.springframework.data.relational.core.conversion.DbAction.InsertRoot;
import org.springframework.data.relational.core.conversion.DbAction.UpdateRoot;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.data.relational.domain.Identifier;
import org.springframework.data.relational.core.sql.SqlIdentifier;
/**
* Unit tests for {@link DefaultJdbcInterpreter}
*
* @author Jens Schauder
* @author Mark Paluch
* @author Myeonghyeon Lee
* @author Tyler Van Gorder
*/
public class DefaultJdbcInterpreterUnitTests {
public static final SqlIdentifier BACK_REFERENCE = quoted("CONTAINER");
static final long CONTAINER_ID = 23L;
RelationalMappingContext context = new JdbcMappingContext();
JdbcConverter converter = new BasicJdbcConverter(context, (Identifier, path) -> null);
DataAccessStrategy dataAccessStrategy = mock(DataAccessStrategy.class);
DefaultJdbcInterpreter interpreter = new DefaultJdbcInterpreter(converter, context, dataAccessStrategy);
Container container = new Container();
Element element = new Element();
InsertRoot<Container> containerInsert = new InsertRoot<>(container);
Insert<?> elementInsert = new Insert<>(element, toPath("element", Container.class, context), containerInsert);
Insert<?> element1Insert = new Insert<>(element, toPath("element.element1", Container.class, context), elementInsert);
@Test // DATAJDBC-145
public void insertDoesHonourNamingStrategyForBackReference() {
container.id = CONTAINER_ID;
containerInsert.setGeneratedId(CONTAINER_ID);
interpreter.interpret(elementInsert);
ArgumentCaptor<Identifier> argumentCaptor = ArgumentCaptor.forClass(Identifier.class);
verify(dataAccessStrategy).insert(eq(element), eq(Element.class), argumentCaptor.capture());
assertThat(argumentCaptor.getValue().getParts()) //
.extracting("name", "value", "targetType") //
.containsExactly(tuple(BACK_REFERENCE, CONTAINER_ID, Long.class));
}
@Test // DATAJDBC-251
public void idOfParentGetsPassedOnAsAdditionalParameterIfNoIdGotGenerated() {
container.id = CONTAINER_ID;
interpreter.interpret(elementInsert);
ArgumentCaptor<Identifier> argumentCaptor = ArgumentCaptor.forClass(Identifier.class);
verify(dataAccessStrategy).insert(eq(element), eq(Element.class), argumentCaptor.capture());
assertThat(argumentCaptor.getValue().getParts()) //
.extracting("name", "value", "targetType") //
.containsExactly(tuple(BACK_REFERENCE, CONTAINER_ID, Long.class));
}
@Test // DATAJDBC-251
public void generatedIdOfParentGetsPassedOnAsAdditionalParameter() {
containerInsert.setGeneratedId(CONTAINER_ID);
interpreter.interpret(elementInsert);
ArgumentCaptor<Identifier> argumentCaptor = ArgumentCaptor.forClass(Identifier.class);
verify(dataAccessStrategy).insert(eq(element), eq(Element.class), argumentCaptor.capture());
assertThat(argumentCaptor.getValue().getParts()) //
.extracting("name", "value", "targetType") //
.containsExactly(tuple(BACK_REFERENCE, CONTAINER_ID, Long.class));
}
@Test // DATAJDBC-359
public void generatedIdOfParentsParentGetsPassedOnAsAdditionalParameter() {
containerInsert.setGeneratedId(CONTAINER_ID);
interpreter.interpret(element1Insert);
ArgumentCaptor<Identifier> argumentCaptor = ArgumentCaptor.forClass(Identifier.class);
verify(dataAccessStrategy).insert(eq(element), eq(Element.class), argumentCaptor.capture());
assertThat(argumentCaptor.getValue().getParts()) //
.extracting("name", "value", "targetType") //
.containsExactly(tuple(BACK_REFERENCE, CONTAINER_ID, Long.class));
}
@Test // DATAJDBC-223
public void generateCascadingIds() {
RootWithList rootWithList = new RootWithList();
WithList listContainer = new WithList();
InsertRoot<RootWithList> listListContainerInsert = new InsertRoot<>(rootWithList);
PersistentPropertyPath<RelationalPersistentProperty> listContainersPath = toPath("listContainers",
RootWithList.class, context);
Insert<?> listContainerInsert = new Insert<>(listContainer, listContainersPath, listListContainerInsert);
listContainerInsert.getQualifiers().put(listContainersPath, 3);
PersistentPropertyPath<RelationalPersistentProperty> listContainersElementsPath = toPath("listContainers.elements",
RootWithList.class, context);
Insert<?> elementInsertInList = new Insert<>(element, listContainersElementsPath, listContainerInsert);
elementInsertInList.getQualifiers().put(listContainersElementsPath, 6);
elementInsertInList.getQualifiers().put(listContainersPath, 3);
listListContainerInsert.setGeneratedId(CONTAINER_ID);
interpreter.interpret(elementInsertInList);
ArgumentCaptor<Identifier> argumentCaptor = ArgumentCaptor.forClass(Identifier.class);
verify(dataAccessStrategy).insert(eq(element), eq(Element.class), argumentCaptor.capture());
assertThat(argumentCaptor.getValue().getParts()) //
.extracting("name", "value", "targetType") //
.containsOnly(tuple(quoted("ROOT_WITH_LIST"), CONTAINER_ID, Long.class), // the top
// level id
tuple(quoted("ROOT_WITH_LIST_KEY"), 3, Integer.class), // midlevel key
tuple(quoted("WITH_LIST_KEY"), 6, Integer.class) // lowlevel key
);
}
@Test // DATAJDBC-438
public void throwExceptionUpdateFailedRootDoesNotExist() {
container.id = CONTAINER_ID;
UpdateRoot<Container> containerUpdate = new UpdateRoot<>(container);
when(dataAccessStrategy.update(container, Container.class)).thenReturn(false);
assertThatExceptionOfType(IncorrectUpdateSemanticsDataAccessException.class).isThrownBy(() -> {
interpreter.interpret(containerUpdate);
}) //
.withMessageContaining(Long.toString(CONTAINER_ID)) //
.withMessageContaining(container.toString());
}
@SuppressWarnings("unused")
static class Container {
@Id Long id;
Element element;
}
@SuppressWarnings("unused")
static class Element {
Element1 element1;
}
static class Element1 {}
static class RootWithList {
@Id Long id;
List<WithList> listContainers;
}
private static class WithList {
List<Element> elements;
}
}

View File

@@ -0,0 +1,187 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jdbc.core;
import static java.util.Collections.*;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import lombok.AllArgsConstructor;
import lombok.Value;
import lombok.With;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Version;
import org.springframework.data.jdbc.core.convert.BasicJdbcConverter;
import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.mapping.PersistentPropertyPaths;
import org.springframework.data.relational.core.conversion.DbAction;
import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.data.relational.domain.Identifier;
import org.springframework.lang.Nullable;
public class JdbcAggregateChangeExecutorContextImmutableUnitTests {
RelationalMappingContext context = new RelationalMappingContext();
JdbcConverter converter = new BasicJdbcConverter(context, (identifier, path) -> {
throw new UnsupportedOperationException();
});
DataAccessStrategy accessStrategy = mock(DataAccessStrategy.class);
JdbcAggregateChangeExecutionContext executionContext = new JdbcAggregateChangeExecutionContext(converter,
accessStrategy);
DummyEntity root = new DummyEntity();
@Test // DATAJDBC-453
public void rootOfEmptySetOfActionsisNull() {
Object root = executionContext.populateIdsIfNecessary();
assertThat(root).isNull();
}
@Test // DATAJDBC-453
public void afterInsertRootIdAndVersionMaybeUpdated() {
// note that the root entity isn't the original one, but a new instance with the version set.
when(accessStrategy.insert(any(DummyEntity.class), eq(DummyEntity.class), eq(Identifier.empty()))).thenReturn(23L);
executionContext.executeInsertRoot(new DbAction.InsertRoot<>(root));
DummyEntity newRoot = executionContext.populateIdsIfNecessary();
assertThat(newRoot).isNotNull();
assertThat(newRoot.id).isEqualTo(23L);
newRoot = executionContext.populateRootVersionIfNecessary(newRoot);
assertThat(newRoot.version).isEqualTo(1);
}
@Test // DATAJDBC-453
public void idGenerationOfChild() {
Content content = new Content();
when(accessStrategy.insert(any(DummyEntity.class), eq(DummyEntity.class), eq(Identifier.empty()))).thenReturn(23L);
when(accessStrategy.insert(any(Content.class), eq(Content.class), eq(createBackRef()))).thenReturn(24L);
DbAction.InsertRoot<DummyEntity> rootInsert = new DbAction.InsertRoot<>(root);
executionContext.executeInsertRoot(rootInsert);
executionContext.executeInsert(createInsert(rootInsert, "content", content, null));
DummyEntity newRoot = executionContext.populateIdsIfNecessary();
assertThat(newRoot).isNotNull();
assertThat(newRoot.id).isEqualTo(23L);
assertThat(newRoot.content.id).isEqualTo(24L);
}
@Test // DATAJDBC-453
public void idGenerationOfChildInList() {
Content content = new Content();
when(accessStrategy.insert(any(DummyEntity.class), eq(DummyEntity.class), eq(Identifier.empty()))).thenReturn(23L);
when(accessStrategy.insert(eq(content), eq(Content.class), any(Identifier.class))).thenReturn(24L);
DbAction.InsertRoot<DummyEntity> rootInsert = new DbAction.InsertRoot<>(root);
executionContext.executeInsertRoot(rootInsert);
executionContext.executeInsert(createInsert(rootInsert, "list", content, 1));
DummyEntity newRoot = executionContext.populateIdsIfNecessary();
assertThat(newRoot).isNotNull();
assertThat(newRoot.id).isEqualTo(23L);
assertThat(newRoot.list.get(0).id).isEqualTo(24L);
}
DbAction.Insert<?> createInsert(DbAction.WithEntity<?> parent, String propertyName, Object value,
@Nullable Object key) {
DbAction.Insert<Object> insert = new DbAction.Insert<>(value, getPersistentPropertyPath(propertyName), parent,
key == null ? emptyMap() : singletonMap(toPath(propertyName), key));
return insert;
}
PersistentPropertyPathExtension toPathExt(String path) {
return new PersistentPropertyPathExtension(context, getPersistentPropertyPath(path));
}
@NotNull
PersistentPropertyPath<RelationalPersistentProperty> getPersistentPropertyPath(String propertyName) {
return context.getPersistentPropertyPath(propertyName, DummyEntity.class);
}
@NotNull
Identifier createBackRef() {
return JdbcIdentifierBuilder.forBackReferences(converter, toPathExt("content"), 23L).build();
}
PersistentPropertyPath<RelationalPersistentProperty> toPath(String path) {
PersistentPropertyPaths<?, RelationalPersistentProperty> persistentPropertyPaths = context
.findPersistentPropertyPaths(DummyEntity.class, p -> true);
return persistentPropertyPaths.filter(p -> p.toDotPath().equals(path)).stream().findFirst()
.orElseThrow(() -> new IllegalArgumentException("No matching path found"));
}
@Value
@AllArgsConstructor
@With
private static class DummyEntity {
@Id Long id;
@Version long version;
Content content;
List<Content> list;
DummyEntity() {
id = null;
version = 0;
content = null;
list = null;
}
}
@Value
@AllArgsConstructor
@With
private static class Content {
@Id Long id;
Content() {
id = null;
}
}
}

View File

@@ -0,0 +1,166 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jdbc.core;
import static java.util.Collections.*;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.util.ArrayList;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Version;
import org.springframework.data.jdbc.core.convert.BasicJdbcConverter;
import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.mapping.PersistentPropertyPaths;
import org.springframework.data.relational.core.conversion.DbAction;
import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.data.relational.domain.Identifier;
import org.springframework.lang.Nullable;
public class JdbcAggregateChangeExecutorContextUnitTests {
RelationalMappingContext context = new RelationalMappingContext();
JdbcConverter converter = new BasicJdbcConverter(context, (identifier, path) -> {
throw new UnsupportedOperationException();
});
DataAccessStrategy accessStrategy = mock(DataAccessStrategy.class);
AggregateChangeExecutor executor = new AggregateChangeExecutor(converter, accessStrategy);
JdbcAggregateChangeExecutionContext executionContext = new JdbcAggregateChangeExecutionContext(converter,
accessStrategy);
DummyEntity root = new DummyEntity();
@Test // DATAJDBC-453
public void rootOfEmptySetOfActionsisNull() {
Object root = executionContext.populateIdsIfNecessary();
assertThat(root).isNull();
}
@Test // DATAJDBC-453
public void afterInsertRootIdAndVersionMaybeUpdated() {
when(accessStrategy.insert(root, DummyEntity.class, Identifier.empty())).thenReturn(23L);
executionContext.executeInsertRoot(new DbAction.InsertRoot<>(root));
DummyEntity newRoot = executionContext.populateIdsIfNecessary();
assertThat(newRoot).isNull();
assertThat(root.id).isEqualTo(23L);
executionContext.populateRootVersionIfNecessary(root);
assertThat(root.version).isEqualTo(1);
}
@Test // DATAJDBC-453
public void idGenerationOfChild() {
Content content = new Content();
when(accessStrategy.insert(root, DummyEntity.class, Identifier.empty())).thenReturn(23L);
when(accessStrategy.insert(content, Content.class, createBackRef())).thenReturn(24L);
DbAction.InsertRoot<DummyEntity> rootInsert = new DbAction.InsertRoot<>(root);
executionContext.executeInsertRoot(rootInsert);
executionContext.executeInsert(createInsert(rootInsert, "content", content, null));
DummyEntity newRoot = executionContext.populateIdsIfNecessary();
assertThat(newRoot).isNull();
assertThat(root.id).isEqualTo(23L);
assertThat(content.id).isEqualTo(24L);
}
@Test // DATAJDBC-453
public void idGenerationOfChildInList() {
Content content = new Content();
when(accessStrategy.insert(root, DummyEntity.class, Identifier.empty())).thenReturn(23L);
when(accessStrategy.insert(eq(content), eq(Content.class), any(Identifier.class))).thenReturn(24L);
DbAction.InsertRoot<DummyEntity> rootInsert = new DbAction.InsertRoot<>(root);
executionContext.executeInsertRoot(rootInsert);
executionContext.executeInsert(createInsert(rootInsert, "list", content, 1));
DummyEntity newRoot = executionContext.populateIdsIfNecessary();
assertThat(newRoot).isNull();
assertThat(root.id).isEqualTo(23L);
assertThat(content.id).isEqualTo(24L);
}
DbAction.Insert<?> createInsert(DbAction.WithEntity<?> parent, String propertyName, Object value,
@Nullable Object key) {
DbAction.Insert<Object> insert = new DbAction.Insert<>(value, getPersistentPropertyPath(propertyName), parent,
key == null ? emptyMap() : singletonMap(toPath(propertyName), key));
return insert;
}
PersistentPropertyPathExtension toPathExt(String path) {
return new PersistentPropertyPathExtension(context, getPersistentPropertyPath(path));
}
@NotNull
PersistentPropertyPath<RelationalPersistentProperty> getPersistentPropertyPath(String propertyName) {
return context.getPersistentPropertyPath(propertyName, DummyEntity.class);
}
@NotNull
Identifier createBackRef() {
return JdbcIdentifierBuilder.forBackReferences(converter, toPathExt("content"), 23L).build();
}
PersistentPropertyPath<RelationalPersistentProperty> toPath(String path) {
PersistentPropertyPaths<?, RelationalPersistentProperty> persistentPropertyPaths = context
.findPersistentPropertyPaths(DummyEntity.class, p -> true);
return persistentPropertyPaths.filter(p -> p.toDotPath().equals(path)).stream().findFirst()
.orElseThrow(() -> new IllegalArgumentException("No matching path found"));
}
private static class DummyEntity {
@Id Long id;
@Version long version;
Content content;
List<Content> list = new ArrayList<>();
}
private static class Content {
@Id Long id;
}
}

View File

@@ -37,7 +37,7 @@ import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.jdbc.core.convert.RelationResolver;
import org.springframework.data.mapping.callback.EntityCallbacks;
import org.springframework.data.relational.core.conversion.AggregateChange;
import org.springframework.data.relational.core.conversion.MutableAggregateChange;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.NamingStrategy;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
@@ -105,7 +105,7 @@ public class JdbcAggregateTemplateUnitTests {
SampleEntity last = template.save(first);
verify(callbacks).callback(BeforeConvertCallback.class, first);
verify(callbacks).callback(eq(BeforeSaveCallback.class), eq(second), any(AggregateChange.class));
verify(callbacks).callback(eq(BeforeSaveCallback.class), eq(second), any(MutableAggregateChange.class));
verify(callbacks).callback(AfterSaveCallback.class, third);
assertThat(last).isEqualTo(third);
}
@@ -120,7 +120,7 @@ public class JdbcAggregateTemplateUnitTests {
template.delete(first, SampleEntity.class);
verify(callbacks).callback(eq(BeforeDeleteCallback.class), eq(first), any(AggregateChange.class));
verify(callbacks).callback(eq(BeforeDeleteCallback.class), eq(first), any(MutableAggregateChange.class));
verify(callbacks).callback(AfterDeleteCallback.class, second);
}

View File

@@ -1,181 +1,44 @@
/*
* Copyright 2017-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.relational.core.conversion;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* 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
*/
public class AggregateChange<T> {
private final Kind kind;
/** Type of the aggregate root to be changed */
private final Class<T> entityType;
private final List<DbAction<?>> actions = new ArrayList<>();
/** Aggregate root, to which the change applies, if available */
@Nullable private T entity;
public AggregateChange(Kind kind, Class<T> entityType, @Nullable T entity) {
this.kind = kind;
this.entityType = entityType;
this.entity = entity;
}
/**
* Factory method to create an {@link AggregateChange} for saving entities.
*
* @param entity aggregate root to save.
* @param <T> entity type.
* @return the {@link AggregateChange} for saving the root {@code entity}.
* @since 1.2
*/
@SuppressWarnings("unchecked")
public static <T> AggregateChange<T> forSave(T entity) {
Assert.notNull(entity, "Entity must not be null");
return new AggregateChange<>(Kind.SAVE, (Class<T>) ClassUtils.getUserClass(entity), entity);
}
/**
* Factory method to create an {@link AggregateChange} for deleting entities.
*
* @param entity aggregate root to delete.
* @param <T> entity type.
* @return the {@link AggregateChange} for deleting the root {@code entity}.
* @since 1.2
*/
@SuppressWarnings("unchecked")
public static <T> AggregateChange<T> forDelete(T entity) {
Assert.notNull(entity, "Entity must not be null");
return forDelete((Class<T>) ClassUtils.getUserClass(entity), entity);
}
/**
* Factory method to create an {@link AggregateChange} for deleting entities.
*
* @param entityClass aggregate root type.
* @param entity aggregate root to delete.
* @param <T> entity type.
* @return the {@link AggregateChange} for deleting the root {@code entity}.
* @since 1.2
*/
public static <T> AggregateChange<T> forDelete(Class<T> entityClass, @Nullable T entity) {
Assert.notNull(entityClass, "Entity class must not be null");
return new AggregateChange<>(Kind.DELETE, entityClass, entity);
}
/**
* Adds an action to this {@code AggregateChange}.
*
* @param action must not be {@literal null}.
*/
public void addAction(DbAction<?> action) {
Assert.notNull(action, "Action must not be null.");
actions.add(action);
}
public interface AggregateChange<T> {
/**
* Applies the given consumer to each {@link DbAction} in this {@code AggregateChange}.
*
*
* @param consumer must not be {@literal null}.
*/
public void forEachAction(Consumer<? super DbAction<?>> consumer) {
Assert.notNull(consumer, "Consumer must not be null.");
actions.forEach(consumer);
}
/**
* All the actions contained in this {@code AggregateChange}.
* <p>
* The behavior when modifying this list might result in undesired behavior.
* <p>
* Use {@link #addAction(DbAction)} to add actions.
*
* @return Guaranteed to be not {@literal null}.
*/
public List<DbAction<?>> getActions() {
return this.actions;
}
void forEachAction(Consumer<? super DbAction<?>> consumer);
/**
* Returns the {@link Kind} of {@code AggregateChange} this is.
*
*
* @return guaranteed to be not {@literal null}.
*/
public Kind getKind() {
return this.kind;
}
Kind getKind();
/**
* The type of the root of this {@code AggregateChange}.
*
*
* @return Guaranteed to be not {@literal null}.
*/
public Class<T> getEntityType() {
return this.entityType;
}
/**
* Set the root object of the {@code AggregateChange}.
*
* @param aggregateRoot may be {@literal null} if the change refers to a list of aggregates or references it by id.
*/
public void setEntity(@Nullable T aggregateRoot) {
if (aggregateRoot != null) {
Assert.isInstanceOf(entityType, aggregateRoot,
String.format("AggregateRoot must be of type %s", entityType.getName()));
}
entity = aggregateRoot;
}
Class<T> getEntityType();
/**
* The entity to which this {@link AggregateChange} relates.
*
*
* @return may be {@literal null}.
*/
@Nullable
public T getEntity() {
return this.entity;
}
T getEntity();
/**
* The kind of action to be performed on an aggregate.
*/
public enum Kind {
enum Kind {
/**
* A {@code SAVE} of an aggregate typically involves an {@code insert} or {@code update} on the aggregate root plus
* {@code insert}s, {@code update}s, and {@code delete}s on the other elements of an aggregate.
@@ -187,5 +50,4 @@ public class AggregateChange<T> {
*/
DELETE
}
}

View File

@@ -0,0 +1,3 @@
package org.springframework.data.relational.core.conversion;
public interface AggregateChangeExecutionContext {}

View File

@@ -15,12 +15,7 @@
*/
package org.springframework.data.relational.core.conversion;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -42,55 +37,53 @@ public interface DbAction<T> {
Class<T> getEntityType();
/**
* Executing this DbAction with the given {@link Interpreter}.
* <p>
* The default implementation just performs exception handling and delegates to {@link #doExecuteWith(Interpreter)}.
*
* @param interpreter the {@link Interpreter} responsible for actually executing the {@link DbAction}.Must not be
* {@code null}.
*/
default void executeWith(Interpreter interpreter) {
try {
doExecuteWith(interpreter);
} catch (Exception e) {
throw new DbActionExecutionException(this, e);
}
}
/**
* Executing this DbAction with the given {@link Interpreter} without any exception handling.
*
* @param interpreter the {@link Interpreter} responsible for actually executing the {@link DbAction}.
*/
void doExecuteWith(Interpreter interpreter);
/**
* Represents an insert statement for a single entity that is not the root of an aggregate.
*
* @param <T> type of the entity for which this represents a database interaction.
*/
@Data
class Insert<T> implements WithGeneratedId<T>, WithDependingOn<T> {
@NonNull final T entity;
@NonNull final PersistentPropertyPath<RelationalPersistentProperty> propertyPath;
@NonNull final WithEntity<?> dependingOn;
private final T entity;
private final PersistentPropertyPath<RelationalPersistentProperty> propertyPath;
private final WithEntity<?> dependingOn;
Map<PersistentPropertyPath<RelationalPersistentProperty>, Object> qualifiers = new HashMap<>();
final Map<PersistentPropertyPath<RelationalPersistentProperty>, Object> qualifiers;
private Object generatedId;
public Insert(T entity, PersistentPropertyPath<RelationalPersistentProperty> propertyPath,
WithEntity<?> dependingOn, Map<PersistentPropertyPath<RelationalPersistentProperty>, Object> qualifiers) {
@Override
public void doExecuteWith(Interpreter interpreter) {
interpreter.interpret(this);
this.entity = entity;
this.propertyPath = propertyPath;
this.dependingOn = dependingOn;
this.qualifiers = Collections.unmodifiableMap(new HashMap<>(qualifiers));
}
@Override
public Class<T> getEntityType() {
return WithDependingOn.super.getEntityType();
}
public T getEntity() {
return this.entity;
}
public PersistentPropertyPath<RelationalPersistentProperty> getPropertyPath() {
return this.propertyPath;
}
public DbAction.WithEntity<?> getDependingOn() {
return this.dependingOn;
}
public Map<PersistentPropertyPath<RelationalPersistentProperty>, Object> getQualifiers() {
return this.qualifiers;
}
public String toString() {
return "DbAction.Insert(entity=" + this.getEntity() + ", propertyPath=" + this.getPropertyPath()
+ ", dependingOn=" + this.getDependingOn() + ", qualifiers=" + this.getQualifiers() + ")";
}
}
/**
@@ -99,17 +92,20 @@ public interface DbAction<T> {
*
* @param <T> type of the entity for which this represents a database interaction.
*/
@Data
@RequiredArgsConstructor
class InsertRoot<T> implements WithVersion, WithGeneratedId<T> {
class InsertRoot<T> implements WithGeneratedId<T> {
@NonNull final T entity;
private Number nextVersion;
private Object generatedId;
private final T entity;
@Override
public void doExecuteWith(Interpreter interpreter) {
interpreter.interpret(this);
public InsertRoot(T entity) {
this.entity = entity;
}
public T getEntity() {
return this.entity;
}
public String toString() {
return "DbAction.InsertRoot(entity=" + this.getEntity() + ")";
}
}
@@ -118,15 +114,26 @@ public interface DbAction<T> {
*
* @param <T> type of the entity for which this represents a database interaction.
*/
@Value
class Update<T> implements WithEntity<T> {
final class Update<T> implements WithEntity<T> {
@NonNull T entity;
@NonNull PersistentPropertyPath<RelationalPersistentProperty> propertyPath;
private final T entity;
private final PersistentPropertyPath<RelationalPersistentProperty> propertyPath;
@Override
public void doExecuteWith(Interpreter interpreter) {
interpreter.interpret(this);
public Update(T entity, PersistentPropertyPath<RelationalPersistentProperty> propertyPath) {
this.entity = entity;
this.propertyPath = propertyPath;
}
public T getEntity() {
return this.entity;
}
public PersistentPropertyPath<RelationalPersistentProperty> getPropertyPath() {
return this.propertyPath;
}
public String toString() {
return "DbAction.Update(entity=" + this.getEntity() + ", propertyPath=" + this.getPropertyPath() + ")";
}
}
@@ -135,15 +142,20 @@ public interface DbAction<T> {
*
* @param <T> type of the entity for which this represents a database interaction.
*/
@Data
class UpdateRoot<T> implements WithEntity<T>, WithVersion {
class UpdateRoot<T> implements WithEntity<T> {
@NonNull final T entity;
@Nullable Number nextVersion;
private final T entity;
@Override
public void doExecuteWith(Interpreter interpreter) {
interpreter.interpret(this);
public UpdateRoot(T entity) {
this.entity = entity;
}
public T getEntity() {
return this.entity;
}
public String toString() {
return "DbAction.UpdateRoot(entity=" + this.getEntity() + ")";
}
}
@@ -152,18 +164,40 @@ public interface DbAction<T> {
*
* @param <T> type of the entity for which this represents a database interaction.
*/
@Value
class Merge<T> implements WithDependingOn<T>, WithPropertyPath<T> {
final class Merge<T> implements WithDependingOn<T>, WithPropertyPath<T> {
@NonNull T entity;
@NonNull PersistentPropertyPath<RelationalPersistentProperty> propertyPath;
@NonNull WithEntity<?> dependingOn;
private final T entity;
private final PersistentPropertyPath<RelationalPersistentProperty> propertyPath;
private final WithEntity<?> dependingOn;
Map<PersistentPropertyPath<RelationalPersistentProperty>, Object> qualifiers = new HashMap<>();
private final Map<PersistentPropertyPath<RelationalPersistentProperty>, Object> qualifiers = Collections.emptyMap();
@Override
public void doExecuteWith(Interpreter interpreter) {
interpreter.interpret(this);
public Merge(T entity, PersistentPropertyPath<RelationalPersistentProperty> propertyPath,
WithEntity<?> dependingOn) {
this.entity = entity;
this.propertyPath = propertyPath;
this.dependingOn = dependingOn;
}
public T getEntity() {
return this.entity;
}
public PersistentPropertyPath<RelationalPersistentProperty> getPropertyPath() {
return this.propertyPath;
}
public DbAction.WithEntity<?> getDependingOn() {
return this.dependingOn;
}
public Map<PersistentPropertyPath<RelationalPersistentProperty>, Object> getQualifiers() {
return this.qualifiers;
}
public String toString() {
return "DbAction.Merge(entity=" + this.getEntity() + ", propertyPath=" + this.getPropertyPath() + ", dependingOn="
+ this.getDependingOn() + ", qualifiers=" + this.getQualifiers() + ")";
}
}
@@ -172,15 +206,27 @@ public interface DbAction<T> {
*
* @param <T> type of the entity for which this represents a database interaction.
*/
@Value
class Delete<T> implements WithPropertyPath<T> {
final class Delete<T> implements WithPropertyPath<T> {
@NonNull Object rootId;
@NonNull PersistentPropertyPath<RelationalPersistentProperty> propertyPath;
private final Object rootId;
@Override
public void doExecuteWith(Interpreter interpreter) {
interpreter.interpret(this);
private final PersistentPropertyPath<RelationalPersistentProperty> propertyPath;
public Delete(Object rootId, PersistentPropertyPath<RelationalPersistentProperty> propertyPath) {
this.rootId = rootId;
this.propertyPath = propertyPath;
}
public Object getRootId() {
return this.rootId;
}
public PersistentPropertyPath<RelationalPersistentProperty> getPropertyPath() {
return this.propertyPath;
}
public String toString() {
return "DbAction.Delete(rootId=" + this.getRootId() + ", propertyPath=" + this.getPropertyPath() + ")";
}
}
@@ -192,16 +238,36 @@ public interface DbAction<T> {
*
* @param <T> type of the entity for which this represents a database interaction.
*/
@Value
class DeleteRoot<T> implements DbAction<T>{
final class DeleteRoot<T> implements DbAction<T> {
@NonNull final Object id;
@NonNull final Class<T> entityType;
@Nullable final Number previousVersion;
private final Object id;
private final Class<T> entityType;
@Nullable private final Number previousVersion;
@Override
public void doExecuteWith(Interpreter interpreter) {
interpreter.interpret(this);
public DeleteRoot(Object id, Class<T> entityType, @Nullable Number previousVersion) {
this.id = id;
this.entityType = entityType;
this.previousVersion = previousVersion;
}
public Object getId() {
return this.id;
}
public Class<T> getEntityType() {
return this.entityType;
}
@Nullable
public Number getPreviousVersion() {
return this.previousVersion;
}
public String toString() {
return "DbAction.DeleteRoot(id=" + this.getId() + ", entityType=" + this.getEntityType() + ", previousVersion="
+ this.getPreviousVersion() + ")";
}
}
@@ -211,14 +277,20 @@ public interface DbAction<T> {
*
* @param <T> type of the entity for which this represents a database interaction.
*/
@Value
class DeleteAll<T> implements WithPropertyPath<T> {
final class DeleteAll<T> implements WithPropertyPath<T> {
@NonNull PersistentPropertyPath<RelationalPersistentProperty> propertyPath;
private final PersistentPropertyPath<RelationalPersistentProperty> propertyPath;
@Override
public void doExecuteWith(Interpreter interpreter) {
interpreter.interpret(this);
public DeleteAll(PersistentPropertyPath<RelationalPersistentProperty> propertyPath) {
this.propertyPath = propertyPath;
}
public PersistentPropertyPath<RelationalPersistentProperty> getPropertyPath() {
return this.propertyPath;
}
public String toString() {
return "DbAction.DeleteAll(propertyPath=" + this.getPropertyPath() + ")";
}
}
@@ -230,14 +302,20 @@ public interface DbAction<T> {
*
* @param <T> type of the entity for which this represents a database interaction.
*/
@Value
class DeleteAllRoot<T> implements DbAction<T> {
final class DeleteAllRoot<T> implements DbAction<T> {
@NonNull private final Class<T> entityType;
private final Class<T> entityType;
@Override
public void doExecuteWith(Interpreter interpreter) {
interpreter.interpret(this);
public DeleteAllRoot(Class<T> entityType) {
this.entityType = entityType;
}
public Class<T> getEntityType() {
return this.entityType;
}
public String toString() {
return "DbAction.DeleteAllRoot(entityType=" + this.getEntityType() + ")";
}
}
@@ -272,12 +350,13 @@ public interface DbAction<T> {
// Probably we need better names.
@Nullable
default Pair<PersistentPropertyPath<RelationalPersistentProperty>, Object> getQualifier() {
Map<PersistentPropertyPath<RelationalPersistentProperty>, Object> qualifiers = getQualifiers();
if (qualifiers.size() == 0)
return null;
if (qualifiers.size() > 1) {
throw new IllegalStateException("Can't handle more then on qualifier");
throw new IllegalStateException("Can't handle more then one qualifier");
}
Map.Entry<PersistentPropertyPath<RelationalPersistentProperty>, Object> entry = qualifiers.entrySet().iterator()
@@ -321,12 +400,6 @@ public interface DbAction<T> {
*/
interface WithGeneratedId<T> extends WithEntity<T> {
/**
* @return the entity to persist. Guaranteed to be not {@code null}.
*/
@Nullable
Object getGeneratedId();
@SuppressWarnings("unchecked")
@Override
default Class<T> getEntityType() {
@@ -352,11 +425,4 @@ public interface DbAction<T> {
return (Class<T>) getPropertyPath().getRequiredLeafProperty().getActualType();
}
}
interface WithVersion {
Number getNextVersion();
void setNextVersion(Number nextVersion);
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.relational.core.conversion;
import org.springframework.lang.Nullable;
/**
* @author Jens Schauder
* @since 2.0
*/
public class DbActionExecutionResult {
private final Object id;
private final DbAction<?> action;
public DbActionExecutionResult(DbAction<?> action, @Nullable Object newId) {
this.action = action;
this.id = newId;
}
public DbActionExecutionResult() {
action = null;
id = null;
}
@Nullable
public Object getId() {
return id;
}
public DbAction<?> getAction() {
return action;
}
}

View File

@@ -1,61 +0,0 @@
/*
* Copyright 2017-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.relational.core.conversion;
import org.springframework.data.relational.core.conversion.DbAction.Delete;
import org.springframework.data.relational.core.conversion.DbAction.DeleteAll;
import org.springframework.data.relational.core.conversion.DbAction.DeleteAllRoot;
import org.springframework.data.relational.core.conversion.DbAction.DeleteRoot;
import org.springframework.data.relational.core.conversion.DbAction.Insert;
import org.springframework.data.relational.core.conversion.DbAction.InsertRoot;
import org.springframework.data.relational.core.conversion.DbAction.Merge;
import org.springframework.data.relational.core.conversion.DbAction.Update;
import org.springframework.data.relational.core.conversion.DbAction.UpdateRoot;
/**
* An {@link Interpreter} gets called by a {@link AggregateChange} for each {@link DbAction} and is tasked with
* executing that action against a database. While the {@link DbAction} is just an abstract representation of a database
* action it's the task of an interpreter to actually execute it. This typically involves creating some SQL and running
* it using JDBC, but it may also use some third party technology like MyBatis or jOOQ to do this.
*
* @author Jens Schauder
*/
public interface Interpreter {
<T> void interpret(Insert<T> insert);
<T> void interpret(InsertRoot<T> insert);
/**
* Interpret an {@link Update}. Interpreting normally means "executing".
*
* @param <T> the type of entity to work on.
* @param update the {@link Update} to be executed
*/
<T> void interpret(Update<T> update);
<T> void interpret(UpdateRoot<T> update);
<T> void interpret(Merge<T> update);
<T> void interpret(Delete<T> delete);
<T> void interpret(DeleteRoot<T> deleteRoot);
<T> void interpret(DeleteAll<T> delete);
<T> void interpret(DeleteAllRoot<T> deleteAllRoot);
}

View File

@@ -0,0 +1,166 @@
/*
* Copyright 2017-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.relational.core.conversion;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* 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
*/
public class MutableAggregateChange<T> implements AggregateChange<T> {
private final Kind kind;
/** Type of the aggregate root to be changed */
private final Class<T> entityType;
private final List<DbAction<?>> actions = new ArrayList<>();
/** Aggregate root, to which the change applies, if available */
@Nullable private T entity;
public MutableAggregateChange(Kind kind, Class<T> entityType, @Nullable T entity) {
this.kind = kind;
this.entityType = entityType;
this.entity = entity;
}
/**
* Factory method to create an {@link MutableAggregateChange} for saving entities.
*
* @param entity aggregate root to save.
* @param <T> entity type.
* @return the {@link MutableAggregateChange} for saving the root {@code entity}.
* @since 1.2
*/
@SuppressWarnings("unchecked")
public static <T> MutableAggregateChange<T> forSave(T entity) {
Assert.notNull(entity, "Entity must not be null");
return new MutableAggregateChange<>(Kind.SAVE, (Class<T>) ClassUtils.getUserClass(entity), entity);
}
/**
* Factory method to create an {@link MutableAggregateChange} for deleting entities.
*
* @param entity aggregate root to delete.
* @param <T> entity type.
* @return the {@link MutableAggregateChange} for deleting the root {@code entity}.
* @since 1.2
*/
@SuppressWarnings("unchecked")
public static <T> MutableAggregateChange<T> forDelete(T entity) {
Assert.notNull(entity, "Entity must not be null");
return forDelete((Class<T>) ClassUtils.getUserClass(entity), entity);
}
/**
* Factory method to create an {@link MutableAggregateChange} for deleting entities.
*
* @param entityClass aggregate root type.
* @param entity aggregate root to delete.
* @param <T> entity type.
* @return the {@link MutableAggregateChange} for deleting the root {@code entity}.
* @since 1.2
*/
public static <T> MutableAggregateChange<T> forDelete(Class<T> entityClass, @Nullable T entity) {
Assert.notNull(entityClass, "Entity class must not be null");
return new MutableAggregateChange<>(Kind.DELETE, entityClass, entity);
}
/**
* Adds an action to this {@code AggregateChange}.
*
* @param action must not be {@literal null}.
*/
public void addAction(DbAction<?> action) {
Assert.notNull(action, "Action must not be null.");
actions.add(action);
}
/**
* Applies the given consumer to each {@link DbAction} in this {@code AggregateChange}.
*
* @param consumer must not be {@literal null}.
*/
@Override
public void forEachAction(Consumer<? super DbAction<?>> consumer) {
Assert.notNull(consumer, "Consumer must not be null.");
actions.forEach(consumer);
}
/**
* Returns the {@link Kind} of {@code AggregateChange} this is.
*
* @return guaranteed to be not {@literal null}.
*/
@Override
public Kind getKind() {
return this.kind;
}
/**
* The type of the root of this {@code AggregateChange}.
*
* @return Guaranteed to be not {@literal null}.
*/
@Override
public Class<T> getEntityType() {
return this.entityType;
}
/**
* Set the root object of the {@code AggregateChange}.
*
* @param aggregateRoot may be {@literal null} if the change refers to a list of aggregates or references it by id.
*/
public void setEntity(@Nullable T aggregateRoot) {
if (aggregateRoot != null) {
Assert.isInstanceOf(entityType, aggregateRoot,
String.format("AggregateRoot must be of type %s", entityType.getName()));
}
entity = aggregateRoot;
}
/**
* The entity to which this {@link MutableAggregateChange} relates.
*
* @return may be {@literal null}.
*/
@Override
@Nullable
public T getEntity() {
return this.entity;
}
}

View File

@@ -27,17 +27,17 @@ import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Converts an entity that is about to be deleted into {@link DbAction}s inside a {@link AggregateChange} that need to
* be executed against the database to recreate the appropriate state in the database. If the {@link AggregateChange}
* has a reference to the entity and the entity has a version attribute, the delete will include an optimistic record
* locking check.
* Converts an entity that is about to be deleted into {@link DbAction}s inside a {@link MutableAggregateChange} that
* need to be executed against the database to recreate the appropriate state in the database. If the
* {@link MutableAggregateChange} has a reference to the entity and the entity has a version attribute, the delete will
* include an optimistic record locking check.
*
* @author Jens Schauder
* @author Mark Paluch
* @author Bastian Wilhelm
* @author Tyler Van Gorder
*/
public class RelationalEntityDeleteWriter implements EntityWriter<Object, AggregateChange<?>> {
public class RelationalEntityDeleteWriter implements EntityWriter<Object, MutableAggregateChange<?>> {
private final RelationalMappingContext context;
@@ -49,7 +49,7 @@ public class RelationalEntityDeleteWriter implements EntityWriter<Object, Aggreg
}
/**
* Fills the provided {@link AggregateChange} with the necessary {@link DbAction}s to delete the aggregate root
* Fills the provided {@link MutableAggregateChange} 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".
*
@@ -57,7 +57,7 @@ public class RelationalEntityDeleteWriter implements EntityWriter<Object, Aggreg
* @param aggregateChange must not be {@code null}.
*/
@Override
public void write(@Nullable Object id, AggregateChange<?> aggregateChange) {
public void write(@Nullable Object id, MutableAggregateChange<?> aggregateChange) {
if (id == null) {
deleteAll(aggregateChange.getEntityType()).forEach(aggregateChange::addAction);
@@ -81,7 +81,7 @@ public class RelationalEntityDeleteWriter implements EntityWriter<Object, Aggreg
return actions;
}
private <T> List<DbAction<?>> deleteRoot(Object id, AggregateChange<T> aggregateChange) {
private <T> List<DbAction<?>> deleteRoot(Object id, MutableAggregateChange<T> aggregateChange) {
List<DbAction<?>> actions = new ArrayList<>(deleteReferencedEntities(id, aggregateChange));
actions.add(new DbAction.DeleteRoot<>(id, aggregateChange.getEntityType(), getVersion(aggregateChange)));
@@ -90,12 +90,12 @@ public class RelationalEntityDeleteWriter implements EntityWriter<Object, Aggreg
}
/**
* Add {@link DbAction.Delete} actions to the {@link AggregateChange} for deleting all referenced entities.
* Add {@link DbAction.Delete} actions to the {@link MutableAggregateChange} 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 List<DbAction<?>> deleteReferencedEntities(Object id, AggregateChange<?> aggregateChange) {
private List<DbAction<?>> deleteReferencedEntities(Object id, MutableAggregateChange<?> aggregateChange) {
List<DbAction<?>> actions = new ArrayList<>();
@@ -108,7 +108,7 @@ public class RelationalEntityDeleteWriter implements EntityWriter<Object, Aggreg
}
@Nullable
private Number getVersion(AggregateChange<?> aggregateChange) {
private Number getVersion(MutableAggregateChange<?> aggregateChange) {
RelationalPersistentEntity<?> persistentEntity = context
.getRequiredPersistentEntity(aggregateChange.getEntityType());

View File

@@ -21,13 +21,14 @@ import org.springframework.data.convert.EntityWriter;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
/**
* Converts an aggregate represented by its root into an {@link AggregateChange}. Does not perform any isNew check.
* Converts an aggregate represented by its root into an {@link MutableAggregateChange}. Does not perform any isNew
* check.
*
* @author Thomas Lang
* @author Jens Schauder
* @since 1.1
*/
public class RelationalEntityInsertWriter implements EntityWriter<Object, AggregateChange<?>> {
public class RelationalEntityInsertWriter implements EntityWriter<Object, MutableAggregateChange<?>> {
private final RelationalMappingContext context;
@@ -40,7 +41,7 @@ public class RelationalEntityInsertWriter implements EntityWriter<Object, Aggreg
* @see org.springframework.data.convert.EntityWriter#save(java.lang.Object, java.lang.Object)
*/
@Override
public void write(Object root, AggregateChange<?> aggregateChange) {
public void write(Object root, MutableAggregateChange<?> aggregateChange) {
List<DbAction<?>> actions = new WritingContext(context, root, aggregateChange).insert();
actions.forEach(aggregateChange::addAction);

View File

@@ -21,13 +21,14 @@ import org.springframework.data.convert.EntityWriter;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
/**
* Converts an aggregate represented by its root into an {@link AggregateChange}. Does not perform any isNew check.
* Converts an aggregate represented by its root into an {@link MutableAggregateChange}. Does not perform any isNew
* check.
*
* @author Thomas Lang
* @author Jens Schauder
* @since 1.1
*/
public class RelationalEntityUpdateWriter implements EntityWriter<Object, AggregateChange<?>> {
public class RelationalEntityUpdateWriter implements EntityWriter<Object, MutableAggregateChange<?>> {
private final RelationalMappingContext context;
@@ -40,7 +41,7 @@ public class RelationalEntityUpdateWriter implements EntityWriter<Object, Aggreg
* @see org.springframework.data.convert.EntityWriter#save(java.lang.Object, java.lang.Object)
*/
@Override
public void write(Object root, AggregateChange<?> aggregateChange) {
public void write(Object root, MutableAggregateChange<?> aggregateChange) {
List<DbAction<?>> actions = new WritingContext(context, root, aggregateChange).update();
actions.forEach(aggregateChange::addAction);

View File

@@ -43,6 +43,7 @@ public class RelationalEntityVersionUtils {
@Nullable
public static <S> Number getVersionNumberFromEntity(S instance, RelationalPersistentEntity<S> persistentEntity,
RelationalConverter converter) {
if (!persistentEntity.hasVersionProperty()) {
throw new IllegalArgumentException("The entity does not have a version property.");
}

View File

@@ -21,12 +21,12 @@ import org.springframework.data.convert.EntityWriter;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
/**
* Converts an aggregate represented by its root into an {@link AggregateChange}.
* Converts an aggregate represented by its root into an {@link MutableAggregateChange}.
*
* @author Jens Schauder
* @author Mark Paluch
*/
public class RelationalEntityWriter implements EntityWriter<Object, AggregateChange<?>> {
public class RelationalEntityWriter implements EntityWriter<Object, MutableAggregateChange<?>> {
private final RelationalMappingContext context;
@@ -39,7 +39,7 @@ public class RelationalEntityWriter implements EntityWriter<Object, AggregateCha
* @see org.springframework.data.convert.EntityWriter#save(java.lang.Object, java.lang.Object)
*/
@Override
public void write(Object root, AggregateChange<?> aggregateChange) {
public void write(Object root, MutableAggregateChange<?> aggregateChange) {
List<DbAction<?>> actions = new WritingContext(context, root, aggregateChange).save();
actions.forEach(aggregateChange::addAction);

View File

@@ -49,7 +49,7 @@ class WritingContext {
private final Map<PathNode, DbAction> previousActions = new HashMap<>();
private Map<PersistentPropertyPath<RelationalPersistentProperty>, List<PathNode>> nodesCache = new HashMap<>();
WritingContext(RelationalMappingContext context, Object root, AggregateChange<?> aggregateChange) {
WritingContext(RelationalMappingContext context, Object root, MutableAggregateChange<?> aggregateChange) {
this.context = context;
this.root = root;
@@ -73,8 +73,7 @@ class WritingContext {
}
/**
* Leaves out the isNew check as defined in #DATAJDBC-282
* Possible Deadlocks in Execution Order in #DATAJDBC-488
* Leaves out the isNew check as defined in #DATAJDBC-282 Possible Deadlocks in Execution Order in #DATAJDBC-488
*
* @return List of {@link DbAction}s
* @see <a href="https://jira.spring.io/browse/DATAJDBC-282">DAJDBC-282</a>
@@ -133,17 +132,18 @@ class WritingContext {
if (node.getPath().getRequiredLeafProperty().isQualified()) {
Pair<Object, Object> value = (Pair) node.getValue();
insert = new DbAction.Insert<>(value.getSecond(), path, parentAction);
insert.getQualifiers().put(node.getPath(), value.getFirst());
Map<PersistentPropertyPath<RelationalPersistentProperty>, Object> qualifiers = new HashMap<>();
qualifiers.put(node.getPath(), value.getFirst());
RelationalPersistentEntity<?> parentEntity = context.getRequiredPersistentEntity(parentAction.getEntityType());
if (!parentEntity.hasIdProperty() && parentAction instanceof DbAction.Insert) {
insert.getQualifiers().putAll(((DbAction.Insert<?>) parentAction).getQualifiers());
qualifiers.putAll(((DbAction.Insert<?>) parentAction).getQualifiers());
}
insert = new DbAction.Insert<>(value.getSecond(), path, parentAction, qualifiers);
} else {
insert = new DbAction.Insert<>(node.getValue(), path, parentAction);
insert = new DbAction.Insert<>(node.getValue(), path, parentAction, new HashMap<>());
}
previousActions.put(node, insert);
actions.add(insert);

View File

@@ -37,8 +37,8 @@ import org.springframework.util.Assert;
public class PersistentPropertyPathExtension {
private final RelationalPersistentEntity<?> entity;
private final @Nullable PersistentPropertyPath<RelationalPersistentProperty> path;
private final MappingContext<RelationalPersistentEntity<?>, RelationalPersistentProperty> context;
private final @Nullable PersistentPropertyPath<? extends RelationalPersistentProperty> path;
private final MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> context;
private final Lazy<SqlIdentifier> columnAlias = Lazy.of(() -> prefixWithTableAlias(getColumnName()));
@@ -49,7 +49,7 @@ public class PersistentPropertyPathExtension {
* @param entity Root entity of the path. Must not be {@literal null}.
*/
public PersistentPropertyPathExtension(
MappingContext<RelationalPersistentEntity<?>, RelationalPersistentProperty> context,
MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> context,
RelationalPersistentEntity<?> entity) {
Assert.notNull(context, "Context must not be null.");
@@ -67,8 +67,8 @@ public class PersistentPropertyPathExtension {
* @param path must not be {@literal null}.
*/
public PersistentPropertyPathExtension(
MappingContext<RelationalPersistentEntity<?>, RelationalPersistentProperty> context,
PersistentPropertyPath<RelationalPersistentProperty> path) {
MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> context,
PersistentPropertyPath<? extends RelationalPersistentProperty> path) {
Assert.notNull(context, "Context must not be null.");
Assert.notNull(path, "Path must not be null.");
@@ -319,7 +319,7 @@ public class PersistentPropertyPathExtension {
*/
public PersistentPropertyPathExtension extendBy(RelationalPersistentProperty property) {
PersistentPropertyPath<RelationalPersistentProperty> newPath = path == null //
PersistentPropertyPath<? extends RelationalPersistentProperty> newPath = path == null //
? context.getPersistentPropertyPath(property.getName(), entity.getType()) //
: context.getPersistentPropertyPath(path.toDotPath() + "." + property.getName(), entity.getType());
@@ -367,7 +367,7 @@ public class PersistentPropertyPathExtension {
* @return Guaranteed to be not {@literal null}.
* @throws IllegalStateException if this path is empty.
*/
public PersistentPropertyPath<RelationalPersistentProperty> getRequiredPersistentPropertyPath() {
public PersistentPropertyPath<? extends RelationalPersistentProperty> getRequiredPersistentPropertyPath() {
Assert.state(path != null, "No path.");
@@ -413,7 +413,7 @@ public class PersistentPropertyPathExtension {
return suffix;
}
PersistentPropertyPath<RelationalPersistentProperty> parentPath = path.getParentPath();
PersistentPropertyPath<? extends RelationalPersistentProperty> parentPath = path.getParentPath();
RelationalPersistentProperty parentLeaf = parentPath.getRequiredLeafProperty();
if (!parentLeaf.isEmbedded()) {

View File

@@ -16,6 +16,7 @@
package org.springframework.data.relational.core.mapping.event;
import org.springframework.data.relational.core.conversion.AggregateChange;
import org.springframework.data.relational.core.conversion.MutableAggregateChange;
import org.springframework.lang.Nullable;
/**
@@ -32,8 +33,8 @@ public class AfterDeleteEvent<E> extends RelationalDeleteEvent<E> {
/**
* @param id of the entity. Must not be {@literal null}.
* @param instance the deleted entity if it is available. May be {@literal null}.
* @param change the {@link AggregateChange} encoding the actions that were performed on the database as part of the
* delete operation. Must not be {@literal null}.
* @param change the {@link MutableAggregateChange} encoding the actions that were performed on the database as part
* of the delete operation. Must not be {@literal null}.
*/
public AfterDeleteEvent(Identifier id, @Nullable E instance, AggregateChange<E> change) {
super(id, instance, change);

View File

@@ -16,6 +16,7 @@
package org.springframework.data.relational.core.mapping.event;
import org.springframework.data.relational.core.conversion.AggregateChange;
import org.springframework.data.relational.core.conversion.MutableAggregateChange;
/**
* Gets published after a new instance or a changed instance was saved in the database.
@@ -28,7 +29,8 @@ public class AfterSaveEvent<E> extends RelationalSaveEvent<E> {
/**
* @param instance the saved entity. Must not be {@literal null}.
* @param change the {@link AggregateChange} encoding the actions performed on the database as part of the delete.
* @param change the {@link MutableAggregateChange} encoding the actions performed on the database as part of the
* delete.
* Must not be {@literal null}.
*/
public AfterSaveEvent(E instance, AggregateChange<E> change) {

View File

@@ -17,6 +17,7 @@ package org.springframework.data.relational.core.mapping.event;
import org.springframework.data.mapping.callback.EntityCallback;
import org.springframework.data.relational.core.conversion.AggregateChange;
import org.springframework.data.relational.core.conversion.MutableAggregateChange;
/**
* An {@link EntityCallback} that gets invoked before an entity is deleted. This callback gets only invoked if the
@@ -31,12 +32,12 @@ public interface BeforeDeleteCallback<T> extends EntityCallback<T> {
/**
* Entity callback method invoked before an aggregate root is deleted. Can return either the same or a modified
* instance of the aggregate and can modify {@link AggregateChange} contents. This method is called after converting
* the {@code aggregate} to {@link AggregateChange}. Changes to the aggregate are not taken into account for deleting.
* Only transient fields of the entity should be changed in this callback.
* instance of the aggregate and can modify {@link MutableAggregateChange} contents. This method is called after
* converting the {@code aggregate} to {@link MutableAggregateChange}. Changes to the aggregate are not taken into
* account for deleting. Only transient fields of the entity should be changed in this callback.
*
* @param aggregate the aggregate.
* @param aggregateChange the associated {@link AggregateChange}.
* @param aggregateChange the associated {@link MutableAggregateChange}.
* @return the aggregate to be deleted.
*/
T onBeforeDelete(T aggregate, AggregateChange<T> aggregateChange);

View File

@@ -16,11 +16,12 @@
package org.springframework.data.relational.core.mapping.event;
import org.springframework.data.relational.core.conversion.AggregateChange;
import org.springframework.data.relational.core.conversion.MutableAggregateChange;
import org.springframework.lang.Nullable;
/**
* Gets published when an entity is about to get deleted. The contained {@link AggregateChange} is mutable and may be
* changed in order to change the actions that get performed on the database as part of the delete operation.
* Gets published when an entity is about to get deleted. The contained {@link MutableAggregateChange} is mutable and
* may be changed in order to change the actions that get performed on the database as part of the delete operation.
*
* @author Jens Schauder
*/
@@ -31,7 +32,7 @@ public class BeforeDeleteEvent<E> extends RelationalDeleteEvent<E> {
/**
* @param id the id of the entity. Must not be {@literal null}.
* @param entity the entity about to get deleted. May be {@literal null}.
* @param change the {@link AggregateChange} encoding the planned actions to be performed on the database.
* @param change the {@link MutableAggregateChange} encoding the planned actions to be performed on the database.
*/
public BeforeDeleteEvent(Identifier id, @Nullable E entity, AggregateChange<E> change) {
super(id, entity, change);

View File

@@ -17,6 +17,7 @@ package org.springframework.data.relational.core.mapping.event;
import org.springframework.data.mapping.callback.EntityCallback;
import org.springframework.data.relational.core.conversion.AggregateChange;
import org.springframework.data.relational.core.conversion.MutableAggregateChange;
/**
* An {@link EntityCallback} that gets invoked before changes are applied to the database, after the aggregate was
@@ -31,13 +32,13 @@ public interface BeforeSaveCallback<T> extends EntityCallback<T> {
/**
* Entity callback method invoked before an aggregate root is saved. Can return either the same or a modified instance
* of the aggregate and can modify {@link AggregateChange} contents. This method is called after converting the
* {@code aggregate} to {@link AggregateChange}. Changes to the aggregate are not taken into account for saving. Only
* transient fields of the entity should be changed in this callback. To change persistent the entity before being
* converted, use the {@link BeforeConvertCallback}.
* of the aggregate and can modify {@link MutableAggregateChange} contents. This method is called after converting the
* {@code aggregate} to {@link MutableAggregateChange}. Changes to the aggregate are not taken into account for
* saving. Only transient fields of the entity should be changed in this callback. To change persistent the entity
* before being converted, use the {@link BeforeConvertCallback}.
*
* @param aggregate the aggregate.
* @param aggregateChange the associated {@link AggregateChange}.
* @param aggregateChange the associated {@link MutableAggregateChange}.
* @return the aggregate object to be persisted.
*/
T onBeforeSave(T aggregate, AggregateChange<T> aggregateChange);

View File

@@ -16,10 +16,11 @@
package org.springframework.data.relational.core.mapping.event;
import org.springframework.data.relational.core.conversion.AggregateChange;
import org.springframework.data.relational.core.conversion.MutableAggregateChange;
/**
* Gets published before an entity gets saved to the database. The contained {@link AggregateChange} is mutable and may
* be changed in order to change the actions that get performed on the database as part of the save operation.
* Gets published before an entity gets saved to the database. The contained {@link MutableAggregateChange} is mutable
* and may be changed in order to change the actions that get performed on the database as part of the save operation.
*
* @author Jens Schauder
*/
@@ -29,7 +30,7 @@ public class BeforeSaveEvent<E> extends RelationalSaveEvent<E> {
/**
* @param instance the entity about to get saved. Must not be {@literal null}.
* @param change the {@link AggregateChange} that is going to get applied to the database. Must not be
* @param change the {@link MutableAggregateChange} that is going to get applied to the database. Must not be
* {@literal null}.
*/
public BeforeSaveEvent(E instance, AggregateChange<E> change) {

View File

@@ -1,53 +0,0 @@
/*
* Copyright 2017-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.relational.core.conversion;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import org.junit.Test;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
/**
* Unit tests for {@link DbAction}s
*
* @author Jens Schauder
*/
public class DbActionUnitTests {
RelationalMappingContext context = new RelationalMappingContext();
@Test // DATAJDBC-150
public void exceptionFromActionContainsUsefulInformationWhenInterpreterFails() {
DummyEntity entity = new DummyEntity();
DbAction.InsertRoot<DummyEntity> insert = new DbAction.InsertRoot<>(entity);
Interpreter failingInterpreter = mock(Interpreter.class);
doThrow(new RuntimeException()).when(failingInterpreter).interpret(any(DbAction.InsertRoot.class));
assertThatExceptionOfType(DbActionExecutionException.class) //
.isThrownBy(() -> insert.executeWith(failingInterpreter)) //
.withMessageContaining("Insert") //
.withMessageContaining(entity.toString());
}
static class DummyEntity {
String someName;
}
}

View File

@@ -17,13 +17,15 @@ package org.springframework.data.relational.core.conversion;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
import org.assertj.core.api.Assertions;
import org.assertj.core.groups.Tuple;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.conversion.AggregateChange.Kind;
import org.springframework.data.relational.core.conversion.DbAction.Delete;
import org.springframework.data.relational.core.conversion.DbAction.DeleteAll;
import org.springframework.data.relational.core.conversion.DbAction.DeleteAllRoot;
@@ -45,11 +47,12 @@ public class RelationalEntityDeleteWriterUnitTests {
SomeEntity entity = new SomeEntity(23L);
AggregateChange<SomeEntity> aggregateChange = new AggregateChange<>(Kind.DELETE, SomeEntity.class, entity);
MutableAggregateChange<SomeEntity> aggregateChange = new MutableAggregateChange<>(AggregateChange.Kind.DELETE,
SomeEntity.class, entity);
converter.write(entity.id, aggregateChange);
Assertions.assertThat(aggregateChange.getActions())
Assertions.assertThat(extractActions(aggregateChange))
.extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath) //
.containsExactly( //
Tuple.tuple(Delete.class, YetAnother.class, "other.yetAnother"), //
@@ -61,11 +64,12 @@ public class RelationalEntityDeleteWriterUnitTests {
@Test // DATAJDBC-188
public void deleteAllDeletesAllEntitiesAndReferencedEntities() {
AggregateChange<SomeEntity> aggregateChange = new AggregateChange<>(Kind.DELETE, SomeEntity.class, null);
MutableAggregateChange<SomeEntity> aggregateChange = new MutableAggregateChange<>(AggregateChange.Kind.DELETE,
SomeEntity.class, null);
converter.write(null, aggregateChange);
Assertions.assertThat(aggregateChange.getActions())
Assertions.assertThat(extractActions(aggregateChange))
.extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath) //
.containsExactly( //
Tuple.tuple(DeleteAll.class, YetAnother.class, "other.yetAnother"), //
@@ -74,6 +78,13 @@ public class RelationalEntityDeleteWriterUnitTests {
);
}
private List<DbAction<?>> extractActions(MutableAggregateChange<?> aggregateChange) {
List<DbAction<?>> actions = new ArrayList<>();
aggregateChange.forEachAction(actions::add);
return actions;
}
@Data
private static class SomeEntity {

View File

@@ -19,11 +19,13 @@ import static org.assertj.core.api.Assertions.*;
import lombok.RequiredArgsConstructor;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.conversion.AggregateChange.Kind;
import org.springframework.data.relational.core.conversion.DbAction.InsertRoot;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
@@ -42,12 +44,12 @@ public class RelationalEntityInsertWriterUnitTests {
public void newEntityGetsConvertedToOneInsert() {
SingleReferenceEntity entity = new SingleReferenceEntity(null);
AggregateChange<SingleReferenceEntity> aggregateChange = //
new AggregateChange(Kind.SAVE, SingleReferenceEntity.class, entity);
MutableAggregateChange<SingleReferenceEntity> aggregateChange = //
new MutableAggregateChange(AggregateChange.Kind.SAVE, SingleReferenceEntity.class, entity);
converter.write(entity, aggregateChange);
assertThat(aggregateChange.getActions()) //
assertThat(extractActions(aggregateChange)) //
.extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath,
DbActionTestSupport::actualEntityType, DbActionTestSupport::isWithDependsOn) //
.containsExactly( //
@@ -60,12 +62,12 @@ public class RelationalEntityInsertWriterUnitTests {
SingleReferenceEntity entity = new SingleReferenceEntity(SOME_ENTITY_ID);
AggregateChange<SingleReferenceEntity> aggregateChange = //
new AggregateChange(Kind.SAVE, SingleReferenceEntity.class, entity);
MutableAggregateChange<SingleReferenceEntity> aggregateChange = //
new MutableAggregateChange(AggregateChange.Kind.SAVE, SingleReferenceEntity.class, entity);
converter.write(entity, aggregateChange);
assertThat(aggregateChange.getActions()) //
assertThat(extractActions(aggregateChange)) //
.extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath,
DbActionTestSupport::actualEntityType, DbActionTestSupport::isWithDependsOn) //
.containsExactly( //
@@ -74,6 +76,13 @@ public class RelationalEntityInsertWriterUnitTests {
}
private List<DbAction<?>> extractActions(MutableAggregateChange<?> aggregateChange) {
List<DbAction<?>> actions = new ArrayList<>();
aggregateChange.forEachAction(actions::add);
return actions;
}
@RequiredArgsConstructor
static class SingleReferenceEntity {

View File

@@ -19,11 +19,13 @@ import static org.assertj.core.api.Assertions.*;
import lombok.RequiredArgsConstructor;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.conversion.AggregateChange.Kind;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
/**
@@ -43,20 +45,27 @@ public class RelationalEntityUpdateWriterUnitTests {
SingleReferenceEntity entity = new SingleReferenceEntity(SOME_ENTITY_ID);
AggregateChange<RelationalEntityWriterUnitTests.SingleReferenceEntity> aggregateChange = //
new AggregateChange(Kind.SAVE, SingleReferenceEntity.class, entity);
MutableAggregateChange<RelationalEntityWriterUnitTests.SingleReferenceEntity> aggregateChange = //
new MutableAggregateChange(AggregateChange.Kind.SAVE, SingleReferenceEntity.class, entity);
converter.write(entity, aggregateChange);
assertThat(aggregateChange.getActions()) //
.extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath, DbActionTestSupport::actualEntityType,
DbActionTestSupport::isWithDependsOn) //
assertThat(extractActions(aggregateChange)) //
.extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath,
DbActionTestSupport::actualEntityType, DbActionTestSupport::isWithDependsOn) //
.containsExactly( //
tuple(DbAction.UpdateRoot.class, SingleReferenceEntity.class, "", SingleReferenceEntity.class, false), //
tuple(DbAction.Delete.class, Element.class, "other", null, false) //
tuple(DbAction.Delete.class, Element.class, "other", null, false) //
);
}
private List<DbAction<?>> extractActions(MutableAggregateChange<?> aggregateChange) {
List<DbAction<?>> actions = new ArrayList<>();
aggregateChange.forEachAction(actions::add);
return actions;
}
@RequiredArgsConstructor
static class SingleReferenceEntity {

View File

@@ -32,7 +32,6 @@ import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.data.annotation.Id;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.mapping.PersistentPropertyPaths;
import org.springframework.data.relational.core.conversion.AggregateChange.Kind;
import org.springframework.data.relational.core.conversion.DbAction.Delete;
import org.springframework.data.relational.core.conversion.DbAction.Insert;
import org.springframework.data.relational.core.conversion.DbAction.InsertRoot;
@@ -80,12 +79,12 @@ public class RelationalEntityWriterUnitTests {
public void newEntityGetsConvertedToOneInsert() {
SingleReferenceEntity entity = new SingleReferenceEntity(null);
AggregateChange<SingleReferenceEntity> aggregateChange = //
new AggregateChange<>(Kind.SAVE, SingleReferenceEntity.class, entity);
MutableAggregateChange<SingleReferenceEntity> aggregateChange = //
new MutableAggregateChange<>(AggregateChange.Kind.SAVE, SingleReferenceEntity.class, entity);
converter.write(entity, aggregateChange);
assertThat(aggregateChange.getActions()) //
assertThat(extractActions(aggregateChange)) //
.extracting(DbAction::getClass, //
DbAction::getEntityType, //
DbActionTestSupport::extractPath, //
@@ -102,12 +101,12 @@ public class RelationalEntityWriterUnitTests {
EmbeddedReferenceEntity entity = new EmbeddedReferenceEntity(null);
entity.other = new Element(2L);
AggregateChange<EmbeddedReferenceEntity> aggregateChange = //
new AggregateChange<>(Kind.SAVE, EmbeddedReferenceEntity.class, entity);
MutableAggregateChange<EmbeddedReferenceEntity> aggregateChange = //
new MutableAggregateChange<>(AggregateChange.Kind.SAVE, EmbeddedReferenceEntity.class, entity);
converter.write(entity, aggregateChange);
assertThat(aggregateChange.getActions()) //
assertThat(extractActions(aggregateChange)) //
.extracting(DbAction::getClass, //
DbAction::getEntityType, //
DbActionTestSupport::extractPath, //
@@ -124,12 +123,12 @@ public class RelationalEntityWriterUnitTests {
SingleReferenceEntity entity = new SingleReferenceEntity(null);
entity.other = new Element(null);
AggregateChange<SingleReferenceEntity> aggregateChange = //
new AggregateChange<>(Kind.SAVE, SingleReferenceEntity.class, entity);
MutableAggregateChange<SingleReferenceEntity> aggregateChange = //
new MutableAggregateChange<>(AggregateChange.Kind.SAVE, SingleReferenceEntity.class, entity);
converter.write(entity, aggregateChange);
assertThat(aggregateChange.getActions()) //
assertThat(extractActions(aggregateChange)) //
.extracting(DbAction::getClass, //
DbAction::getEntityType, //
DbActionTestSupport::extractPath, //
@@ -146,12 +145,12 @@ public class RelationalEntityWriterUnitTests {
SingleReferenceEntity entity = new SingleReferenceEntity(SOME_ENTITY_ID);
AggregateChange<SingleReferenceEntity> aggregateChange = //
new AggregateChange<>(Kind.SAVE, SingleReferenceEntity.class, entity);
MutableAggregateChange<SingleReferenceEntity> aggregateChange = //
new MutableAggregateChange<>(AggregateChange.Kind.SAVE, SingleReferenceEntity.class, entity);
converter.write(entity, aggregateChange);
assertThat(aggregateChange.getActions()) //
assertThat(extractActions(aggregateChange)) //
.extracting(DbAction::getClass, //
DbAction::getEntityType, //
DbActionTestSupport::extractPath, //
@@ -159,7 +158,7 @@ public class RelationalEntityWriterUnitTests {
DbActionTestSupport::isWithDependsOn) //
.containsExactly( //
tuple(UpdateRoot.class, SingleReferenceEntity.class, "", SingleReferenceEntity.class, false), //
tuple(Delete.class, Element.class, "other", null, false) //
tuple(Delete.class, Element.class, "other", null, false) //
);
}
@@ -169,12 +168,12 @@ public class RelationalEntityWriterUnitTests {
SingleReferenceEntity entity = new SingleReferenceEntity(SOME_ENTITY_ID);
entity.other = new Element(null);
AggregateChange<SingleReferenceEntity> aggregateChange = new AggregateChange<>(Kind.SAVE,
SingleReferenceEntity.class, entity);
MutableAggregateChange<SingleReferenceEntity> aggregateChange = new MutableAggregateChange<>(
AggregateChange.Kind.SAVE, SingleReferenceEntity.class, entity);
converter.write(entity, aggregateChange);
assertThat(aggregateChange.getActions()) //
assertThat(extractActions(aggregateChange)) //
.extracting(DbAction::getClass, //
DbAction::getEntityType, //
DbActionTestSupport::extractPath, //
@@ -191,12 +190,12 @@ public class RelationalEntityWriterUnitTests {
public void newEntityWithEmptySetResultsInSingleInsert() {
SetContainer entity = new SetContainer(null);
AggregateChange<RelationalEntityWriterUnitTests.SetContainer> aggregateChange = new AggregateChange<>(Kind.SAVE,
MutableAggregateChange<SetContainer> aggregateChange = new MutableAggregateChange<>(AggregateChange.Kind.SAVE,
SetContainer.class, entity);
converter.write(entity, aggregateChange);
assertThat(aggregateChange.getActions()) //
assertThat(extractActions(aggregateChange)) //
.extracting(DbAction::getClass, //
DbAction::getEntityType, //
DbActionTestSupport::extractPath, //
@@ -213,10 +212,11 @@ public class RelationalEntityWriterUnitTests {
entity.elements.add(new Element(null));
entity.elements.add(new Element(null));
AggregateChange<SetContainer> aggregateChange = new AggregateChange<>(Kind.SAVE, SetContainer.class, entity);
MutableAggregateChange<SetContainer> aggregateChange = new MutableAggregateChange<>(AggregateChange.Kind.SAVE,
SetContainer.class, entity);
converter.write(entity, aggregateChange);
assertThat(aggregateChange.getActions()).extracting(DbAction::getClass, //
assertThat(extractActions(aggregateChange)).extracting(DbAction::getClass, //
DbAction::getEntityType, //
DbActionTestSupport::extractPath, //
DbActionTestSupport::actualEntityType, //
@@ -243,12 +243,12 @@ public class RelationalEntityWriterUnitTests {
new Element(null)) //
);
AggregateChange<CascadingReferenceEntity> aggregateChange = new AggregateChange<>(Kind.SAVE,
CascadingReferenceEntity.class, entity);
MutableAggregateChange<CascadingReferenceEntity> aggregateChange = new MutableAggregateChange<>(
AggregateChange.Kind.SAVE, CascadingReferenceEntity.class, entity);
converter.write(entity, aggregateChange);
assertThat(aggregateChange.getActions()).extracting(DbAction::getClass, //
assertThat(extractActions(aggregateChange)).extracting(DbAction::getClass, //
DbAction::getEntityType, //
DbActionTestSupport::extractPath, //
DbActionTestSupport::actualEntityType, //
@@ -281,12 +281,12 @@ public class RelationalEntityWriterUnitTests {
new Element(null)) //
);
AggregateChange<CascadingReferenceEntity> aggregateChange = new AggregateChange<>(Kind.SAVE,
CascadingReferenceEntity.class, entity);
MutableAggregateChange<CascadingReferenceEntity> aggregateChange = new MutableAggregateChange<>(
AggregateChange.Kind.SAVE, CascadingReferenceEntity.class, entity);
converter.write(entity, aggregateChange);
assertThat(aggregateChange.getActions()).extracting(DbAction::getClass, //
assertThat(extractActions(aggregateChange)).extracting(DbAction::getClass, //
DbAction::getEntityType, //
DbActionTestSupport::extractPath, //
DbActionTestSupport::actualEntityType, //
@@ -310,11 +310,12 @@ public class RelationalEntityWriterUnitTests {
public void newEntityWithEmptyMapResultsInSingleInsert() {
MapContainer entity = new MapContainer(null);
AggregateChange<MapContainer> aggregateChange = new AggregateChange<>(Kind.SAVE, MapContainer.class, entity);
MutableAggregateChange<MapContainer> aggregateChange = new MutableAggregateChange<>(AggregateChange.Kind.SAVE,
MapContainer.class, entity);
converter.write(entity, aggregateChange);
assertThat(aggregateChange.getActions()).extracting(DbAction::getClass, //
assertThat(extractActions(aggregateChange)).extracting(DbAction::getClass, //
DbAction::getEntityType, //
DbActionTestSupport::extractPath) //
.containsExactly( //
@@ -328,10 +329,11 @@ public class RelationalEntityWriterUnitTests {
entity.elements.put("one", new Element(null));
entity.elements.put("two", new Element(null));
AggregateChange<MapContainer> aggregateChange = new AggregateChange<>(Kind.SAVE, MapContainer.class, entity);
MutableAggregateChange<MapContainer> aggregateChange = new MutableAggregateChange<>(AggregateChange.Kind.SAVE,
MapContainer.class, entity);
converter.write(entity, aggregateChange);
assertThat(aggregateChange.getActions()).extracting(DbAction::getClass, //
assertThat(extractActions(aggregateChange)).extracting(DbAction::getClass, //
DbAction::getEntityType, //
this::getMapKey, //
DbActionTestSupport::extractPath) //
@@ -366,10 +368,11 @@ public class RelationalEntityWriterUnitTests {
entity.elements.put("a", new Element(null));
entity.elements.put("b", new Element(null));
AggregateChange<MapContainer> aggregateChange = new AggregateChange<>(Kind.SAVE, MapContainer.class, entity);
MutableAggregateChange<MapContainer> aggregateChange = new MutableAggregateChange<>(AggregateChange.Kind.SAVE,
MapContainer.class, entity);
converter.write(entity, aggregateChange);
assertThat(aggregateChange.getActions()).extracting(DbAction::getClass, //
assertThat(extractActions(aggregateChange)).extracting(DbAction::getClass, //
DbAction::getEntityType, //
this::getMapKey, //
DbActionTestSupport::extractPath) //
@@ -394,11 +397,12 @@ public class RelationalEntityWriterUnitTests {
public void newEntityWithEmptyListResultsInSingleInsert() {
ListContainer entity = new ListContainer(null);
AggregateChange<ListContainer> aggregateChange = new AggregateChange<>(Kind.SAVE, ListContainer.class, entity);
MutableAggregateChange<ListContainer> aggregateChange = new MutableAggregateChange<>(AggregateChange.Kind.SAVE,
ListContainer.class, entity);
converter.write(entity, aggregateChange);
assertThat(aggregateChange.getActions()).extracting(DbAction::getClass, //
assertThat(extractActions(aggregateChange)).extracting(DbAction::getClass, //
DbAction::getEntityType, //
DbActionTestSupport::extractPath) //
.containsExactly( //
@@ -412,10 +416,11 @@ public class RelationalEntityWriterUnitTests {
entity.elements.add(new Element(null));
entity.elements.add(new Element(null));
AggregateChange<ListContainer> aggregateChange = new AggregateChange<>(Kind.SAVE, ListContainer.class, entity);
MutableAggregateChange<ListContainer> aggregateChange = new MutableAggregateChange<>(AggregateChange.Kind.SAVE,
ListContainer.class, entity);
converter.write(entity, aggregateChange);
assertThat(aggregateChange.getActions()).extracting(DbAction::getClass, //
assertThat(extractActions(aggregateChange)).extracting(DbAction::getClass, //
DbAction::getEntityType, //
this::getListKey, //
DbActionTestSupport::extractPath) //
@@ -438,11 +443,12 @@ public class RelationalEntityWriterUnitTests {
MapContainer entity = new MapContainer(SOME_ENTITY_ID);
entity.elements.put("one", new Element(null));
AggregateChange<MapContainer> aggregateChange = new AggregateChange<>(Kind.SAVE, MapContainer.class, entity);
MutableAggregateChange<MapContainer> aggregateChange = new MutableAggregateChange<>(AggregateChange.Kind.SAVE,
MapContainer.class, entity);
converter.write(entity, aggregateChange);
assertThat(aggregateChange.getActions()) //
assertThat(extractActions(aggregateChange)) //
.extracting(DbAction::getClass, //
DbAction::getEntityType, //
this::getMapKey, //
@@ -460,11 +466,12 @@ public class RelationalEntityWriterUnitTests {
ListContainer entity = new ListContainer(SOME_ENTITY_ID);
entity.elements.add(new Element(null));
AggregateChange<ListContainer> aggregateChange = new AggregateChange<>(Kind.SAVE, ListContainer.class, entity);
MutableAggregateChange<ListContainer> aggregateChange = new MutableAggregateChange<>(AggregateChange.Kind.SAVE,
ListContainer.class, entity);
converter.write(entity, aggregateChange);
assertThat(aggregateChange.getActions()) //
assertThat(extractActions(aggregateChange)) //
.extracting(DbAction::getClass, //
DbAction::getEntityType, //
this::getListKey, //
@@ -483,12 +490,12 @@ public class RelationalEntityWriterUnitTests {
listMapContainer.maps.add(new MapContainer(SOME_ENTITY_ID));
listMapContainer.maps.get(0).elements.put("one", new Element(null));
AggregateChange<ListMapContainer> aggregateChange = new AggregateChange<>(Kind.SAVE, ListMapContainer.class,
listMapContainer);
MutableAggregateChange<ListMapContainer> aggregateChange = new MutableAggregateChange<>(AggregateChange.Kind.SAVE,
ListMapContainer.class, listMapContainer);
converter.write(listMapContainer, aggregateChange);
assertThat(aggregateChange.getActions()) //
assertThat(extractActions(aggregateChange)) //
.extracting(DbAction::getClass, //
DbAction::getEntityType, //
a -> getQualifier(a, listMapContainerMaps), //
@@ -510,12 +517,12 @@ public class RelationalEntityWriterUnitTests {
listMapContainer.maps.add(new NoIdMapContainer());
listMapContainer.maps.get(0).elements.put("one", new NoIdElement());
AggregateChange<NoIdListMapContainer> aggregateChange = new AggregateChange<>(Kind.SAVE, NoIdListMapContainer.class,
listMapContainer);
MutableAggregateChange<NoIdListMapContainer> aggregateChange = new MutableAggregateChange<>(
AggregateChange.Kind.SAVE, NoIdListMapContainer.class, listMapContainer);
converter.write(listMapContainer, aggregateChange);
assertThat(aggregateChange.getActions()) //
assertThat(extractActions(aggregateChange)) //
.extracting(DbAction::getClass, //
DbAction::getEntityType, //
a -> getQualifier(a, noIdListMapContainerMaps), //
@@ -536,12 +543,12 @@ public class RelationalEntityWriterUnitTests {
EmbeddedReferenceChainEntity entity = new EmbeddedReferenceChainEntity(null);
// the embedded is null !!!
AggregateChange<EmbeddedReferenceChainEntity> aggregateChange = //
new AggregateChange<>(Kind.SAVE, EmbeddedReferenceChainEntity.class, entity);
MutableAggregateChange<EmbeddedReferenceChainEntity> aggregateChange = //
new MutableAggregateChange<>(AggregateChange.Kind.SAVE, EmbeddedReferenceChainEntity.class, entity);
converter.write(entity, aggregateChange);
assertThat(aggregateChange.getActions()) //
assertThat(extractActions(aggregateChange)) //
.extracting(DbAction::getClass, //
DbAction::getEntityType, //
DbActionTestSupport::extractPath, //
@@ -559,12 +566,12 @@ public class RelationalEntityWriterUnitTests {
root.other = new EmbeddedReferenceChainEntity(null);
// the embedded is null !!!
AggregateChange<RootWithEmbeddedReferenceChainEntity> aggregateChange = //
new AggregateChange<>(Kind.SAVE, RootWithEmbeddedReferenceChainEntity.class, root);
MutableAggregateChange<RootWithEmbeddedReferenceChainEntity> aggregateChange = //
new MutableAggregateChange<>(AggregateChange.Kind.SAVE, RootWithEmbeddedReferenceChainEntity.class, root);
converter.write(root, aggregateChange);
assertThat(aggregateChange.getActions()) //
assertThat(extractActions(aggregateChange)) //
.extracting(DbAction::getClass, //
DbAction::getEntityType, //
DbActionTestSupport::extractPath, //
@@ -577,6 +584,13 @@ public class RelationalEntityWriterUnitTests {
);
}
private List<DbAction<?>> extractActions(MutableAggregateChange<?> aggregateChange) {
List<DbAction<?>> actions = new ArrayList<>();
aggregateChange.forEachAction(actions::add);
return actions;
}
private CascadingReferenceMiddleElement createMiddleElement(Element first, Element second) {
CascadingReferenceMiddleElement middleElement1 = new CascadingReferenceMiddleElement(null);

View File

@@ -21,7 +21,7 @@ import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import org.springframework.data.relational.core.conversion.AggregateChange;
import org.springframework.data.relational.core.conversion.MutableAggregateChange;
/**
* Unit tests for {@link AbstractRelationalEventListener}.
@@ -46,7 +46,7 @@ public class AbstractRelationalEventListenerUnitTests {
@Test // DATAJDBC-454
public void beforeConvert() {
listener.onApplicationEvent(new BeforeConvertEvent<>(dummyEntity, AggregateChange.forDelete(dummyEntity)));
listener.onApplicationEvent(new BeforeConvertEvent<>(dummyEntity, MutableAggregateChange.forDelete(dummyEntity)));
assertThat(events).containsExactly("beforeConvert");
}
@@ -54,7 +54,7 @@ public class AbstractRelationalEventListenerUnitTests {
@Test // DATAJDBC-454
public void beforeSave() {
listener.onApplicationEvent(new BeforeSaveEvent<>(dummyEntity, AggregateChange.forSave(dummyEntity)));
listener.onApplicationEvent(new BeforeSaveEvent<>(dummyEntity, MutableAggregateChange.forSave(dummyEntity)));
assertThat(events).containsExactly("beforeSave");
}
@@ -62,7 +62,7 @@ public class AbstractRelationalEventListenerUnitTests {
@Test // DATAJDBC-454
public void afterSave() {
listener.onApplicationEvent(new AfterSaveEvent<>(dummyEntity, AggregateChange.forDelete(dummyEntity)));
listener.onApplicationEvent(new AfterSaveEvent<>(dummyEntity, MutableAggregateChange.forDelete(dummyEntity)));
assertThat(events).containsExactly("afterSave");
}
@@ -71,7 +71,7 @@ public class AbstractRelationalEventListenerUnitTests {
public void beforeDelete() {
listener.onApplicationEvent(
new BeforeDeleteEvent<>(Identifier.of(23), dummyEntity, AggregateChange.forDelete(dummyEntity)));
new BeforeDeleteEvent<>(Identifier.of(23), dummyEntity, MutableAggregateChange.forDelete(dummyEntity)));
assertThat(events).containsExactly("beforeDelete");
}
@@ -80,7 +80,7 @@ public class AbstractRelationalEventListenerUnitTests {
public void afterDelete() {
listener.onApplicationEvent(
new AfterDeleteEvent<>(Identifier.of(23), dummyEntity, AggregateChange.forDelete(dummyEntity)));
new AfterDeleteEvent<>(Identifier.of(23), dummyEntity, MutableAggregateChange.forDelete(dummyEntity)));
assertThat(events).containsExactly("afterDelete");
}
@@ -91,7 +91,7 @@ public class AbstractRelationalEventListenerUnitTests {
String notADummyEntity = "I'm not a dummy entity";
listener.onApplicationEvent(
new AfterDeleteEvent<>(Identifier.of(23), String.class, AggregateChange.forDelete(notADummyEntity)));
new AfterDeleteEvent<>(Identifier.of(23), String.class, MutableAggregateChange.forDelete(notADummyEntity)));
assertThat(events).isEmpty();
}