DATAJDBC-241 - Support for immutable entities.

Immutable entities can now be saved and loaded and the immutability will be honored.

This feature is currently limited to a single level of reference.

In order to implement this the logic for updating IDs with those generated from the database got moved out of the DefaultDataAccessStrategy into the AggregateChange.
As part of that move DataAccessStrategy.save now returns a generated id, if available.

See also: DATAJDBC-248.
This commit is contained in:
Jens Schauder
2018-07-23 10:41:16 +02:00
committed by Oliver Gierke
parent eb69f4206c
commit e398db544c
23 changed files with 648 additions and 121 deletions

View File

@@ -43,7 +43,7 @@ public class CascadingDataAccessStrategy implements DataAccessStrategy {
* @see org.springframework.data.jdbc.core.DataAccessStrategy#insert(java.lang.Object, java.lang.Class, java.util.Map)
*/
@Override
public <T> T insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) {
public <T> Object insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) {
return collect(das -> das.insert(instance, domainType, additionalParameters));
}

View File

@@ -38,10 +38,9 @@ public interface DataAccessStrategy {
* @param additionalParameters name-value pairs of additional parameters. Especially ids of parent entities that need
* to get referenced are contained in this map. Must not be {@code null}.
* @param <T> the type of the instance.
* @return the instance after insert into. The returned instance may be different to the given {@code instance} as
* this method can apply changes to the return object.
* @return the id generated by the database if any.
*/
<T> T insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters);
<T> Object insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters);
/**
* Updates the data of a single entity in the database. Referenced entities don't get handled.

View File

@@ -21,13 +21,11 @@ import lombok.RequiredArgsConstructor;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.dao.NonTransientDataAccessException;
import org.springframework.data.jdbc.support.JdbcUtil;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.PersistentPropertyPath;
@@ -83,7 +81,7 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
* @see org.springframework.data.jdbc.core.DataAccessStrategy#insert(java.lang.Object, java.lang.Class, java.util.Map)
*/
@Override
public <T> T insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) {
public <T> Object insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) {
KeyHolder holder = new GeneratedKeyHolder();
RelationalPersistentEntity<T> persistentEntity = getRequiredPersistentEntity(domainType);
@@ -110,16 +108,7 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
holder //
);
instance = setIdFromJdbc(instance, holder, persistentEntity);
// if there is an id property and it was null before the save
// The database should have created an id and provided it.
if (idProperty != null && idValue == null && persistentEntity.isNew(instance)) {
throw new IllegalStateException(String.format(ENTITY_NEW_AFTER_INSERT, persistentEntity));
}
return instance;
return getIdFromHolder(holder, persistentEntity);
}
/*
@@ -327,41 +316,20 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
|| (idProperty.getType() == long.class && idValue.equals(0L));
}
private <S> S setIdFromJdbc(S instance, KeyHolder holder, RelationalPersistentEntity<S> persistentEntity) {
try {
PersistentPropertyAccessor<S> accessor = converter.getPropertyAccessor(persistentEntity, instance);
getIdFromHolder(holder, persistentEntity).ifPresent(it -> {
RelationalPersistentProperty idProperty = persistentEntity.getRequiredIdProperty();
accessor.setProperty(idProperty, it);
});
return accessor.getBean();
} catch (NonTransientDataAccessException e) {
throw new UnableToSetId("Unable to set id of " + instance, e);
}
}
private <S> Optional<Object> getIdFromHolder(KeyHolder holder, RelationalPersistentEntity<S> persistentEntity) {
private <S> Object getIdFromHolder(KeyHolder holder, RelationalPersistentEntity<S> persistentEntity) {
try {
// MySQL just returns one value with a special name
return Optional.ofNullable(holder.getKey());
return holder.getKey();
} catch (InvalidDataAccessApiUsageException e) {
// Postgres returns a value for each column
Map<String, Object> keys = holder.getKeys();
if (keys == null || persistentEntity.getIdProperty() == null) {
return Optional.empty();
return null;
}
return Optional.ofNullable(keys.get(persistentEntity.getIdColumn()));
return keys.get(persistentEntity.getIdColumn());
}
}

View File

@@ -58,8 +58,9 @@ class DefaultJdbcInterpreter implements Interpreter {
@Override
public <T> void interpret(Insert<T> insert) {
T entity = accessStrategy.insert(insert.getEntity(), insert.getEntityType(), createAdditionalColumnValues(insert));
insert.setResultingEntity(entity);
Object id = accessStrategy.insert(insert.getEntity(), insert.getEntityType(), createAdditionalColumnValues(insert));
insert.setGeneratedId(id);
}
/*
@@ -69,8 +70,8 @@ class DefaultJdbcInterpreter implements Interpreter {
@Override
public <T> void interpret(InsertRoot<T> insert) {
T entity = accessStrategy.insert(insert.getEntity(), insert.getEntityType(), Collections.emptyMap());
insert.setResultingEntity(entity);
Object id = accessStrategy.insert(insert.getEntity(), insert.getEntityType(), Collections.emptyMap());
insert.setGeneratedId(id);
}
/*
@@ -78,7 +79,7 @@ class DefaultJdbcInterpreter implements Interpreter {
* @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) {
public <T> void interpret(Update<T> update ) {
accessStrategy.update(update.getEntity(), update.getEntityType());
}
@@ -169,8 +170,8 @@ class DefaultJdbcInterpreter implements Interpreter {
Object entity = dependingOn.getEntity();
if (dependingOn instanceof DbAction.WithResultEntity) {
entity = ((DbAction.WithResultEntity<?>) dependingOn).getResultingEntity();
if (dependingOn instanceof DbAction.WithGeneratedId) {
return ((DbAction.WithGeneratedId<?>) dependingOn).getGeneratedId();
}
return persistentEntity.getIdentifierAccessor(entity).getIdentifier();

View File

@@ -36,7 +36,7 @@ public class DelegatingDataAccessStrategy implements DataAccessStrategy {
* @see org.springframework.data.jdbc.core.DataAccessStrategy#insert(java.lang.Object, java.lang.Class, java.util.Map)
*/
@Override
public <T> T insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) {
public <T> Object insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) {
return delegate.insert(instance, domainType, additionalParameters);
}

View File

@@ -22,6 +22,7 @@ import org.springframework.data.mapping.IdentifierAccessor;
import org.springframework.data.relational.core.conversion.AggregateChange;
import org.springframework.data.relational.core.conversion.AggregateChange.Kind;
import org.springframework.data.relational.core.conversion.Interpreter;
import org.springframework.data.relational.core.conversion.RelationalConverter;
import org.springframework.data.relational.core.conversion.RelationalEntityDeleteWriter;
import org.springframework.data.relational.core.conversion.RelationalEntityWriter;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
@@ -46,6 +47,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
private final ApplicationEventPublisher publisher;
private final RelationalMappingContext context;
private final RelationalConverter converter;
private final Interpreter interpreter;
private final RelationalEntityWriter jdbcEntityWriter;
@@ -62,14 +64,16 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
* @param dataAccessStrategy must not be {@literal null}.
*/
public JdbcAggregateTemplate(ApplicationEventPublisher publisher, RelationalMappingContext context,
DataAccessStrategy dataAccessStrategy) {
RelationalConverter converter, DataAccessStrategy dataAccessStrategy) {
Assert.notNull(publisher, "ApplicationEventPublisher must not be null!");
Assert.notNull(context, "RelationalMappingContext must not be null!");
Assert.notNull(converter, "RelationalConverter must not be null!");
Assert.notNull(dataAccessStrategy, "DataAccessStrategy must not be null!");
this.publisher = publisher;
this.context = context;
this.converter = converter;
this.accessStrategy = dataAccessStrategy;
this.jdbcEntityWriter = new RelationalEntityWriter(context);
@@ -86,8 +90,8 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
Assert.notNull(instance, "Aggregate instance must not be null!");
RelationalPersistentEntity<?> entity = context.getRequiredPersistentEntity(instance.getClass());
IdentifierAccessor identifierAccessor = entity.getIdentifierAccessor(instance);
RelationalPersistentEntity<?> persistentEntity = context.getRequiredPersistentEntity(instance.getClass());
IdentifierAccessor identifierAccessor = persistentEntity.getIdentifierAccessor(instance);
AggregateChange<T> change = createChange(instance);
@@ -97,9 +101,9 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
change //
));
change.executeWith(interpreter);
change.executeWith(interpreter, context, converter);
Object identifier = entity.getIdentifierAccessor(change.getEntity()).getIdentifier();
Object identifier = persistentEntity.getIdentifierAccessor(change.getEntity()).getIdentifier();
Assert.notNull(identifier, "After saving the identifier must not be null");
@@ -198,7 +202,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
public void deleteAll(Class<?> domainType) {
AggregateChange<?> change = createDeletingChange(domainType);
change.executeWith(interpreter);
change.executeWith(interpreter, context, converter);
}
private void deleteTree(Object id, @Nullable Object entity, Class<?> domainType) {
@@ -209,7 +213,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
Optional<Object> optionalEntity = Optional.ofNullable(entity);
publisher.publishEvent(new BeforeDeleteEvent(specifiedId, optionalEntity, change));
change.executeWith(interpreter);
change.executeWith(interpreter, context, converter);
publisher.publishEvent(new AfterDeleteEvent(specifiedId, optionalEntity, change));
}

View File

@@ -128,12 +128,13 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
* @see org.springframework.data.jdbc.core.DataAccessStrategy#insert(java.lang.Object, java.lang.Class, java.util.Map)
*/
@Override
public <T> T insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) {
sqlSession().insert(namespace(domainType) + ".insert",
new MyBatisContext(null, instance, domainType, additionalParameters));
public <T> Object insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) {
return instance;
MyBatisContext myBatisContext = new MyBatisContext(null, instance, domainType, additionalParameters);
sqlSession().insert(namespace(domainType) + ".insert",
myBatisContext);
return myBatisContext.getId();
}
/*

View File

@@ -104,7 +104,7 @@ public class JdbcRepositoryFactory extends RepositoryFactorySupport {
@Override
protected Object getTargetRepository(RepositoryInformation repositoryInformation) {
JdbcAggregateTemplate template = new JdbcAggregateTemplate(publisher, context, accessStrategy);
JdbcAggregateTemplate template = new JdbcAggregateTemplate(publisher, context, converter, accessStrategy);
return new SimpleJdbcRepository<>(template, context.getPersistentEntity(repositoryInformation.getDomainType()));
}

View File

@@ -16,10 +16,19 @@
package org.springframework.data.relational.core.conversion;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.PersistentPropertyPath;
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.lang.Nullable;
import org.springframework.util.Assert;
/**
* Represents the change happening to the aggregate (as used in the context of Domain Driven Design) as a whole.
@@ -36,11 +45,11 @@ public class AggregateChange<T> {
private final Class<T> entityType;
/** Aggregate root, to which the change applies, if available */
private T entity;
@Nullable private T entity;
private final List<DbAction<?>> actions = new ArrayList<>();
public AggregateChange(Kind kind, Class<T> entityType, T entity) {
public AggregateChange(Kind kind, Class<T> entityType, @Nullable T entity) {
this.kind = kind;
this.entityType = entityType;
@@ -48,22 +57,144 @@ public class AggregateChange<T> {
}
@SuppressWarnings("unchecked")
public void executeWith(Interpreter interpreter) {
public void executeWith(Interpreter interpreter, RelationalMappingContext context, RelationalConverter converter) {
RelationalPersistentEntity<T> persistentEntity = entity != null
? (RelationalPersistentEntity<T>) context.getRequiredPersistentEntity(entity.getClass())
: null;
PersistentPropertyAccessor<T> propertyAccessor = //
persistentEntity != null //
? converter.getPropertyAccessor(persistentEntity, entity) //
: null;
actions.forEach(a -> {
a.executeWith(interpreter);
if (a instanceof DbAction.InsertRoot && a.getEntityType().equals(entityType)) {
entity = (T) ((DbAction.InsertRoot<?>) a).getResultingEntity();
if (a instanceof DbAction.WithGeneratedId) {
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<?>) a).getGeneratedId();
if (generatedId != null) {
if (a instanceof DbAction.InsertRoot && a.getEntityType().equals(entityType)) {
propertyAccessor.setProperty(persistentEntity.getRequiredIdProperty(), generatedId);
} else if (a instanceof DbAction.WithDependingOn) {
setId(context, converter, propertyAccessor, (DbAction.WithDependingOn<?>) a, generatedId);
}
}
}
});
if (propertyAccessor != null) {
entity = propertyAccessor.getBean();
}
}
public void addAction(DbAction<?> action) {
actions.add(action);
}
@SuppressWarnings("unchecked")
static void setId(RelationalMappingContext context, RelationalConverter converter,
PersistentPropertyAccessor<?> propertyAccessor, DbAction.WithDependingOn<?> action, Object generatedId) {
PersistentPropertyPath<RelationalPersistentProperty> propertyPathToEntity = action.getPropertyPath();
RelationalPersistentProperty requiredIdProperty = context
.getRequiredPersistentEntity(propertyPathToEntity.getRequiredLeafProperty().getActualType())
.getRequiredIdProperty();
PersistentPropertyPath<RelationalPersistentProperty> pathToId = context.getPersistentPropertyPath(
propertyPathToEntity.toDotPath() + '.' + requiredIdProperty.getName(),
propertyPathToEntity.getBaseProperty().getOwner().getType());
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()) {
String keyColumn = leafProperty.getKeyColumn();
Object keyObject = action.getAdditionalValues().get(keyColumn);
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 {
propertyAccessor.setProperty(pathToId, generatedId);
}
}
@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) {
Object originalElement = action.getEntity();
RelationalPersistentEntity<T> persistentEntity = (RelationalPersistentEntity<T>) converter.getMappingContext()
.getRequiredPersistentEntity(action.getEntityType());
PersistentPropertyAccessor<T> intermediateAccessor = converter.getPropertyAccessor(persistentEntity,
(T) originalElement);
intermediateAccessor.setProperty(persistentEntity.getRequiredIdProperty(), generatedId);
return intermediateAccessor;
}
/**
* The kind of action to be performed on an aggregate.
*/

View File

@@ -25,6 +25,9 @@ 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;
import org.springframework.util.Assert;
/**
* An instance of this interface represents a (conceptual) single interaction with a database, e.g. a single update,
@@ -68,16 +71,15 @@ public interface DbAction<T> {
* @param <T> type of the entity for which this represents a database interaction.
*/
@Data
@RequiredArgsConstructor
class Insert<T> implements WithDependingOn<T>, WithEntity<T>, WithResultEntity<T> {
class Insert<T> implements WithGeneratedId<T>, WithDependingOn<T> {
@NonNull private final T entity;
@NonNull private final PersistentPropertyPath<RelationalPersistentProperty> propertyPath;
@NonNull private final WithEntity<?> dependingOn;
@NonNull final T entity;
@NonNull final PersistentPropertyPath<RelationalPersistentProperty> propertyPath;
@NonNull final WithEntity<?> dependingOn;
Map<String, Object> additionalValues = new HashMap<>();
private T resultingEntity;
private Object generatedId;
@Override
public void doExecuteWith(Interpreter interpreter) {
@@ -97,11 +99,11 @@ public interface DbAction<T> {
*/
@Data
@RequiredArgsConstructor
class InsertRoot<T> implements WithEntity<T>, WithResultEntity<T> {
class InsertRoot<T> implements WithEntity<T>, WithGeneratedId<T> {
@NonNull private final T entity;
private T resultingEntity;
private Object generatedId;
@Override
public void doExecuteWith(Interpreter interpreter) {
@@ -240,7 +242,7 @@ public interface DbAction<T> {
*
* @author Jens Schauder
*/
interface WithDependingOn<T> extends WithPropertyPath<T> {
interface WithDependingOn<T> extends WithPropertyPath<T>, WithEntity<T>{
/**
* The {@link DbAction} of a parent entity, possibly the aggregate root. This is used to obtain values needed to
@@ -260,6 +262,11 @@ public interface DbAction<T> {
* @return Guaranteed to be not {@code null}.
*/
Map<String, Object> getAdditionalValues();
@Override
default Class<T> getEntityType() {
return WithEntity.super.getEntityType();
}
}
/**
@@ -287,12 +294,13 @@ public interface DbAction<T> {
*
* @author Jens Schauder
*/
interface WithResultEntity<T> extends WithEntity<T> {
interface WithGeneratedId<T> extends WithEntity<T> {
/**
* @return the entity to persist. Guaranteed to be not {@code null}.
*/
T getResultingEntity();
@Nullable
Object getGeneratedId();
@SuppressWarnings("unchecked")
@Override

View File

@@ -42,8 +42,8 @@ public interface Interpreter {
/**
* Interpret an {@link Update}. Interpreting normally means "executing".
*
* @param update the {@link Update} to be executed
* @param <T> the type of entity to work on.
* @param update the {@link Update} to be executed
*/
<T> void interpret(Update<T> update);

View File

@@ -15,9 +15,6 @@
*/
package org.springframework.data.relational.core.conversion;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -25,6 +22,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.Value;
import org.springframework.data.convert.EntityWriter;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PersistentPropertyPath;
@@ -164,8 +162,13 @@ public class RelationalEntityWriter implements EntityWriter<Object, AggregateCha
DbAction action = previousActions.get(parent);
if (action != null) {
Assert.isInstanceOf(DbAction.WithEntity.class, action,
"dependsOn action is not a WithEntity, but " + action.getClass().getSimpleName());
Assert.isInstanceOf( //
DbAction.WithEntity.class, //
action, //
"dependsOn action is not a WithEntity, but " + action.getClass().getSimpleName() //
);
return (DbAction.WithEntity<?>) action;
}
@@ -176,9 +179,9 @@ public class RelationalEntityWriter implements EntityWriter<Object, AggregateCha
return context.getRequiredPersistentEntity(o.getClass()).isNew(o);
}
private List<WritingContext.PathNode> from(PersistentPropertyPath<RelationalPersistentProperty> path) {
private List<PathNode> from(PersistentPropertyPath<RelationalPersistentProperty> path) {
List<WritingContext.PathNode> nodes = new ArrayList<>();
List<PathNode> nodes = new ArrayList<>();
if (path.getLength() == 1) {
@@ -194,7 +197,7 @@ public class RelationalEntityWriter implements EntityWriter<Object, AggregateCha
List<PathNode> pathNodes = nodesCache.get(path.getParentPath());
pathNodes.forEach(parentNode -> {
Object value = path.getRequiredLeafProperty().getOwner().getPropertyAccessor(parentNode.value)
Object value = path.getRequiredLeafProperty().getOwner().getPropertyAccessor(parentNode.getValue())
.getProperty(path.getRequiredLeafProperty());
nodes.addAll(createNodes(path, parentNode, value));
@@ -213,7 +216,7 @@ public class RelationalEntityWriter implements EntityWriter<Object, AggregateCha
return Collections.emptyList();
}
List<WritingContext.PathNode> nodes = new ArrayList<>();
List<PathNode> nodes = new ArrayList<>();
if (path.getRequiredLeafProperty().isQualified()) {
@@ -235,17 +238,26 @@ public class RelationalEntityWriter implements EntityWriter<Object, AggregateCha
return nodes;
}
/**
* Represents a single entity in an aggregate along with its property path from the root entity and the chain of
* objects to traverse a long this path.
*/
@RequiredArgsConstructor
@Getter
private class PathNode {
}
private final PersistentPropertyPath<RelationalPersistentProperty> path;
private final @Nullable PathNode parent;
private final Object value;
}
/**
* Represents a single entity in an aggregate along with its property path from the root entity and the chain of objects
* to traverse a long this path.
*/
@Value
static class PathNode {
/** The path to this entity */
PersistentPropertyPath<RelationalPersistentProperty> path;
/**
* The parent {@link PathNode}. This is {@code null} if this is
* the root entity.
*/
@Nullable
PathNode parent;
/** The value of the entity. */
Object value;
}
}

View File

@@ -129,15 +129,6 @@ class BasicRelationalPersistentProperty extends AnnotationBasedPersistentPropert
return isListLike();
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.model.AbstractPersistentProperty#isImmutable()
*/
@Override
public boolean isImmutable() {
return false;
}
private boolean isListLike() {
return isCollectionLike() && !Set.class.isAssignableFrom(this.getType());
}