DATAJDBC-291 - Setting DB-generated ids now works across collections.
Original pull request: #173.
This commit is contained in:
committed by
Mark Paluch
parent
2a0729d7fe
commit
feb60c8560
@@ -97,6 +97,29 @@ public class ImmutableAggregateTemplateHsqlIntegrationTests {
|
||||
softly.assertAll();
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-291
|
||||
public void saveAndLoadAnEntityWithTwoReferencedEntitiesById() {
|
||||
|
||||
LegoSet saved = template.save(createLegoSet(createManual(), new Author(null, "Alfred E. Neumann")));
|
||||
|
||||
assertThat(saved.manual.id).describedAs("id of stored manual").isNotNull();
|
||||
assertThat(saved.author.id).describedAs("id of stored author").isNotNull();
|
||||
|
||||
LegoSet reloadedLegoSet = template.findById(saved.getId(), LegoSet.class);
|
||||
|
||||
assertThat(reloadedLegoSet.manual).isNotNull();
|
||||
|
||||
SoftAssertions softly = new SoftAssertions();
|
||||
|
||||
softly.assertThat(reloadedLegoSet.manual.getId()) //
|
||||
.isEqualTo(saved.getManual().getId()) //
|
||||
.isNotNull();
|
||||
softly.assertThat(reloadedLegoSet.manual.getContent()).isEqualTo(saved.getManual().getContent());
|
||||
softly.assertThat(reloadedLegoSet.author.getName()).isEqualTo(saved.getAuthor().getName());
|
||||
|
||||
softly.assertAll();
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-241
|
||||
public void saveAndLoadManyEntitiesWithReferencedEntity() {
|
||||
|
||||
@@ -168,7 +191,7 @@ public class ImmutableAggregateTemplateHsqlIntegrationTests {
|
||||
|
||||
LegoSet saved = template.save(createLegoSet(null));
|
||||
|
||||
LegoSet changedLegoSet = new LegoSet(saved.id, saved.name, new Manual(23L, "Some content"));
|
||||
LegoSet changedLegoSet = new LegoSet(saved.id, saved.name, new Manual(23L, "Some content"), null);
|
||||
|
||||
template.save(changedLegoSet);
|
||||
|
||||
@@ -182,7 +205,7 @@ public class ImmutableAggregateTemplateHsqlIntegrationTests {
|
||||
|
||||
LegoSet saved = template.save(createLegoSet(null));
|
||||
|
||||
LegoSet changedLegoSet = new LegoSet(saved.id, saved.name, null);
|
||||
LegoSet changedLegoSet = new LegoSet(saved.id, saved.name, null, null);
|
||||
|
||||
template.save(changedLegoSet);
|
||||
|
||||
@@ -201,7 +224,7 @@ public class ImmutableAggregateTemplateHsqlIntegrationTests {
|
||||
|
||||
LegoSet saved = template.save(createLegoSet(null));
|
||||
|
||||
LegoSet changedLegoSet = new LegoSet(saved.id, saved.name, new Manual(null, "other content"));
|
||||
LegoSet changedLegoSet = new LegoSet(saved.id, saved.name, new Manual(null, "other content"), null);
|
||||
|
||||
template.save(changedLegoSet);
|
||||
|
||||
@@ -233,7 +256,12 @@ public class ImmutableAggregateTemplateHsqlIntegrationTests {
|
||||
|
||||
private static LegoSet createLegoSet(Manual manual) {
|
||||
|
||||
return new LegoSet(null, "Star Destroyer", manual);
|
||||
return new LegoSet(null, "Star Destroyer", manual, null);
|
||||
}
|
||||
|
||||
private static LegoSet createLegoSet(Manual manual, Author author) {
|
||||
|
||||
return new LegoSet(null, "Star Destroyer", manual, author);
|
||||
}
|
||||
|
||||
private static Manual createManual() {
|
||||
@@ -248,6 +276,7 @@ public class ImmutableAggregateTemplateHsqlIntegrationTests {
|
||||
@Id Long id;
|
||||
String name;
|
||||
Manual manual;
|
||||
Author author;
|
||||
}
|
||||
|
||||
@Value
|
||||
@@ -258,6 +287,14 @@ public class ImmutableAggregateTemplateHsqlIntegrationTests {
|
||||
String content;
|
||||
}
|
||||
|
||||
@Value
|
||||
@Wither
|
||||
static class Author {
|
||||
|
||||
@Id Long id;
|
||||
String name;
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Import(TestConfiguration.class)
|
||||
static class Config {
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
CREATE TABLE LEGO_SET ( id BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, NAME VARCHAR(30));
|
||||
CREATE TABLE MANUAL ( id BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, LEGO_SET BIGINT, CONTENT VARCHAR(2000));
|
||||
CREATE TABLE LEGO_SET
|
||||
(
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY,
|
||||
NAME VARCHAR(30)
|
||||
);
|
||||
|
||||
ALTER TABLE MANUAL ADD FOREIGN KEY (LEGO_SET)
|
||||
REFERENCES LEGO_SET(id);
|
||||
CREATE TABLE MANUAL
|
||||
(
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY,
|
||||
LEGO_SET BIGINT,
|
||||
CONTENT VARCHAR(2000)
|
||||
);
|
||||
ALTER TABLE MANUAL
|
||||
ADD FOREIGN KEY (LEGO_SET)
|
||||
REFERENCES LEGO_SET (id);
|
||||
|
||||
CREATE TABLE AUTHOR
|
||||
(
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY,
|
||||
LEGO_SET BIGINT,
|
||||
NAME VARCHAR(2000)
|
||||
);
|
||||
ALTER TABLE AUTHOR
|
||||
ADD FOREIGN KEY (LEGO_SET)
|
||||
REFERENCES LEGO_SET (id);
|
||||
|
||||
@@ -18,16 +18,20 @@ package org.springframework.data.relational.core.conversion;
|
||||
import lombok.Getter;
|
||||
|
||||
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.Set;
|
||||
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
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.util.Pair;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -55,164 +59,108 @@ public class AggregateChange<T> {
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static void setIdOfNonRootEntity(RelationalMappingContext context, RelationalConverter converter,
|
||||
PersistentPropertyAccessor<?> propertyAccessor, DbAction.WithDependingOn<?> action, Object generatedId) {
|
||||
|
||||
PersistentPropertyPath<RelationalPersistentProperty> propertyPathToEntity = action.getPropertyPath();
|
||||
PersistentPropertyPathExtension extPath = new PersistentPropertyPathExtension(context, propertyPathToEntity);
|
||||
|
||||
RelationalPersistentProperty leafProperty = propertyPathToEntity.getRequiredLeafProperty();
|
||||
|
||||
Object currentPropertyValue = propertyAccessor.getProperty(propertyPathToEntity);
|
||||
Assert.notNull(currentPropertyValue, "Trying to set an ID for an element that does not exist");
|
||||
|
||||
if (leafProperty.isQualified()) {
|
||||
|
||||
Object keyObject = action.getQualifiers().get(propertyPathToEntity);
|
||||
|
||||
if (List.class.isAssignableFrom(leafProperty.getType())) {
|
||||
setIdInElementOfList(converter, action, generatedId, (List) currentPropertyValue, (int) keyObject);
|
||||
} else if (Map.class.isAssignableFrom(leafProperty.getType())) {
|
||||
setIdInElementOfMap(converter, action, generatedId, (Map) currentPropertyValue, keyObject);
|
||||
} else {
|
||||
throw new IllegalStateException("Can't handle " + currentPropertyValue);
|
||||
}
|
||||
} else if (leafProperty.isCollectionLike()) {
|
||||
|
||||
if (Set.class.isAssignableFrom(leafProperty.getType())) {
|
||||
setIdInElementOfSet(converter, action, generatedId, (Set) currentPropertyValue);
|
||||
} else {
|
||||
throw new IllegalStateException("Can't handle " + currentPropertyValue);
|
||||
}
|
||||
} else if (extPath.hasIdProperty()) {
|
||||
|
||||
RelationalPersistentProperty requiredIdProperty = context
|
||||
.getRequiredPersistentEntity(propertyPathToEntity.getRequiredLeafProperty().getActualType())
|
||||
.getRequiredIdProperty();
|
||||
|
||||
PersistentPropertyPath<RelationalPersistentProperty> pathToId = context.getPersistentPropertyPath(
|
||||
propertyPathToEntity.toDotPath() + '.' + requiredIdProperty.getName(),
|
||||
propertyPathToEntity.getBaseProperty().getOwner().getType());
|
||||
|
||||
propertyAccessor.setProperty(pathToId, generatedId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void setEntity(@Nullable T aggregateRoot) {
|
||||
entity = aggregateRoot;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T> void setIdInElementOfSet(RelationalConverter converter, DbAction.WithDependingOn<?> action,
|
||||
Object generatedId, Set<T> set) {
|
||||
|
||||
PersistentPropertyAccessor<?> intermediateAccessor = setId(converter, action, generatedId);
|
||||
|
||||
// this currently only works on the standard collections
|
||||
// no support for immutable collections, nor specialized ones.
|
||||
set.remove((T) action.getEntity());
|
||||
set.add((T) intermediateAccessor.getBean());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <K, V> void setIdInElementOfMap(RelationalConverter converter, DbAction.WithDependingOn<?> action,
|
||||
Object generatedId, Map<K, V> map, K keyObject) {
|
||||
|
||||
PersistentPropertyAccessor<?> intermediateAccessor = setId(converter, action, generatedId);
|
||||
|
||||
// this currently only works on the standard collections
|
||||
// no support for immutable collections, nor specialized ones.
|
||||
map.put(keyObject, (V) intermediateAccessor.getBean());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T> void setIdInElementOfList(RelationalConverter converter, DbAction.WithDependingOn<?> action,
|
||||
Object generatedId, List<T> list, int index) {
|
||||
|
||||
PersistentPropertyAccessor<?> intermediateAccessor = setId(converter, action, generatedId);
|
||||
|
||||
// this currently only works on the standard collections
|
||||
// no support for immutable collections, nor specialized ones.
|
||||
list.set(index, (T) intermediateAccessor.getBean());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the id of the entity referenced in the action and uses the {@link PersistentPropertyAccessor} used for that.
|
||||
*/
|
||||
private static <T> PersistentPropertyAccessor<T> setId(RelationalConverter converter,
|
||||
DbAction.WithDependingOn<T> action, Object generatedId) {
|
||||
|
||||
T originalElement = action.getEntity();
|
||||
|
||||
RelationalPersistentEntity<T> persistentEntity = (RelationalPersistentEntity<T>) converter.getMappingContext()
|
||||
.getRequiredPersistentEntity(action.getEntityType());
|
||||
PersistentPropertyAccessor<T> intermediateAccessor = converter.getPropertyAccessor(persistentEntity,
|
||||
originalElement);
|
||||
|
||||
RelationalPersistentProperty idProperty = persistentEntity.getIdProperty();
|
||||
if (idProperty != null) {
|
||||
intermediateAccessor.setProperty(idProperty, generatedId);
|
||||
}
|
||||
|
||||
return intermediateAccessor;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void executeWith(Interpreter interpreter, RelationalMappingContext context, RelationalConverter converter) {
|
||||
|
||||
RelationalPersistentEntity<T> persistentEntity = entity != null
|
||||
? (RelationalPersistentEntity<T>) context.getRequiredPersistentEntity(entity.getClass())
|
||||
: null;
|
||||
actions.forEach(action -> action.executeWith(interpreter));
|
||||
|
||||
PersistentPropertyAccessor<T> propertyAccessor = //
|
||||
persistentEntity != null //
|
||||
? converter.getPropertyAccessor(persistentEntity, entity) //
|
||||
: null;
|
||||
T newRoot = setGeneratedIds(context, converter);
|
||||
|
||||
actions.forEach(action -> {
|
||||
|
||||
action.executeWith(interpreter);
|
||||
|
||||
processGeneratedId(context, converter, persistentEntity, propertyAccessor, action);
|
||||
});
|
||||
|
||||
if (propertyAccessor != null) {
|
||||
entity = propertyAccessor.getBean();
|
||||
if (newRoot != null) {
|
||||
entity = newRoot;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
private T setGeneratedIds(RelationalMappingContext context, RelationalConverter converter) {
|
||||
|
||||
T newRoot = null;
|
||||
|
||||
// have the actions so that the inserts on the leaves come first.
|
||||
ArrayList<DbAction<?>> reverseActions = new ArrayList<>(actions);
|
||||
Collections.reverse(reverseActions);
|
||||
|
||||
CascadingValuesLookup cascadingValues = new CascadingValuesLookup();
|
||||
|
||||
for (DbAction<?> action : reverseActions) {
|
||||
|
||||
if (action instanceof DbAction.WithGeneratedId) {
|
||||
|
||||
DbAction.WithGeneratedId<?> withGeneratedId = (DbAction.WithGeneratedId<?>) action;
|
||||
Object generatedId = withGeneratedId.getGeneratedId();
|
||||
|
||||
Object newEntity = setIdAndCascadingProperties(context, converter, 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.add(insert.dependingOn, insert.propertyPath, newEntity,
|
||||
qualifier == null ? null : qualifier.getSecond());
|
||||
|
||||
} else if (action instanceof DbAction.InsertRoot) {
|
||||
newRoot = (T) newEntity;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newRoot;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <S> Object setIdAndCascadingProperties(RelationalMappingContext context, RelationalConverter converter,
|
||||
DbAction.WithGeneratedId<S> action, @Nullable Object generatedId, CascadingValuesLookup cascadingValues) {
|
||||
|
||||
S originalEntity = action.getEntity();
|
||||
|
||||
RelationalPersistentEntity<S> persistentEntity = (RelationalPersistentEntity<S>) context
|
||||
.getRequiredPersistentEntity(action.getEntityType());
|
||||
PersistentPropertyAccessor<S> propertyAccessor = converter.getPropertyAccessor(persistentEntity, originalEntity);
|
||||
|
||||
if (generatedId != null) {
|
||||
propertyAccessor.setProperty(persistentEntity.getRequiredIdProperty(), generatedId);
|
||||
}
|
||||
|
||||
// set values of changed immutables referenced by this entity
|
||||
Map<PersistentPropertyPath, Object> cascadingValue = cascadingValues.get(action);
|
||||
for (Map.Entry<PersistentPropertyPath, Object> pathValuePair : cascadingValue.entrySet()) {
|
||||
propertyAccessor.setProperty(getRelativePath(action, pathValuePair), pathValuePair.getValue());
|
||||
}
|
||||
|
||||
return propertyAccessor.getBean();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private PersistentPropertyPath getRelativePath(DbAction action,
|
||||
Map.Entry<PersistentPropertyPath, Object> pathValuePair) {
|
||||
|
||||
PersistentPropertyPath pathToValue = pathValuePair.getKey();
|
||||
|
||||
if (action instanceof DbAction.Insert) {
|
||||
return pathToValue.getExtensionForBaseOf(((DbAction.Insert) action).propertyPath);
|
||||
}
|
||||
|
||||
if (action instanceof DbAction.InsertRoot) {
|
||||
return pathToValue;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(String.format("DbAction of type %s is not supported.", action.getClass()));
|
||||
}
|
||||
|
||||
public void addAction(DbAction<?> action) {
|
||||
actions.add(action);
|
||||
}
|
||||
|
||||
private void processGeneratedId(RelationalMappingContext context, RelationalConverter converter,
|
||||
@Nullable RelationalPersistentEntity<T> persistentEntity,
|
||||
@Nullable PersistentPropertyAccessor<T> propertyAccessor, DbAction<?> action) {
|
||||
|
||||
if (!(action instanceof DbAction.WithGeneratedId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Assert.notNull(persistentEntity,
|
||||
"For statements triggering database side id generation a RelationalPersistentEntity must be provided.");
|
||||
Assert.notNull(propertyAccessor, "propertyAccessor must not be null");
|
||||
|
||||
Object generatedId = ((DbAction.WithGeneratedId<?>) action).getGeneratedId();
|
||||
|
||||
if (generatedId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (action instanceof DbAction.InsertRoot && action.getEntityType().equals(entityType)) {
|
||||
propertyAccessor.setProperty(persistentEntity.getRequiredIdProperty(), generatedId);
|
||||
} else if (action instanceof DbAction.WithDependingOn) {
|
||||
|
||||
setIdOfNonRootEntity(context, converter, propertyAccessor, (DbAction.WithDependingOn<?>) action, generatedId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The kind of action to be performed on an aggregate.
|
||||
*/
|
||||
@@ -228,4 +176,170 @@ public class AggregateChange<T> {
|
||||
*/
|
||||
DELETE
|
||||
}
|
||||
|
||||
/**
|
||||
* Gathers and holds information about immutable properties in an aggregate that need updating.
|
||||
*/
|
||||
private static class CascadingValuesLookup {
|
||||
|
||||
static final List<MultiValueAggregator> aggregators = Arrays.asList(new SetAggregator(), new MapAggregator(),
|
||||
new ListAggregator(), new SingleElementAggregator());
|
||||
|
||||
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 value The value to be set. Must not be {@literal null}.
|
||||
* @param qualifier If {@code path} is a qualified multivalued properties this parameter contains the qualifier. May
|
||||
* be {@literal null}.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> void add(DbAction<?> action, PersistentPropertyPath path, Object value, @Nullable Object qualifier) {
|
||||
|
||||
MultiValueAggregator<T> aggregator = getAggregatorFor(path);
|
||||
|
||||
Map<PersistentPropertyPath, Object> valuesForPath = this.values.get(action);
|
||||
if (valuesForPath == null) {
|
||||
valuesForPath = new HashMap<>();
|
||||
values.put(action, valuesForPath);
|
||||
}
|
||||
|
||||
T currentValue = (T) valuesForPath.get(path);
|
||||
if (currentValue == null) {
|
||||
currentValue = aggregator.createEmptyInstance();
|
||||
}
|
||||
|
||||
Object newValue = aggregator.add(currentValue, value, qualifier);
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
public Map<PersistentPropertyPath, Object> get(DbAction<?> action) {
|
||||
return values.getOrDefault(action, Collections.emptyMap());
|
||||
}
|
||||
}
|
||||
|
||||
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, Object value, @Nullable Object qualifier);
|
||||
|
||||
}
|
||||
|
||||
static private class SetAggregator implements MultiValueAggregator<Set> {
|
||||
|
||||
@Override
|
||||
public Class<Set> handledType() {
|
||||
return Set.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set createEmptyInstance() {
|
||||
return new HashSet();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Set add(@Nullable Set set, Object value, @Nullable Object qualifier) {
|
||||
|
||||
Assert.notNull(set, "Set must not be null");
|
||||
|
||||
set.add(value);
|
||||
return set;
|
||||
}
|
||||
}
|
||||
|
||||
static private class ListAggregator implements MultiValueAggregator<List> {
|
||||
|
||||
@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, Object value, @Nullable Object qualifier) {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
static private class MapAggregator implements MultiValueAggregator<Map> {
|
||||
|
||||
@Override
|
||||
public Class<Map> handledType() {
|
||||
return Map.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map createEmptyInstance() {
|
||||
return new HashMap();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Map add(@Nullable Map map, Object value, @Nullable Object qualifier) {
|
||||
|
||||
Assert.notNull(map, "Map must not be null.");
|
||||
|
||||
map.put(qualifier, value);
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
static private class SingleElementAggregator implements MultiValueAggregator<Object> {
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Object createEmptyInstance() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object add(@Nullable Object __null, Object value, @Nullable Object qualifier) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import java.util.Map;
|
||||
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.data.util.Pair;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
@@ -261,6 +262,24 @@ public interface DbAction<T> {
|
||||
*/
|
||||
Map<PersistentPropertyPath<RelationalPersistentProperty>, Object> getQualifiers();
|
||||
|
||||
@Nullable
|
||||
default Pair<PersistentPropertyPath<RelationalPersistentProperty>, Object> getQualifier() {
|
||||
Map<PersistentPropertyPath<RelationalPersistentProperty>, Object> qualifiers = getQualifiers();
|
||||
if (qualifiers.size() == 0)
|
||||
return null;
|
||||
|
||||
if (qualifiers.size() > 1) {
|
||||
throw new IllegalStateException("Can't handle more then on qualifier");
|
||||
}
|
||||
|
||||
Map.Entry<PersistentPropertyPath<RelationalPersistentProperty>, Object> entry = qualifiers.entrySet().iterator().next();
|
||||
if (entry.getValue() == null) {
|
||||
return null;
|
||||
}
|
||||
return Pair.of(entry.getKey(), entry.getValue());
|
||||
};
|
||||
|
||||
|
||||
@Override
|
||||
default Class<T> getEntityType() {
|
||||
return WithEntity.super.getEntityType();
|
||||
|
||||
@@ -0,0 +1,583 @@
|
||||
/*
|
||||
* Copyright 2018-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.relational.core.conversion;
|
||||
|
||||
import static java.util.Arrays.*;
|
||||
import static java.util.Collections.*;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Value;
|
||||
import lombok.experimental.Wither;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.assertj.core.api.SoftAssertions;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.mapping.PersistentPropertyPaths;
|
||||
import org.springframework.data.relational.core.mapping.Embedded;
|
||||
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} testing the setting of generated ids in aggregates consisting of immutable entities.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
* @author Myeonghyeon-Lee
|
||||
*/
|
||||
public class AggregateChangeIdGenerationImmutableUnitTests {
|
||||
|
||||
DummyEntity entity = new DummyEntity();
|
||||
Content content = new Content();
|
||||
Content content2 = new Content();
|
||||
Tag tag1 = new Tag("tag1");
|
||||
Tag tag2 = new Tag("tag2");
|
||||
Tag tag3 = new Tag("tag3");
|
||||
ContentNoId contentNoId = new ContentNoId();
|
||||
ContentNoId contentNoId2 = new ContentNoId();
|
||||
|
||||
RelationalMappingContext context = new RelationalMappingContext();
|
||||
RelationalConverter converter = new BasicRelationalConverter(context);
|
||||
|
||||
DbAction.WithEntity<?> rootInsert = new DbAction.InsertRoot<>(entity);
|
||||
|
||||
@Test // DATAJDBC-291
|
||||
public void singleRoot() {
|
||||
|
||||
AggregateChange<DummyEntity> aggregateChange = new AggregateChange<>(AggregateChange.Kind.SAVE, DummyEntity.class,
|
||||
entity);
|
||||
aggregateChange.addAction(rootInsert);
|
||||
|
||||
aggregateChange.executeWith(new IdSettingInterpreter(), context, converter);
|
||||
|
||||
entity = aggregateChange.getEntity();
|
||||
|
||||
assertThat(entity.rootId).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-291
|
||||
public void simpleReference() {
|
||||
|
||||
entity = entity.withSingle(content);
|
||||
|
||||
AggregateChange<DummyEntity> aggregateChange = new AggregateChange<>(AggregateChange.Kind.SAVE, DummyEntity.class,
|
||||
entity);
|
||||
aggregateChange.addAction(rootInsert);
|
||||
aggregateChange.addAction(createInsert("single", content, null));
|
||||
|
||||
aggregateChange.executeWith(new IdSettingInterpreter(), context, converter);
|
||||
|
||||
entity = aggregateChange.getEntity();
|
||||
|
||||
SoftAssertions.assertSoftly(softly -> {
|
||||
|
||||
softly.assertThat(entity.rootId).isEqualTo(1);
|
||||
softly.assertThat(entity.single.id).isEqualTo(2);
|
||||
});
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-291
|
||||
public void listReference() {
|
||||
|
||||
entity = entity.withContentList(asList(content, content2));
|
||||
|
||||
AggregateChange<DummyEntity> aggregateChange = new AggregateChange<>(AggregateChange.Kind.SAVE, DummyEntity.class,
|
||||
entity);
|
||||
aggregateChange.addAction(rootInsert);
|
||||
aggregateChange.addAction(createInsert("contentList", content, 0));
|
||||
aggregateChange.addAction(createInsert("contentList", content2, 1));
|
||||
|
||||
aggregateChange.executeWith(new IdSettingInterpreter(), context, converter);
|
||||
|
||||
entity = aggregateChange.getEntity();
|
||||
|
||||
SoftAssertions.assertSoftly(softly -> {
|
||||
|
||||
softly.assertThat(entity.rootId).isEqualTo(1);
|
||||
softly.assertThat(entity.contentList).extracting(c -> c.id).containsExactly(2, 3);
|
||||
});
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-291
|
||||
public void mapReference() {
|
||||
|
||||
entity = entity.withContentMap(createContentMap("a", content, "b", content2));
|
||||
|
||||
AggregateChange<DummyEntity> aggregateChange = new AggregateChange<>(AggregateChange.Kind.SAVE, DummyEntity.class,
|
||||
entity);
|
||||
aggregateChange.addAction(rootInsert);
|
||||
aggregateChange.addAction(createInsert("contentMap", content, "a"));
|
||||
aggregateChange.addAction(createInsert("contentMap", content2, "b"));
|
||||
|
||||
aggregateChange.executeWith(new IdSettingInterpreter(), context, converter);
|
||||
|
||||
entity = aggregateChange.getEntity();
|
||||
|
||||
assertThat(entity.rootId).isEqualTo(1);
|
||||
assertThat(entity.contentMap.values()).extracting(c -> c.id).containsExactly(2, 3);
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-291
|
||||
public void setIdForDeepReference() {
|
||||
|
||||
content = content.withSingle(tag1);
|
||||
entity = entity.withSingle(content);
|
||||
|
||||
DbAction.Insert<?> parentInsert = createInsert("single", content, null);
|
||||
DbAction.Insert<?> insert = createDeepInsert("single", tag1, null, parentInsert);
|
||||
|
||||
AggregateChange<DummyEntity> aggregateChange = new AggregateChange<>(AggregateChange.Kind.SAVE, DummyEntity.class,
|
||||
entity);
|
||||
aggregateChange.addAction(rootInsert);
|
||||
aggregateChange.addAction(parentInsert);
|
||||
aggregateChange.addAction(insert);
|
||||
|
||||
aggregateChange.executeWith(new IdSettingInterpreter(), context, converter);
|
||||
|
||||
entity = aggregateChange.getEntity();
|
||||
|
||||
assertThat(entity.rootId).isEqualTo(1);
|
||||
assertThat(entity.single.id).isEqualTo(2);
|
||||
assertThat(entity.single.single.id).isEqualTo(3);
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-291
|
||||
public void setIdForDeepReferenceElementList() {
|
||||
|
||||
content = content.withTagList(asList(tag1, tag2));
|
||||
entity = entity.withSingle(content);
|
||||
|
||||
DbAction.Insert<?> parentInsert = createInsert("single", content, null);
|
||||
DbAction.Insert<?> insert1 = createDeepInsert("tagList", tag1, 0, parentInsert);
|
||||
DbAction.Insert<?> insert2 = createDeepInsert("tagList", tag2, 1, parentInsert);
|
||||
|
||||
AggregateChange<DummyEntity> aggregateChange = new AggregateChange<>(AggregateChange.Kind.SAVE, DummyEntity.class,
|
||||
entity);
|
||||
aggregateChange.addAction(rootInsert);
|
||||
aggregateChange.addAction(parentInsert);
|
||||
aggregateChange.addAction(insert1);
|
||||
aggregateChange.addAction(insert2);
|
||||
|
||||
aggregateChange.executeWith(new IdSettingInterpreter(), context, converter);
|
||||
|
||||
entity = aggregateChange.getEntity();
|
||||
|
||||
SoftAssertions.assertSoftly(softly -> {
|
||||
|
||||
softly.assertThat(entity.rootId).isEqualTo(1);
|
||||
softly.assertThat(entity.single.id).isEqualTo(2);
|
||||
softly.assertThat(entity.single.tagList).extracting(t -> t.id).containsExactly(3, 4);
|
||||
});
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-291
|
||||
public void setIdForDeepElementSetElementSet() {
|
||||
|
||||
content = content.withTagSet(Stream.of(tag1, tag2).collect(Collectors.toSet()));
|
||||
entity = entity.withContentSet(singleton(content));
|
||||
|
||||
DbAction.Insert<?> parentInsert = createInsert("contentSet", content, null);
|
||||
DbAction.Insert<?> insert1 = createDeepInsert("tagSet", tag1, null, parentInsert);
|
||||
DbAction.Insert<?> insert2 = createDeepInsert("tagSet", tag2, null, parentInsert);
|
||||
|
||||
AggregateChange<DummyEntity> aggregateChange = new AggregateChange<>(AggregateChange.Kind.SAVE, DummyEntity.class,
|
||||
entity);
|
||||
aggregateChange.addAction(rootInsert);
|
||||
aggregateChange.addAction(parentInsert);
|
||||
aggregateChange.addAction(insert1);
|
||||
aggregateChange.addAction(insert2);
|
||||
|
||||
aggregateChange.executeWith(new IdSettingInterpreter(), context, converter);
|
||||
|
||||
entity = aggregateChange.getEntity();
|
||||
|
||||
SoftAssertions.assertSoftly(softly -> {
|
||||
|
||||
softly.assertThat(entity.rootId).isEqualTo(1);
|
||||
softly.assertThat(entity.contentSet) //
|
||||
.extracting(c -> c.id) //
|
||||
.containsExactly(2); //
|
||||
softly.assertThat(entity.contentSet.stream() //
|
||||
.flatMap(c -> c.tagSet.stream())) //
|
||||
.extracting(t -> t.id) //
|
||||
.containsExactlyInAnyOrder(3, 4); //
|
||||
});
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-291
|
||||
public void setIdForDeepElementListSingleReference() {
|
||||
|
||||
content = content.withSingle(tag1);
|
||||
content2 = content2.withSingle(tag2);
|
||||
entity = entity.withContentList(asList(content, content2));
|
||||
|
||||
DbAction.Insert<?> parentInsert1 = createInsert("contentList", content, 0);
|
||||
DbAction.Insert<?> parentInsert2 = createInsert("contentList", content2, 1);
|
||||
DbAction.Insert<?> insert1 = createDeepInsert("single", tag1, null, parentInsert1);
|
||||
DbAction.Insert<?> insert2 = createDeepInsert("single", tag2, null, parentInsert2);
|
||||
|
||||
AggregateChange<DummyEntity> aggregateChange = new AggregateChange<>(AggregateChange.Kind.SAVE, DummyEntity.class,
|
||||
entity);
|
||||
aggregateChange.addAction(rootInsert);
|
||||
aggregateChange.addAction(parentInsert1);
|
||||
aggregateChange.addAction(parentInsert2);
|
||||
aggregateChange.addAction(insert1);
|
||||
aggregateChange.addAction(insert2);
|
||||
|
||||
aggregateChange.executeWith(new IdSettingInterpreter(), context, converter);
|
||||
|
||||
entity = aggregateChange.getEntity();
|
||||
|
||||
SoftAssertions.assertSoftly(softly -> {
|
||||
|
||||
softly.assertThat(entity.rootId).isEqualTo(1);
|
||||
softly.assertThat(entity.contentList) //
|
||||
.extracting(c -> c.id, c -> c.single.id) //
|
||||
.containsExactly(tuple(2, 4), tuple(3, 5)); //
|
||||
});
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-291
|
||||
public void setIdForDeepElementListElementList() {
|
||||
|
||||
content = content.withTagList(singletonList(tag1));
|
||||
content2 = content2.withTagList(asList(tag2, tag3));
|
||||
entity = entity.withContentList(asList(content, content2));
|
||||
|
||||
DbAction.Insert<?> parentInsert1 = createInsert("contentList", content, 0);
|
||||
DbAction.Insert<?> parentInsert2 = createInsert("contentList", content2, 1);
|
||||
DbAction.Insert<?> insert1 = createDeepInsert("tagList", tag1, 0, parentInsert1);
|
||||
DbAction.Insert<?> insert2 = createDeepInsert("tagList", tag2, 0, parentInsert2);
|
||||
DbAction.Insert<?> insert3 = createDeepInsert("tagList", tag3, 1, parentInsert2);
|
||||
|
||||
AggregateChange<DummyEntity> aggregateChange = new AggregateChange<>(AggregateChange.Kind.SAVE, DummyEntity.class,
|
||||
entity);
|
||||
aggregateChange.addAction(rootInsert);
|
||||
aggregateChange.addAction(parentInsert1);
|
||||
aggregateChange.addAction(parentInsert2);
|
||||
aggregateChange.addAction(insert1);
|
||||
aggregateChange.addAction(insert2);
|
||||
aggregateChange.addAction(insert3);
|
||||
|
||||
aggregateChange.executeWith(new IdSettingInterpreter(), context, converter);
|
||||
|
||||
entity = aggregateChange.getEntity();
|
||||
|
||||
SoftAssertions.assertSoftly(softly -> {
|
||||
|
||||
softly.assertThat(entity.rootId).isEqualTo(1);
|
||||
softly.assertThat(entity.contentList) //
|
||||
.extracting(c -> c.id) //
|
||||
.containsExactly(2, 3); //
|
||||
softly.assertThat(entity.contentList.stream() //
|
||||
.flatMap(c -> c.tagList.stream()) //
|
||||
).extracting(t -> t.id) //
|
||||
.containsExactly(4, 5, 6); //
|
||||
});
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-291
|
||||
public void setIdForDeepElementMapElementMap() {
|
||||
|
||||
content = content.withTagMap(createTagMap("111", tag1, "222", tag2, "333", tag3));
|
||||
entity = entity.withContentMap(createContentMap("one", content, "two", content2));
|
||||
|
||||
DbAction.Insert<?> parentInsert1 = createInsert("contentMap", content, "one");
|
||||
DbAction.Insert<?> parentInsert2 = createInsert("contentMap", content2, "two");
|
||||
DbAction.Insert<?> insert1 = createDeepInsert("tagMap", tag1, "111", parentInsert1);
|
||||
DbAction.Insert<?> insert2 = createDeepInsert("tagMap", tag2, "222", parentInsert2);
|
||||
DbAction.Insert<?> insert3 = createDeepInsert("tagMap", tag3, "333", parentInsert2);
|
||||
|
||||
AggregateChange<DummyEntity> aggregateChange = new AggregateChange<>(AggregateChange.Kind.SAVE, DummyEntity.class,
|
||||
entity);
|
||||
aggregateChange.addAction(rootInsert);
|
||||
aggregateChange.addAction(parentInsert1);
|
||||
aggregateChange.addAction(parentInsert2);
|
||||
aggregateChange.addAction(insert1);
|
||||
aggregateChange.addAction(insert2);
|
||||
aggregateChange.addAction(insert3);
|
||||
|
||||
aggregateChange.executeWith(new IdSettingInterpreter(), context, converter);
|
||||
|
||||
entity = aggregateChange.getEntity();
|
||||
|
||||
SoftAssertions.assertSoftly(softly -> {
|
||||
|
||||
softly.assertThat(entity.rootId).isEqualTo(1);
|
||||
softly.assertThat(entity.contentMap.entrySet()) //
|
||||
.extracting(Map.Entry::getKey, e -> e.getValue().id) //
|
||||
.containsExactly(tuple("one", 2), tuple("two", 3)); //
|
||||
softly.assertThat(entity.contentMap.values().stream() //
|
||||
.flatMap(c -> c.tagMap.entrySet().stream())) //
|
||||
.extracting(Map.Entry::getKey, e -> e.getValue().id) //
|
||||
.containsExactly( //
|
||||
tuple("111", 4), //
|
||||
tuple("222", 5), //
|
||||
tuple("333", 6) //
|
||||
); //
|
||||
});
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-291
|
||||
public void setIdForDeepElementListSingleReferenceWithIntermittentNoId() {
|
||||
|
||||
contentNoId = contentNoId.withSingle(tag1);
|
||||
contentNoId2 = contentNoId2.withSingle(tag2);
|
||||
entity = entity.withContentNoIdList(asList(contentNoId, contentNoId2));
|
||||
|
||||
DbAction.Insert<?> parentInsert1 = createInsert("contentNoIdList", contentNoId, 0);
|
||||
DbAction.Insert<?> parentInsert2 = createInsert("contentNoIdList", contentNoId2, 1);
|
||||
DbAction.Insert<?> insert1 = createDeepInsert("single", tag1, null, parentInsert1);
|
||||
DbAction.Insert<?> insert2 = createDeepInsert("single", tag2, null, parentInsert2);
|
||||
|
||||
AggregateChange<DummyEntity> aggregateChange = new AggregateChange<>(AggregateChange.Kind.SAVE, DummyEntity.class,
|
||||
entity);
|
||||
aggregateChange.addAction(rootInsert);
|
||||
aggregateChange.addAction(parentInsert1);
|
||||
aggregateChange.addAction(parentInsert2);
|
||||
aggregateChange.addAction(insert1);
|
||||
aggregateChange.addAction(insert2);
|
||||
|
||||
aggregateChange.executeWith(new IdSettingInterpreter(), context, converter);
|
||||
|
||||
entity = aggregateChange.getEntity();
|
||||
|
||||
SoftAssertions.assertSoftly(softly -> {
|
||||
|
||||
softly.assertThat(entity.rootId).isEqualTo(1);
|
||||
softly.assertThat(entity.contentNoIdList) //
|
||||
.extracting(c -> c.single.id) //
|
||||
.containsExactly(2, 3); //
|
||||
});
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-291
|
||||
public void setIdForEmbeddedDeepReference() {
|
||||
|
||||
contentNoId = contentNoId2.withSingle(tag1);
|
||||
entity = entity.withEmbedded(contentNoId);
|
||||
|
||||
DbAction.Insert<?> parentInsert = createInsert("embedded.single", tag1, null);
|
||||
|
||||
AggregateChange<DummyEntity> aggregateChange = new AggregateChange<>(AggregateChange.Kind.SAVE, DummyEntity.class,
|
||||
entity);
|
||||
aggregateChange.addAction(rootInsert);
|
||||
aggregateChange.addAction(parentInsert);
|
||||
|
||||
aggregateChange.executeWith(new IdSettingInterpreter(), context, converter);
|
||||
|
||||
entity = aggregateChange.getEntity();
|
||||
|
||||
assertThat(entity.rootId).isEqualTo(1);
|
||||
assertThat(entity.embedded.single.id).isEqualTo(2);
|
||||
}
|
||||
|
||||
private static Map<String, Content> createContentMap(Object... keysAndValues) {
|
||||
|
||||
Map<String, Content> contentMap = new HashMap<>();
|
||||
|
||||
for (int i = 0; i < keysAndValues.length; i += 2) {
|
||||
contentMap.put((String) keysAndValues[i], (Content) keysAndValues[i + 1]);
|
||||
}
|
||||
return unmodifiableMap(contentMap);
|
||||
}
|
||||
|
||||
private static Map<String, Tag> createTagMap(Object... keysAndValues) {
|
||||
|
||||
Map<String, Tag> contentMap = new HashMap<>();
|
||||
|
||||
for (int i = 0; i < keysAndValues.length; i += 2) {
|
||||
contentMap.put((String) keysAndValues[i], (Tag) keysAndValues[i + 1]);
|
||||
}
|
||||
return unmodifiableMap(contentMap);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
DbAction.Insert<?> createDeepInsert(String propertyName, Object value, Object key,
|
||||
@Nullable DbAction.Insert<?> parentInsert) {
|
||||
|
||||
DbAction.Insert<Object> insert = new DbAction.Insert<>(value, toPath(entity, value), parentInsert);
|
||||
insert.getQualifiers().put(toPath(parentInsert.getPropertyPath().toDotPath() + "." + propertyName), key);
|
||||
return insert;
|
||||
}
|
||||
|
||||
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"));
|
||||
}
|
||||
|
||||
PersistentPropertyPath<RelationalPersistentProperty> toPath(DummyEntity root, Object pathValue) {
|
||||
// DefaultPersistentPropertyPath is package-public
|
||||
return new WritingContext(context, entity,
|
||||
new AggregateChange<>(AggregateChange.Kind.SAVE, DummyEntity.class, root)).insert().stream()
|
||||
.filter(a -> a instanceof DbAction.Insert).map(DbAction.Insert.class::cast)
|
||||
.filter(a -> a.getEntity() == pathValue).map(DbAction.Insert::getPropertyPath).findFirst()
|
||||
.orElseThrow(() -> new IllegalArgumentException("No matching path found for " + pathValue));
|
||||
}
|
||||
|
||||
@Value
|
||||
@Wither
|
||||
@AllArgsConstructor
|
||||
private static class DummyEntity {
|
||||
|
||||
@Id Integer rootId;
|
||||
Content single;
|
||||
Set<Content> contentSet;
|
||||
List<Content> contentList;
|
||||
Map<String, Content> contentMap;
|
||||
List<ContentNoId> contentNoIdList;
|
||||
@Embedded(onEmpty = Embedded.OnEmpty.USE_NULL) ContentNoId embedded;
|
||||
|
||||
DummyEntity() {
|
||||
|
||||
rootId = null;
|
||||
single = null;
|
||||
contentSet = emptySet();
|
||||
contentList = emptyList();
|
||||
contentMap = emptyMap();
|
||||
contentNoIdList = emptyList();
|
||||
embedded = new ContentNoId();
|
||||
}
|
||||
}
|
||||
|
||||
@Value
|
||||
@Wither
|
||||
@AllArgsConstructor
|
||||
private static class Content {
|
||||
|
||||
@Id Integer id;
|
||||
Tag single;
|
||||
Set<Tag> tagSet;
|
||||
List<Tag> tagList;
|
||||
Map<String, Tag> tagMap;
|
||||
|
||||
Content() {
|
||||
|
||||
id = null;
|
||||
single = null;
|
||||
tagSet = emptySet();
|
||||
tagList = emptyList();
|
||||
tagMap = emptyMap();
|
||||
}
|
||||
}
|
||||
|
||||
@Value
|
||||
@Wither
|
||||
@AllArgsConstructor
|
||||
private static class ContentNoId {
|
||||
|
||||
Tag single;
|
||||
Set<Tag> tagSet;
|
||||
List<Tag> tagList;
|
||||
Map<String, Tag> tagMap;
|
||||
|
||||
ContentNoId() {
|
||||
|
||||
single = null;
|
||||
tagSet = emptySet();
|
||||
tagList = emptyList();
|
||||
tagMap = emptyMap();
|
||||
}
|
||||
}
|
||||
|
||||
@Value
|
||||
@Wither
|
||||
@AllArgsConstructor
|
||||
private static class Tag {
|
||||
|
||||
@Id Integer id;
|
||||
|
||||
String name;
|
||||
|
||||
Tag(String name) {
|
||||
id = null;
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,437 @@
|
||||
/*
|
||||
* Copyright 2018-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.relational.core.conversion;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.assertj.core.api.SoftAssertions;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.mapping.PersistentPropertyPaths;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Unit tests for the {@link AggregateChange}.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
* Myeonghyeon-Lee
|
||||
*/
|
||||
public class AggregateChangeIdGenerationUnitTests {
|
||||
|
||||
DummyEntity entity = new DummyEntity();
|
||||
Content content = new Content();
|
||||
Content content2 = new Content();
|
||||
Tag tag1 = new Tag();
|
||||
Tag tag2 = new Tag();
|
||||
Tag tag3 = new Tag();
|
||||
|
||||
RelationalMappingContext context = new RelationalMappingContext();
|
||||
RelationalConverter converter = new BasicRelationalConverter(context);
|
||||
|
||||
DbAction.WithEntity<?> rootInsert = new DbAction.InsertRoot<>(entity);
|
||||
|
||||
@Test // DATAJDBC-291
|
||||
public void singleRoot() {
|
||||
|
||||
AggregateChange<DummyEntity> aggregateChange = new AggregateChange<>(AggregateChange.Kind.SAVE, DummyEntity.class,
|
||||
entity);
|
||||
aggregateChange.addAction(rootInsert);
|
||||
|
||||
aggregateChange.executeWith(new IdSettingInterpreter(), context, converter);
|
||||
|
||||
assertThat(entity.rootId).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-291
|
||||
public void simpleReference() {
|
||||
|
||||
entity.single = content;
|
||||
|
||||
AggregateChange<DummyEntity> aggregateChange = new AggregateChange<>(AggregateChange.Kind.SAVE, DummyEntity.class,
|
||||
entity);
|
||||
aggregateChange.addAction(rootInsert);
|
||||
aggregateChange.addAction(createInsert("single", content, null));
|
||||
|
||||
aggregateChange.executeWith(new IdSettingInterpreter(), context, converter);
|
||||
|
||||
SoftAssertions.assertSoftly(softly -> {
|
||||
|
||||
softly.assertThat(entity.rootId).isEqualTo(1);
|
||||
softly.assertThat(entity.single.id).isEqualTo(2);
|
||||
});
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-291
|
||||
public void listReference() {
|
||||
|
||||
entity.contentList.add(content);
|
||||
entity.contentList.add(content2);
|
||||
|
||||
AggregateChange<DummyEntity> aggregateChange = new AggregateChange<>(AggregateChange.Kind.SAVE, DummyEntity.class,
|
||||
entity);
|
||||
aggregateChange.addAction(rootInsert);
|
||||
aggregateChange.addAction(createInsert("contentList", content, 0));
|
||||
aggregateChange.addAction(createInsert("contentList", content2, 1));
|
||||
|
||||
aggregateChange.executeWith(new IdSettingInterpreter(), context, converter);
|
||||
|
||||
SoftAssertions.assertSoftly(softly -> {
|
||||
|
||||
softly.assertThat(entity.rootId).isEqualTo(1);
|
||||
softly.assertThat(entity.contentList).extracting(c -> c.id).containsExactly(2, 3);
|
||||
});
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-291
|
||||
public void mapReference() {
|
||||
|
||||
entity.contentMap.put("a", content);
|
||||
entity.contentMap.put("b", content2);
|
||||
|
||||
AggregateChange<DummyEntity> aggregateChange = new AggregateChange<>(AggregateChange.Kind.SAVE, DummyEntity.class,
|
||||
entity);
|
||||
aggregateChange.addAction(rootInsert);
|
||||
aggregateChange.addAction(createInsert("contentMap", content, "a"));
|
||||
aggregateChange.addAction(createInsert("contentMap", content2, "b"));
|
||||
|
||||
aggregateChange.executeWith(new IdSettingInterpreter(), context, converter);
|
||||
|
||||
assertThat(entity.rootId).isEqualTo(1);
|
||||
assertThat(entity.contentMap.values()).extracting(c -> c.id).containsExactly(2, 3);
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-291
|
||||
public void setIdForDeepReference() {
|
||||
|
||||
content.single = tag1;
|
||||
entity.single = content;
|
||||
|
||||
DbAction.Insert<?> parentInsert = createInsert("single", content, null);
|
||||
DbAction.Insert<?> insert = createDeepInsert("single", tag1, null, parentInsert);
|
||||
|
||||
AggregateChange<DummyEntity> aggregateChange = new AggregateChange<>(AggregateChange.Kind.SAVE, DummyEntity.class,
|
||||
entity);
|
||||
aggregateChange.addAction(rootInsert);
|
||||
aggregateChange.addAction(parentInsert);
|
||||
aggregateChange.addAction(insert);
|
||||
|
||||
aggregateChange.executeWith(new IdSettingInterpreter(), context, converter);
|
||||
|
||||
assertThat(entity.rootId).isEqualTo(1);
|
||||
assertThat(entity.single.id).isEqualTo(2);
|
||||
assertThat(entity.single.single.id).isEqualTo(3);
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-291
|
||||
public void setIdForDeepReferenceElementList() {
|
||||
|
||||
content.tagList.add(tag1);
|
||||
content.tagList.add(tag2);
|
||||
entity.single = content;
|
||||
|
||||
DbAction.Insert<?> parentInsert = createInsert("single", content, null);
|
||||
DbAction.Insert<?> insert1 = createDeepInsert("tagList", tag1, 0, parentInsert);
|
||||
DbAction.Insert<?> insert2 = createDeepInsert("tagList", tag2, 1, parentInsert);
|
||||
|
||||
AggregateChange<DummyEntity> aggregateChange = new AggregateChange<>(AggregateChange.Kind.SAVE, DummyEntity.class,
|
||||
entity);
|
||||
aggregateChange.addAction(rootInsert);
|
||||
aggregateChange.addAction(parentInsert);
|
||||
aggregateChange.addAction(insert1);
|
||||
aggregateChange.addAction(insert2);
|
||||
|
||||
aggregateChange.executeWith(new IdSettingInterpreter(), context, converter);
|
||||
|
||||
SoftAssertions.assertSoftly(softly -> {
|
||||
|
||||
softly.assertThat(entity.rootId).isEqualTo(1);
|
||||
softly.assertThat(entity.single.id).isEqualTo(2);
|
||||
softly.assertThat(entity.single.tagList).extracting(t -> t.id).containsExactly(3, 4);
|
||||
});
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-291
|
||||
public void setIdForDeepElementSetElementSet() {
|
||||
|
||||
content.tagSet.add(tag1);
|
||||
content.tagSet.add(tag2);
|
||||
entity.contentSet.add(content);
|
||||
|
||||
DbAction.Insert<?> parentInsert = createInsert("contentSet", content, null);
|
||||
DbAction.Insert<?> insert1 = createDeepInsert("tagSet", tag1, null, parentInsert);
|
||||
DbAction.Insert<?> insert2 = createDeepInsert("tagSet", tag2, null, parentInsert);
|
||||
|
||||
AggregateChange<DummyEntity> aggregateChange = new AggregateChange<>(AggregateChange.Kind.SAVE, DummyEntity.class,
|
||||
entity);
|
||||
aggregateChange.addAction(rootInsert);
|
||||
aggregateChange.addAction(parentInsert);
|
||||
aggregateChange.addAction(insert1);
|
||||
aggregateChange.addAction(insert2);
|
||||
|
||||
aggregateChange.executeWith(new IdSettingInterpreter(), context, converter);
|
||||
|
||||
SoftAssertions.assertSoftly(softly -> {
|
||||
|
||||
softly.assertThat(entity.rootId).isEqualTo(1);
|
||||
softly.assertThat(entity.contentSet) //
|
||||
.extracting(c -> content.id) //
|
||||
.containsExactly(2); //
|
||||
softly.assertThat(entity.contentSet.stream() //
|
||||
.flatMap(c -> c.tagSet.stream())) //
|
||||
.extracting(t -> t.id) //
|
||||
.containsExactlyInAnyOrder(3, 4); //
|
||||
});
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-291
|
||||
public void setIdForDeepElementListSingleReference() {
|
||||
|
||||
content.single = tag1;
|
||||
content2.single = tag2;
|
||||
entity.contentList.add(content);
|
||||
entity.contentList.add(content2);
|
||||
|
||||
DbAction.Insert<?> parentInsert1 = createInsert("contentList", content, 0);
|
||||
DbAction.Insert<?> parentInsert2 = createInsert("contentList", content2, 1);
|
||||
DbAction.Insert<?> insert1 = createDeepInsert("single", tag1, null, parentInsert1);
|
||||
DbAction.Insert<?> insert2 = createDeepInsert("single", tag2, null, parentInsert2);
|
||||
|
||||
AggregateChange<DummyEntity> aggregateChange = new AggregateChange<>(AggregateChange.Kind.SAVE, DummyEntity.class,
|
||||
entity);
|
||||
aggregateChange.addAction(rootInsert);
|
||||
aggregateChange.addAction(parentInsert1);
|
||||
aggregateChange.addAction(parentInsert2);
|
||||
aggregateChange.addAction(insert1);
|
||||
aggregateChange.addAction(insert2);
|
||||
|
||||
aggregateChange.executeWith(new IdSettingInterpreter(), context, converter);
|
||||
|
||||
SoftAssertions.assertSoftly(softly -> {
|
||||
|
||||
softly.assertThat(entity.rootId).isEqualTo(1);
|
||||
softly.assertThat(entity.contentList) //
|
||||
.extracting(c -> c.id, c -> c.single.id) //
|
||||
.containsExactly(tuple(2, 4), tuple(3, 5)); //
|
||||
});
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-291
|
||||
public void setIdForDeepElementListElementList() {
|
||||
|
||||
content.tagList.add(tag1);
|
||||
content2.tagList.add(tag2);
|
||||
content2.tagList.add(tag3);
|
||||
entity.contentList.add(content);
|
||||
entity.contentList.add(content2);
|
||||
|
||||
DbAction.Insert<?> parentInsert1 = createInsert("contentList", content, 0);
|
||||
DbAction.Insert<?> parentInsert2 = createInsert("contentList", content2, 1);
|
||||
DbAction.Insert<?> insert1 = createDeepInsert("tagList", tag1, 0, parentInsert1);
|
||||
DbAction.Insert<?> insert2 = createDeepInsert("tagList", tag2, 0, parentInsert2);
|
||||
DbAction.Insert<?> insert3 = createDeepInsert("tagList", tag3, 1, parentInsert2);
|
||||
|
||||
AggregateChange<DummyEntity> aggregateChange = new AggregateChange<>(AggregateChange.Kind.SAVE, DummyEntity.class,
|
||||
entity);
|
||||
aggregateChange.addAction(rootInsert);
|
||||
aggregateChange.addAction(parentInsert1);
|
||||
aggregateChange.addAction(parentInsert2);
|
||||
aggregateChange.addAction(insert1);
|
||||
aggregateChange.addAction(insert2);
|
||||
aggregateChange.addAction(insert3);
|
||||
|
||||
aggregateChange.executeWith(new IdSettingInterpreter(), context, converter);
|
||||
|
||||
SoftAssertions.assertSoftly(softly -> {
|
||||
|
||||
softly.assertThat(entity.rootId).isEqualTo(1);
|
||||
softly.assertThat(entity.contentList) //
|
||||
.extracting(c -> c.id) //
|
||||
.containsExactly(2, 3); //
|
||||
softly.assertThat(entity.contentList.stream() //
|
||||
.flatMap(c -> c.tagList.stream()) //
|
||||
).extracting(t -> t.id) //
|
||||
.containsExactly(4, 5, 6); //
|
||||
});
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-291
|
||||
public void setIdForDeepElementMapElementMap() {
|
||||
|
||||
content.tagMap.put("111", tag1);
|
||||
content2.tagMap.put("222", tag2);
|
||||
content2.tagMap.put("333", tag3);
|
||||
entity.contentMap.put("one", content);
|
||||
entity.contentMap.put("two", content2);
|
||||
|
||||
DbAction.Insert<?> parentInsert1 = createInsert("contentMap", content, "one");
|
||||
DbAction.Insert<?> parentInsert2 = createInsert("contentMap", content2, "two");
|
||||
DbAction.Insert<?> insert1 = createDeepInsert("tagMap", tag1, "111", parentInsert1);
|
||||
DbAction.Insert<?> insert2 = createDeepInsert("tagMap", tag2, "222", parentInsert2);
|
||||
DbAction.Insert<?> insert3 = createDeepInsert("tagMap", tag3, "333", parentInsert2);
|
||||
|
||||
AggregateChange<DummyEntity> aggregateChange = new AggregateChange<>(AggregateChange.Kind.SAVE, DummyEntity.class,
|
||||
entity);
|
||||
aggregateChange.addAction(rootInsert);
|
||||
aggregateChange.addAction(parentInsert1);
|
||||
aggregateChange.addAction(parentInsert2);
|
||||
aggregateChange.addAction(insert1);
|
||||
aggregateChange.addAction(insert2);
|
||||
aggregateChange.addAction(insert3);
|
||||
|
||||
aggregateChange.executeWith(new IdSettingInterpreter(), context, converter);
|
||||
|
||||
SoftAssertions.assertSoftly(softly -> {
|
||||
|
||||
softly.assertThat(entity.rootId).isEqualTo(1);
|
||||
softly.assertThat(entity.contentMap.entrySet()) //
|
||||
.extracting(Map.Entry::getKey, e -> e.getValue().id) //
|
||||
.containsExactly(tuple("one", 2), tuple("two", 3)); //
|
||||
softly.assertThat(entity.contentMap.values().stream() //
|
||||
.flatMap(c -> c.tagMap.entrySet().stream())) //
|
||||
.extracting(Map.Entry::getKey, e -> e.getValue().id) //
|
||||
.containsExactly( //
|
||||
tuple("111", 4), //
|
||||
tuple("222", 5), //
|
||||
tuple("333", 6) //
|
||||
); //
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
DbAction.Insert<?> createDeepInsert(String propertyName, Object value, Object key,
|
||||
@Nullable DbAction.Insert<?> parentInsert) {
|
||||
|
||||
DbAction.Insert<Object> insert = new DbAction.Insert<>(value, toPath(entity, value), parentInsert);
|
||||
insert.getQualifiers().put(toPath(parentInsert.getPropertyPath().toDotPath() + "." + propertyName), key);
|
||||
return insert;
|
||||
}
|
||||
|
||||
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"));
|
||||
}
|
||||
|
||||
PersistentPropertyPath<RelationalPersistentProperty> toPath(DummyEntity root, Object pathValue) {
|
||||
// DefaultPersistentPropertyPath is package-public
|
||||
return new WritingContext(context, entity,
|
||||
new AggregateChange<>(AggregateChange.Kind.SAVE, DummyEntity.class, root)).insert().stream()
|
||||
.filter(a -> a instanceof DbAction.Insert).map(DbAction.Insert.class::cast)
|
||||
.filter(a -> a.getEntity() == pathValue).map(DbAction.Insert::getPropertyPath).findFirst()
|
||||
.orElseThrow(() -> new IllegalArgumentException("No matching path found"));
|
||||
}
|
||||
|
||||
private static class DummyEntity {
|
||||
|
||||
@Id Integer rootId;
|
||||
|
||||
Content single;
|
||||
|
||||
Set<Content> contentSet = new HashSet<>();
|
||||
|
||||
List<Content> contentList = new ArrayList<>();
|
||||
|
||||
Map<String, Content> contentMap = new HashMap<>();
|
||||
}
|
||||
|
||||
private static class Content {
|
||||
|
||||
@Id Integer id;
|
||||
|
||||
Tag single;
|
||||
|
||||
Set<Tag> tagSet = new HashSet<>();
|
||||
|
||||
List<Tag> tagList = new ArrayList<>();
|
||||
|
||||
Map<String, Tag> tagMap = new HashMap<>();
|
||||
}
|
||||
|
||||
private static class Tag {
|
||||
@Id Integer id;
|
||||
}
|
||||
|
||||
private static class IdSettingInterpreter implements Interpreter {
|
||||
int id = 0;
|
||||
|
||||
@Override
|
||||
public <T> void interpret(DbAction.Insert<T> insert) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,266 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.relational.core.conversion;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.mapping.PersistentPropertyPaths;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
|
||||
/**
|
||||
* Unit tests for the {@link AggregateChange}.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
public class AggregateChangeUnitTests {
|
||||
|
||||
DummyEntity entity = new DummyEntity();
|
||||
Content content = new Content();
|
||||
Tag tag = new Tag();
|
||||
|
||||
RelationalMappingContext context = new RelationalMappingContext();
|
||||
RelationalConverter converter = new BasicRelationalConverter(context);
|
||||
|
||||
PersistentPropertyAccessor<DummyEntity> propertyAccessor = context.getRequiredPersistentEntity(DummyEntity.class)
|
||||
.getPropertyAccessor(entity);
|
||||
PersistentPropertyAccessor<Content> contentPropertyAccessor = context.getRequiredPersistentEntity(Content.class)
|
||||
.getPropertyAccessor(content);
|
||||
Object id = 23;
|
||||
|
||||
DbAction.WithEntity<?> rootInsert = new DbAction.InsertRoot<>(entity);
|
||||
|
||||
DbAction.Insert<?> createInsert(String propertyName, Object value, Object key) {
|
||||
|
||||
DbAction.Insert<Object> insert = new DbAction.Insert<>(value,
|
||||
context.getPersistentPropertyPath(propertyName, DummyEntity.class), rootInsert);
|
||||
insert.getQualifiers().put(toPath(propertyName, DummyEntity.class), key);
|
||||
|
||||
return insert;
|
||||
}
|
||||
|
||||
DbAction.Insert<?> createDeepInsert(String propertyName, Object value, Object key, DbAction.Insert<?> parentInsert) {
|
||||
DbAction.Insert<Object> insert = new DbAction.Insert<>(value, toPath(entity, value), parentInsert);
|
||||
insert.getQualifiers().put(toPath(parentInsert.getPropertyPath().toDotPath() + "." + propertyName, DummyEntity.class), key);
|
||||
return insert;
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-241
|
||||
public void setIdForSimpleReference() {
|
||||
|
||||
entity.single = content;
|
||||
|
||||
DbAction.Insert<?> insert = createInsert("single", content, null);
|
||||
|
||||
AggregateChange.setIdOfNonRootEntity(context, converter, propertyAccessor, insert, id);
|
||||
|
||||
DummyEntity result = propertyAccessor.getBean();
|
||||
|
||||
assertThat(result.single.id).isEqualTo(id);
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-241
|
||||
public void setIdForSingleElementSet() {
|
||||
|
||||
entity.contentSet.add(content);
|
||||
|
||||
DbAction.Insert<?> insert = createInsert("contentSet", content, null);
|
||||
|
||||
AggregateChange.setIdOfNonRootEntity(context, converter, propertyAccessor, insert, id);
|
||||
|
||||
DummyEntity result = propertyAccessor.getBean();
|
||||
assertThat(result.contentSet).isNotNull();
|
||||
assertThat(result.contentSet).extracting(c -> c == null ? "null" : c.id).containsExactlyInAnyOrder(23);
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-241
|
||||
public void setIdForSingleElementList() {
|
||||
|
||||
entity.contentList.add(content);
|
||||
|
||||
DbAction.Insert<?> insert = createInsert("contentList", content, 0);
|
||||
|
||||
AggregateChange.setIdOfNonRootEntity(context, converter, propertyAccessor, insert, id);
|
||||
|
||||
DummyEntity result = propertyAccessor.getBean();
|
||||
assertThat(result.contentList).extracting(c -> c.id).containsExactlyInAnyOrder(23);
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-241
|
||||
public void setIdForSingleElementMap() {
|
||||
|
||||
entity.contentMap.put("one", content);
|
||||
|
||||
DbAction.Insert<?> insert = createInsert("contentMap", content, "one");
|
||||
|
||||
AggregateChange.setIdOfNonRootEntity(context, converter, propertyAccessor, insert, id);
|
||||
|
||||
DummyEntity result = propertyAccessor.getBean();
|
||||
assertThat(result.contentMap.entrySet()).extracting(e -> e.getKey(), e -> e.getValue().id)
|
||||
.containsExactlyInAnyOrder(tuple("one", 23));
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-291
|
||||
public void setIdForDeepReference() {
|
||||
|
||||
content.single = tag;
|
||||
entity.single = content;
|
||||
|
||||
DbAction.Insert<?> parentInsert = createInsert("single", content, null);
|
||||
DbAction.Insert<?> insert = createDeepInsert("single", tag, null, parentInsert);
|
||||
|
||||
AggregateChange.setIdOfNonRootEntity(context, converter, propertyAccessor, insert, id);
|
||||
|
||||
Content result = contentPropertyAccessor.getBean();
|
||||
|
||||
assertThat(result.single.id).isEqualTo(id);
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-291
|
||||
public void setIdForDeepReferenceElementList() {
|
||||
|
||||
content.tagList.add(tag);
|
||||
entity.single = content;
|
||||
|
||||
DbAction.Insert<?> parentInsert = createInsert("single", content, null);
|
||||
DbAction.Insert<?> insert = createDeepInsert("tagList", tag, 0, parentInsert);
|
||||
|
||||
AggregateChange.setIdOfNonRootEntity(context, converter, propertyAccessor, insert, id);
|
||||
|
||||
Content result = contentPropertyAccessor.getBean();
|
||||
assertThat(result.tagList).extracting(c -> c.id).containsExactlyInAnyOrder(23);
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-291
|
||||
public void setIdForDeepElementSetElementSet() {
|
||||
|
||||
content.tagSet.add(tag);
|
||||
entity.contentSet.add(content);
|
||||
|
||||
DbAction.Insert<?> parentInsert = createInsert("contentSet", content, null);
|
||||
DbAction.Insert<?> insert = createDeepInsert("tagSet", tag, null, parentInsert);
|
||||
|
||||
AggregateChange.setIdOfNonRootEntity(context, converter, propertyAccessor, insert, id);
|
||||
|
||||
Content result = contentPropertyAccessor.getBean();
|
||||
assertThat(result.tagSet).isNotNull();
|
||||
assertThat(result.tagSet).extracting(c -> c == null ? "null" : c.id).containsExactlyInAnyOrder(23);
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-291
|
||||
public void setIdForDeepElementListSingleReference() {
|
||||
|
||||
content.single = tag;
|
||||
entity.contentList.add(content);
|
||||
|
||||
DbAction.Insert<?> parentInsert = createInsert("contentList", content, 0);
|
||||
DbAction.Insert<?> insert = createDeepInsert("single", tag, null, parentInsert);
|
||||
|
||||
AggregateChange.setIdOfNonRootEntity(context, converter, propertyAccessor, insert, id);
|
||||
|
||||
Content result = contentPropertyAccessor.getBean();
|
||||
assertThat(result.single.id).isEqualTo(id);
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-291
|
||||
public void setIdForDeepElementListElementList() {
|
||||
|
||||
content.tagList.add(tag);
|
||||
entity.contentList.add(content);
|
||||
|
||||
DbAction.Insert<?> parentInsert = createInsert("contentList", content, 0);
|
||||
DbAction.Insert<?> insert = createDeepInsert("tagList", tag, 0, parentInsert);
|
||||
|
||||
AggregateChange.setIdOfNonRootEntity(context, converter, propertyAccessor, insert, id);
|
||||
|
||||
Content result = contentPropertyAccessor.getBean();
|
||||
assertThat(result.tagList).extracting(c -> c.id).containsExactlyInAnyOrder(23);
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-291
|
||||
public void setIdForDeepElementMapElementMap() {
|
||||
|
||||
content.tagMap.put("one", tag);
|
||||
entity.contentMap.put("one", content);
|
||||
|
||||
DbAction.Insert<?> parentInsert = createInsert("contentMap", content, "one");
|
||||
DbAction.Insert<?> insert = createDeepInsert("tagMap", tag, "one", parentInsert);
|
||||
|
||||
AggregateChange.setIdOfNonRootEntity(context, converter, propertyAccessor, insert, id);
|
||||
|
||||
Content result = contentPropertyAccessor.getBean();
|
||||
assertThat(result.tagMap.entrySet()).extracting(e -> e.getKey(), e -> e.getValue().id)
|
||||
.containsExactlyInAnyOrder(tuple("one", 23));
|
||||
}
|
||||
|
||||
PersistentPropertyPath<RelationalPersistentProperty> toPath(String path, Class source) {
|
||||
|
||||
PersistentPropertyPaths<?, RelationalPersistentProperty> persistentPropertyPaths = context
|
||||
.findPersistentPropertyPaths(source, p -> true);
|
||||
|
||||
return persistentPropertyPaths.filter(p -> p.toDotPath().equals(path)).stream().findFirst().orElse(null);
|
||||
}
|
||||
|
||||
PersistentPropertyPath<RelationalPersistentProperty> toPath(DummyEntity root, Object pathValue) {
|
||||
// DefaultPersistentPropertyPath is package-public
|
||||
return new WritingContext(context, entity, new AggregateChange<>(AggregateChange.Kind.SAVE, DummyEntity.class, root))
|
||||
.insert().stream().filter(a -> a instanceof DbAction.Insert).map(DbAction.Insert.class::cast)
|
||||
.filter(a -> a.getEntity() == pathValue)
|
||||
.map(DbAction.Insert::getPropertyPath)
|
||||
.findFirst().orElse(null);
|
||||
}
|
||||
|
||||
private static class DummyEntity {
|
||||
|
||||
@Id Integer rootId;
|
||||
|
||||
Content single;
|
||||
|
||||
Set<Content> contentSet = new HashSet<>();
|
||||
|
||||
List<Content> contentList = new ArrayList<>();
|
||||
|
||||
Map<String, Content> contentMap = new HashMap<>();
|
||||
}
|
||||
|
||||
private static class Content {
|
||||
|
||||
@Id Integer id;
|
||||
|
||||
Tag single;
|
||||
|
||||
Set<Tag> tagSet = new HashSet<>();
|
||||
|
||||
List<Tag> tagList = new ArrayList<>();
|
||||
|
||||
Map<String, Tag> tagMap = new HashMap<>();
|
||||
}
|
||||
|
||||
private static class Tag {
|
||||
@Id Integer id;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user