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:
committed by
Oliver Gierke
parent
eb69f4206c
commit
e398db544c
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user