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:
committed by
Mark Paluch
parent
6a1ef7d69c
commit
51b6784579
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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}.
|
||||
|
||||
@@ -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.");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user