DATAJDBC-227 - Refactored JdbcEntityWriter and DbActions.
JdbcEntityWriter and JdbcDeleteEntityWriter now use an iterative approach based on PersistentPropertyPath instead of a recursive one. DbAction is now split into multiple interfaces representing different variants of actions. The implementations are simple value types without any implementation inheritance hierarchy. All elements of a DbAction implementation are not null making usage and construction of instances much easier. Original pull request: #79.
This commit is contained in:
committed by
Mark Paluch
parent
c412aad6b8
commit
bdefd3da9a
@@ -21,7 +21,7 @@ import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
|
||||
/**
|
||||
@@ -45,8 +45,8 @@ public class CascadingDataAccessStrategy implements DataAccessStrategy {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S> void update(S instance, Class<S> domainType) {
|
||||
collectVoid(das -> das.update(instance, domainType));
|
||||
public <S> boolean update(S instance, Class<S> domainType) {
|
||||
return collect(das -> das.update(instance, domainType));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -55,7 +55,7 @@ public class CascadingDataAccessStrategy implements DataAccessStrategy {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(Object rootId, PropertyPath propertyPath) {
|
||||
public void delete(Object rootId, PersistentPropertyPath<RelationalPersistentProperty> propertyPath) {
|
||||
collectVoid(das -> das.delete(rootId, propertyPath));
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ public class CascadingDataAccessStrategy implements DataAccessStrategy {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAll(PropertyPath propertyPath) {
|
||||
public void deleteAll(PersistentPropertyPath<RelationalPersistentProperty> propertyPath) {
|
||||
collectVoid(das -> das.deleteAll(propertyPath));
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ package org.springframework.data.jdbc.core;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
@@ -48,8 +48,9 @@ public interface DataAccessStrategy {
|
||||
* @param instance the instance to save. Must not be {@code null}.
|
||||
* @param domainType the type of the instance to save. Must not be {@code null}.
|
||||
* @param <T> the type of the instance to save.
|
||||
* @return wether the update actually updated a row.
|
||||
*/
|
||||
<T> void update(T instance, Class<T> domainType);
|
||||
<T> boolean update(T instance, Class<T> domainType);
|
||||
|
||||
/**
|
||||
* deletes a single row identified by the id, from the table identified by the domainType. Does not handle cascading
|
||||
@@ -63,11 +64,11 @@ public interface DataAccessStrategy {
|
||||
|
||||
/**
|
||||
* Deletes all entities reachable via {@literal propertyPath} from the instance identified by {@literal rootId}.
|
||||
*
|
||||
*
|
||||
* @param rootId Id of the root object on which the {@literal propertyPath} is based. Must not be {@code null}.
|
||||
* @param propertyPath Leading from the root object to the entities to be deleted. Must not be {@code null}.
|
||||
*/
|
||||
void delete(Object rootId, PropertyPath propertyPath);
|
||||
void delete(Object rootId, PersistentPropertyPath<RelationalPersistentProperty> propertyPath);
|
||||
|
||||
/**
|
||||
* Deletes all entities of the given domain type.
|
||||
@@ -82,7 +83,7 @@ public interface DataAccessStrategy {
|
||||
*
|
||||
* @param propertyPath Leading from the root object to the entities to be deleted. Must not be {@code null}.
|
||||
*/
|
||||
void deleteAll(PropertyPath propertyPath);
|
||||
void deleteAll(PersistentPropertyPath<RelationalPersistentProperty> propertyPath);
|
||||
|
||||
/**
|
||||
* Counts the rows in the table representing the given domain type.
|
||||
|
||||
@@ -29,8 +29,8 @@ 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;
|
||||
import org.springframework.data.mapping.PropertyHandler;
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.relational.core.conversion.RelationalConverter;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
@@ -120,11 +120,11 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
|
||||
* @see org.springframework.data.jdbc.core.DataAccessStrategy#update(java.lang.Object, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public <S> void update(S instance, Class<S> domainType) {
|
||||
public <S> boolean update(S instance, Class<S> domainType) {
|
||||
|
||||
RelationalPersistentEntity<S> persistentEntity = getRequiredPersistentEntity(domainType);
|
||||
|
||||
operations.update(sql(domainType).getUpdate(), getPropertyMap(instance, persistentEntity));
|
||||
return operations.update(sql(domainType).getUpdate(), getPropertyMap(instance, persistentEntity)) != 0;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -145,11 +145,12 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
|
||||
* @see org.springframework.data.jdbc.core.DataAccessStrategy#delete(java.lang.Object, org.springframework.data.mapping.PropertyPath)
|
||||
*/
|
||||
@Override
|
||||
public void delete(Object rootId, PropertyPath propertyPath) {
|
||||
public void delete(Object rootId, PersistentPropertyPath<RelationalPersistentProperty> propertyPath) {
|
||||
|
||||
RelationalPersistentEntity<?> rootEntity = context.getRequiredPersistentEntity(propertyPath.getOwningType());
|
||||
RelationalPersistentEntity<?> rootEntity = context
|
||||
.getRequiredPersistentEntity(propertyPath.getBaseProperty().getOwner().getType());
|
||||
|
||||
RelationalPersistentProperty referencingProperty = rootEntity.getRequiredPersistentProperty(propertyPath.getSegment());
|
||||
RelationalPersistentProperty referencingProperty = propertyPath.getLeafProperty();
|
||||
Assert.notNull(referencingProperty, "No property found matching the PropertyPath " + propertyPath);
|
||||
|
||||
String format = sql(rootEntity.getType()).createDeleteByPath(propertyPath);
|
||||
@@ -173,8 +174,9 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
|
||||
* @see org.springframework.data.jdbc.core.DataAccessStrategy#deleteAll(org.springframework.data.mapping.PropertyPath)
|
||||
*/
|
||||
@Override
|
||||
public void deleteAll(PropertyPath propertyPath) {
|
||||
operations.getJdbcOperations().update(sql(propertyPath.getOwningType().getType()).createDeleteAllSql(propertyPath));
|
||||
public void deleteAll(PersistentPropertyPath<RelationalPersistentProperty> propertyPath) {
|
||||
operations.getJdbcOperations()
|
||||
.update(sql(propertyPath.getBaseProperty().getOwner().getType()).createDeleteAllSql(propertyPath));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -343,7 +345,10 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
|
||||
} catch (InvalidDataAccessApiUsageException e) {
|
||||
// Postgres returns a value for each column
|
||||
Map<String, Object> keys = holder.getKeys();
|
||||
return Optional.ofNullable(keys == null ? null : keys.get(persistentEntity.getIdColumn()));
|
||||
return Optional.ofNullable( //
|
||||
keys == null || persistentEntity.getIdProperty() == null //
|
||||
? null //
|
||||
: keys.get(persistentEntity.getIdColumn()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,17 +18,22 @@ package org.springframework.data.jdbc.core;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.relational.core.conversion.DbAction;
|
||||
import org.springframework.data.relational.core.conversion.Interpreter;
|
||||
import org.springframework.data.relational.core.conversion.DbAction.Delete;
|
||||
import org.springframework.data.relational.core.conversion.DbAction.DeleteAll;
|
||||
import org.springframework.data.relational.core.conversion.DbAction.DeleteAllRoot;
|
||||
import org.springframework.data.relational.core.conversion.DbAction.DeleteRoot;
|
||||
import org.springframework.data.relational.core.conversion.DbAction.Insert;
|
||||
import org.springframework.data.relational.core.conversion.DbAction.InsertRoot;
|
||||
import org.springframework.data.relational.core.conversion.DbAction.Merge;
|
||||
import org.springframework.data.relational.core.conversion.DbAction.Update;
|
||||
import org.springframework.data.relational.core.conversion.DbAction.UpdateRoot;
|
||||
import org.springframework.data.relational.core.conversion.Interpreter;
|
||||
import org.springframework.data.relational.core.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;
|
||||
|
||||
/**
|
||||
* {@link Interpreter} for {@link DbAction}s using a {@link DataAccessStrategy} for performing actual database
|
||||
@@ -53,51 +58,66 @@ class DefaultJdbcInterpreter implements Interpreter {
|
||||
accessStrategy.insert(insert.getEntity(), insert.getEntityType(), createAdditionalColumnValues(insert));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void interpret(InsertRoot<T> insert) {
|
||||
accessStrategy.insert(insert.getEntity(), insert.getEntityType(), new HashMap<>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void interpret(Update<T> update) {
|
||||
accessStrategy.update(update.getEntity(), update.getEntityType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void interpret(Delete<T> delete) {
|
||||
public <T> void interpret(UpdateRoot<T> update) {
|
||||
accessStrategy.update(update.getEntity(), update.getEntityType());
|
||||
}
|
||||
|
||||
if (delete.getPropertyPath() == null) {
|
||||
accessStrategy.delete(delete.getRootId(), delete.getEntityType());
|
||||
} else {
|
||||
accessStrategy.delete(delete.getRootId(), delete.getPropertyPath().getPath());
|
||||
@Override
|
||||
public <T> void interpret(Merge<T> merge) {
|
||||
|
||||
// temporary implementation
|
||||
if (!accessStrategy.update(merge.getEntity(), merge.getEntityType())) {
|
||||
accessStrategy.insert(merge.getEntity(), merge.getEntityType(), createAdditionalColumnValues(merge));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void interpret(DeleteAll<T> delete) {
|
||||
|
||||
if (delete.getEntityType() == null) {
|
||||
accessStrategy.deleteAll(delete.getPropertyPath().getPath());
|
||||
} else {
|
||||
accessStrategy.deleteAll(delete.getEntityType());
|
||||
}
|
||||
public <T> void interpret(Delete<T> delete) {
|
||||
accessStrategy.delete(delete.getRootId(), delete.getPropertyPath());
|
||||
}
|
||||
|
||||
private <T> Map<String, Object> createAdditionalColumnValues(Insert<T> insert) {
|
||||
@Override
|
||||
public <T> void interpret(DeleteRoot<T> delete) {
|
||||
accessStrategy.delete(delete.getRootId(), delete.getEntityType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void interpret(DeleteAll<T> delete) {
|
||||
accessStrategy.deleteAll(delete.getPropertyPath());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void interpret(DeleteAllRoot<T> deleteAllRoot) {
|
||||
accessStrategy.deleteAll(deleteAllRoot.getEntityType());
|
||||
}
|
||||
|
||||
private <T> Map<String, Object> createAdditionalColumnValues(DbAction.WithDependingOn<T> action) {
|
||||
|
||||
Map<String, Object> additionalColumnValues = new HashMap<>();
|
||||
addDependingOnInformation(insert, additionalColumnValues);
|
||||
additionalColumnValues.putAll(insert.getAdditionalValues());
|
||||
addDependingOnInformation(action, additionalColumnValues);
|
||||
additionalColumnValues.putAll(action.getAdditionalValues());
|
||||
|
||||
return additionalColumnValues;
|
||||
}
|
||||
|
||||
private <T> void addDependingOnInformation(Insert<T> insert, Map<String, Object> additionalColumnValues) {
|
||||
private <T> void addDependingOnInformation(DbAction.WithDependingOn<T> action, Map<String, Object> additionalColumnValues) {
|
||||
|
||||
DbAction dependingOn = insert.getDependingOn();
|
||||
|
||||
if (dependingOn == null) {
|
||||
return;
|
||||
}
|
||||
DbAction.WithEntity<?> dependingOn = action.getDependingOn();
|
||||
|
||||
RelationalPersistentEntity<?> persistentEntity = context.getRequiredPersistentEntity(dependingOn.getEntityType());
|
||||
|
||||
String columnName = getColumnNameForReverseColumn(insert, persistentEntity);
|
||||
String columnName = getColumnNameForReverseColumn(action);
|
||||
|
||||
Object identifier = getIdFromEntityDependingOn(dependingOn, persistentEntity);
|
||||
|
||||
@@ -105,16 +125,13 @@ class DefaultJdbcInterpreter implements Interpreter {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Object getIdFromEntityDependingOn(DbAction dependingOn, RelationalPersistentEntity<?> persistentEntity) {
|
||||
private Object getIdFromEntityDependingOn(DbAction.WithEntity<?> dependingOn, RelationalPersistentEntity<?> persistentEntity) {
|
||||
return persistentEntity.getIdentifierAccessor(dependingOn.getEntity()).getIdentifier();
|
||||
}
|
||||
|
||||
private <T> String getColumnNameForReverseColumn(Insert<T> insert, RelationalPersistentEntity<?> persistentEntity) {
|
||||
private String getColumnNameForReverseColumn(DbAction.WithPropertyPath<?> action) {
|
||||
|
||||
PropertyPath path = insert.getPropertyPath().getPath();
|
||||
|
||||
Assert.notNull(path, "There shouldn't be an insert depending on another insert without having a PropertyPath.");
|
||||
|
||||
return persistentEntity.getRequiredPersistentProperty(path.getSegment()).getReverseColumnName();
|
||||
PersistentPropertyPath<RelationalPersistentProperty> path = action.getPropertyPath();
|
||||
return path.getRequiredLeafProperty().getReverseColumnName();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ package org.springframework.data.jdbc.core;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -38,12 +38,12 @@ public class DelegatingDataAccessStrategy implements DataAccessStrategy {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S> void update(S instance, Class<S> domainType) {
|
||||
delegate.update(instance, domainType);
|
||||
public <S> boolean update(S instance, Class<S> domainType) {
|
||||
return delegate.update(instance, domainType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(Object rootId, PropertyPath propertyPath) {
|
||||
public void delete(Object rootId, PersistentPropertyPath<RelationalPersistentProperty> propertyPath) {
|
||||
delegate.delete(rootId, propertyPath);
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ public class DelegatingDataAccessStrategy implements DataAccessStrategy {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAll(PropertyPath propertyPath) {
|
||||
public void deleteAll(PersistentPropertyPath<RelationalPersistentProperty> propertyPath) {
|
||||
delegate.deleteAll(propertyPath);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,8 +25,8 @@ import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.data.jdbc.repository.support.SimpleJdbcRepository;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.mapping.PropertyHandler;
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
@@ -61,7 +61,8 @@ class SqlGenerator {
|
||||
private final Lazy<String> deleteByListSql = Lazy.of(this::createDeleteByListSql);
|
||||
private final SqlGeneratorSource sqlGeneratorSource;
|
||||
|
||||
SqlGenerator(RelationalMappingContext context, RelationalPersistentEntity<?> entity, SqlGeneratorSource sqlGeneratorSource) {
|
||||
SqlGenerator(RelationalMappingContext context, RelationalPersistentEntity<?> entity,
|
||||
SqlGeneratorSource sqlGeneratorSource) {
|
||||
|
||||
this.context = context;
|
||||
this.entity = entity;
|
||||
@@ -109,8 +110,8 @@ class SqlGenerator {
|
||||
*
|
||||
* @param columnName name of the column of the FK back to the referencing entity.
|
||||
* @param keyColumn if the property is of type {@link Map} this column contains the map key.
|
||||
* @param ordered whether the SQL statement should include an ORDER BY for the keyColumn. If this is {@code true},
|
||||
* the keyColumn must not be {@code null}.
|
||||
* @param ordered whether the SQL statement should include an ORDER BY for the keyColumn. If this is {@code true}, the
|
||||
* keyColumn must not be {@code null}.
|
||||
* @return a SQL String.
|
||||
*/
|
||||
String getFindAllByProperty(String columnName, @Nullable String keyColumn, boolean ordered) {
|
||||
@@ -280,20 +281,20 @@ class SqlGenerator {
|
||||
return String.format("DELETE FROM %s WHERE %s = :id", entity.getTableName(), entity.getIdColumn());
|
||||
}
|
||||
|
||||
String createDeleteAllSql(@Nullable PropertyPath path) {
|
||||
String createDeleteAllSql(@Nullable PersistentPropertyPath<RelationalPersistentProperty> path) {
|
||||
|
||||
if (path == null) {
|
||||
return String.format("DELETE FROM %s", entity.getTableName());
|
||||
}
|
||||
|
||||
RelationalPersistentEntity<?> entityToDelete = context.getRequiredPersistentEntity(path.getLeafType());
|
||||
RelationalPersistentEntity<?> entityToDelete = context
|
||||
.getRequiredPersistentEntity(path.getRequiredLeafProperty().getActualType());
|
||||
|
||||
RelationalPersistentEntity<?> owningEntity = context.getRequiredPersistentEntity(path.getOwningType());
|
||||
RelationalPersistentProperty property = owningEntity.getRequiredPersistentProperty(path.getSegment());
|
||||
RelationalPersistentProperty property = path.getBaseProperty();
|
||||
|
||||
String innerMostCondition = String.format("%s IS NOT NULL", property.getReverseColumnName());
|
||||
|
||||
String condition = cascadeConditions(innerMostCondition, path.next());
|
||||
String condition = cascadeConditions(innerMostCondition, getSubPath(path));
|
||||
|
||||
return String.format("DELETE FROM %s WHERE %s", entityToDelete.getTableName(), condition);
|
||||
}
|
||||
@@ -302,29 +303,42 @@ class SqlGenerator {
|
||||
return String.format("DELETE FROM %s WHERE %s IN (:ids)", entity.getTableName(), entity.getIdColumn());
|
||||
}
|
||||
|
||||
String createDeleteByPath(PropertyPath path) {
|
||||
String createDeleteByPath(PersistentPropertyPath<RelationalPersistentProperty> path) {
|
||||
|
||||
RelationalPersistentEntity<?> entityToDelete = context.getRequiredPersistentEntity(path.getLeafType());
|
||||
RelationalPersistentEntity<?> owningEntity = context.getRequiredPersistentEntity(path.getOwningType());
|
||||
RelationalPersistentProperty property = owningEntity.getRequiredPersistentProperty(path.getSegment());
|
||||
RelationalPersistentEntity<?> entityToDelete = context
|
||||
.getRequiredPersistentEntity(path.getRequiredLeafProperty().getActualType());
|
||||
RelationalPersistentProperty property = path.getBaseProperty();
|
||||
|
||||
String innerMostCondition = String.format("%s = :rootId", property.getReverseColumnName());
|
||||
|
||||
String condition = cascadeConditions(innerMostCondition, path.next());
|
||||
String condition = cascadeConditions(innerMostCondition, getSubPath(path));
|
||||
|
||||
return String.format("DELETE FROM %s WHERE %s", entityToDelete.getTableName(), condition);
|
||||
}
|
||||
|
||||
private String cascadeConditions(String innerCondition, @Nullable PropertyPath path) {
|
||||
private PersistentPropertyPath<RelationalPersistentProperty> getSubPath(
|
||||
PersistentPropertyPath<RelationalPersistentProperty> path) {
|
||||
|
||||
if (path == null) {
|
||||
int pathLength = path.getLength();
|
||||
|
||||
PersistentPropertyPath<RelationalPersistentProperty> ancestor = path;
|
||||
|
||||
for (int i = pathLength - 1; i > 0; i--) {
|
||||
ancestor = path.getParentPath();
|
||||
}
|
||||
|
||||
return path.getExtensionForBaseOf(ancestor);
|
||||
}
|
||||
|
||||
private String cascadeConditions(String innerCondition, PersistentPropertyPath<RelationalPersistentProperty> path) {
|
||||
|
||||
if (path.getLength() == 0) {
|
||||
return innerCondition;
|
||||
}
|
||||
|
||||
RelationalPersistentEntity<?> entity = context.getRequiredPersistentEntity(path.getOwningType());
|
||||
RelationalPersistentProperty property = entity.getPersistentProperty(path.getSegment());
|
||||
|
||||
Assert.notNull(property, "could not find property for path " + path.getSegment() + " in " + entity);
|
||||
RelationalPersistentEntity<?> entity = context
|
||||
.getRequiredPersistentEntity(path.getBaseProperty().getOwner().getTypeInformation());
|
||||
RelationalPersistentProperty property = path.getRequiredLeafProperty();
|
||||
|
||||
return String.format("%s IN (SELECT %s FROM %s WHERE %s)", //
|
||||
property.getReverseColumnName(), //
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.springframework.data.jdbc.core.DataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.DefaultDataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.DelegatingDataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.SqlGeneratorSource;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.relational.core.conversion.RelationalConverter;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
@@ -130,10 +131,10 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S> void update(S instance, Class<S> domainType) {
|
||||
public <S> boolean update(S instance, Class<S> domainType) {
|
||||
|
||||
sqlSession().update(namespace(domainType) + ".update",
|
||||
new MyBatisContext(null, instance, domainType, Collections.emptyMap()));
|
||||
return sqlSession().update(namespace(domainType) + ".update",
|
||||
new MyBatisContext(null, instance, domainType, Collections.emptyMap())) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -144,10 +145,11 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(Object rootId, PropertyPath propertyPath) {
|
||||
public void delete(Object rootId, PersistentPropertyPath<RelationalPersistentProperty> propertyPath) {
|
||||
|
||||
sqlSession().delete(namespace(propertyPath.getOwningType().getType()) + ".delete-" + toDashPath(propertyPath),
|
||||
new MyBatisContext(rootId, null, propertyPath.getLeafProperty().getTypeInformation().getType(),
|
||||
sqlSession().delete(
|
||||
namespace(propertyPath.getBaseProperty().getOwner().getType()) + ".delete-" + toDashPath(propertyPath),
|
||||
new MyBatisContext(rootId, null, propertyPath.getRequiredLeafProperty().getTypeInformation().getType(),
|
||||
Collections.emptyMap()));
|
||||
}
|
||||
|
||||
@@ -161,10 +163,10 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAll(PropertyPath propertyPath) {
|
||||
public void deleteAll(PersistentPropertyPath<RelationalPersistentProperty> propertyPath) {
|
||||
|
||||
Class<?> baseType = propertyPath.getOwningType().getType();
|
||||
Class<?> leafType = propertyPath.getLeafProperty().getTypeInformation().getType();
|
||||
Class<?> baseType = propertyPath.getBaseProperty().getOwner().getType();
|
||||
Class<?> leafType = propertyPath.getRequiredLeafProperty().getTypeInformation().getType();
|
||||
|
||||
sqlSession().delete( //
|
||||
namespace(baseType) + ".deleteAll-" + toDashPath(propertyPath), //
|
||||
@@ -217,7 +219,7 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
|
||||
return this.sqlSession;
|
||||
}
|
||||
|
||||
private String toDashPath(PropertyPath propertyPath) {
|
||||
private String toDashPath(PersistentPropertyPath<RelationalPersistentProperty> propertyPath) {
|
||||
return propertyPath.toDotPath().replaceAll("\\.", "-");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017-2018 the original author or authors.
|
||||
* Copyright 2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -15,131 +15,36 @@
|
||||
*/
|
||||
package org.springframework.data.relational.core.conversion;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
import lombok.NonNull;
|
||||
import lombok.Value;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
|
||||
/**
|
||||
* Abstracts over a single interaction with a database.
|
||||
* An instance of this interface represents a (conceptual) single interaction with a database, e.g. a single update,
|
||||
* used as a step when synchronizing the state of an aggregate with the database.
|
||||
*
|
||||
* @param <T> the type of the entity that is affected by this action.
|
||||
* @author Jens Schauder
|
||||
* @since 1.0
|
||||
*/
|
||||
@ToString
|
||||
@Getter
|
||||
public abstract class DbAction<T> {
|
||||
public interface DbAction<T> {
|
||||
|
||||
/**
|
||||
* {@link Class} of the entity of which the database representation is affected by this action.
|
||||
*/
|
||||
final Class<T> entityType;
|
||||
|
||||
/**
|
||||
* The entity of which the database representation is affected by this action. Might be {@literal null}.
|
||||
*/
|
||||
private final T entity;
|
||||
|
||||
/**
|
||||
* The path from the Aggregate Root to the entity affected by this {@link DbAction}.
|
||||
*/
|
||||
private final RelationalPropertyPath propertyPath;
|
||||
|
||||
/**
|
||||
* Key-value-pairs to specify additional values to be used with the statement which can't be obtained from the entity,
|
||||
* nor from {@link DbAction}s {@literal this} depends on. A used case are map keys, which need to be persisted with
|
||||
* the map value but aren't part of the value.
|
||||
*/
|
||||
private final Map<String, Object> additionalValues = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Another action, this action depends on. For example the insert for one entity might need the id of another entity,
|
||||
* which gets insert before this one. That action would be referenced by this property, so that the id becomes
|
||||
* available at execution time. Might be {@literal null}.
|
||||
*/
|
||||
private final DbAction dependingOn;
|
||||
|
||||
private DbAction(Class<T> entityType, @Nullable T entity, @Nullable RelationalPropertyPath propertyPath,
|
||||
@Nullable DbAction dependingOn) {
|
||||
|
||||
Assert.notNull(entityType, "entityType must not be null");
|
||||
|
||||
this.entityType = entityType;
|
||||
this.entity = entity;
|
||||
this.propertyPath = propertyPath;
|
||||
this.dependingOn = dependingOn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link Insert} action for the given parameters.
|
||||
*
|
||||
* @param entity the entity to insert into the database. Must not be {@code null}.
|
||||
* @param propertyPath property path from the aggregate root to the entity to be saved. Must not be {@code null}.
|
||||
* @param dependingOn a {@link DbAction} the to be created insert may depend on. Especially the insert of a parent
|
||||
* entity. May be {@code null}.
|
||||
* @param <T> the type of the entity to be inserted.
|
||||
* @return a {@link DbAction} representing the insert.
|
||||
*/
|
||||
public static <T> Insert<T> insert(T entity, RelationalPropertyPath propertyPath, @Nullable DbAction dependingOn) {
|
||||
return new Insert<>(entity, propertyPath, dependingOn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link Update} action for the given parameters.
|
||||
*
|
||||
* @param entity the entity to update in the database. Must not be {@code null}.
|
||||
* @param propertyPath property path from the aggregate root to the entity to be saved. Must not be {@code null}.
|
||||
* @param dependingOn a {@link DbAction} the to be created update may depend on. Especially the insert of a parent
|
||||
* entity. May be {@code null}.
|
||||
* @param <T> the type of the entity to be updated.
|
||||
* @return a {@link DbAction} representing the update.
|
||||
*/
|
||||
public static <T> Update<T> update(T entity, RelationalPropertyPath propertyPath, @Nullable DbAction dependingOn) {
|
||||
return new Update<>(entity, propertyPath, dependingOn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link Delete} action for the given parameters.
|
||||
*
|
||||
* @param id the id of the aggregate root for which referenced entities to be deleted. May not be {@code null}.
|
||||
* @param type the type of the entity to be deleted. Must not be {@code null}.
|
||||
* @param entity the aggregate roo to be deleted. May be {@code null}.
|
||||
* @param propertyPath the property path from the aggregate root to the entity to be deleted.
|
||||
* @param dependingOn an action this action might depend on.
|
||||
* @param <T> the type of the entity to be deleted.
|
||||
* @return a {@link DbAction} representing the deletion of the entity with given type and id.
|
||||
*/
|
||||
public static <T> Delete<T> delete(Object id, Class<T> type, @Nullable T entity,
|
||||
@Nullable RelationalPropertyPath propertyPath, @Nullable DbAction dependingOn) {
|
||||
return new Delete<>(id, type, entity, propertyPath, dependingOn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link DeleteAll} action for the given parameters.
|
||||
*
|
||||
* @param type the type of entities to be deleted. May be {@code null}.
|
||||
* @param propertyPath the property path describing the relation of the entity to be deleted to the aggregate it
|
||||
* belongs to. May be {@code null} for the aggregate root.
|
||||
* @param dependingOn an action this action might depend on. May be {@code null}.
|
||||
* @param <T> the type of the entity to be deleted.
|
||||
* @return a {@link DbAction} representing the deletion of all entities of a given type belonging to a specific type
|
||||
* of aggregate root.
|
||||
*/
|
||||
public static <T> DeleteAll<T> deleteAll(Class<T> type, @Nullable RelationalPropertyPath propertyPath,
|
||||
@Nullable DbAction dependingOn) {
|
||||
return new DeleteAll<>(type, propertyPath, dependingOn);
|
||||
}
|
||||
Class<T> getEntityType();
|
||||
|
||||
/**
|
||||
* Executing this DbAction with the given {@link Interpreter}.
|
||||
* <p>
|
||||
* The default implementation just performs exception handling and delegates to {@link #doExecuteWith(Interpreter)}.
|
||||
*
|
||||
* @param interpreter the {@link Interpreter} responsible for actually executing the {@link DbAction}.
|
||||
* @param interpreter the {@link Interpreter} responsible for actually executing the {@link DbAction}.Must not be
|
||||
* {@code null}.
|
||||
*/
|
||||
void executeWith(Interpreter interpreter) {
|
||||
default void executeWith(Interpreter interpreter) {
|
||||
|
||||
try {
|
||||
doExecuteWith(interpreter);
|
||||
@@ -153,99 +58,240 @@ public abstract class DbAction<T> {
|
||||
*
|
||||
* @param interpreter the {@link Interpreter} responsible for actually executing the {@link DbAction}.
|
||||
*/
|
||||
protected abstract void doExecuteWith(Interpreter interpreter);
|
||||
void doExecuteWith(Interpreter interpreter);
|
||||
|
||||
/**
|
||||
* {@link InsertOrUpdate} must reference an entity.
|
||||
* Represents an insert statement for a single entity that is not the root of an aggregate.
|
||||
*
|
||||
* @param <T> type o the entity for which this represents a database interaction
|
||||
* @param <T> type of the entity for which this represents a database interaction.
|
||||
*/
|
||||
abstract static class InsertOrUpdate<T> extends DbAction<T> {
|
||||
@Value
|
||||
class Insert<T> implements WithDependingOn<T>, WithEntity<T> {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
InsertOrUpdate(T entity, RelationalPropertyPath propertyPath, @Nullable DbAction dependingOn) {
|
||||
super((Class<T>) entity.getClass(), entity, propertyPath, dependingOn);
|
||||
@NonNull T entity;
|
||||
@NonNull PersistentPropertyPath<RelationalPersistentProperty> propertyPath;
|
||||
@NonNull WithEntity<?> dependingOn;
|
||||
|
||||
Map<String, Object> additionalValues = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public void doExecuteWith(Interpreter interpreter) {
|
||||
interpreter.interpret(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<T> getEntityType() {
|
||||
return WithDependingOn.super.getEntityType();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an insert statement.
|
||||
* Represents an insert statement for the root of an aggregate.
|
||||
*
|
||||
* @param <T> type o the entity for which this represents a database interaction
|
||||
* @param <T> type of the entity for which this represents a database interaction.
|
||||
*/
|
||||
public static class Insert<T> extends InsertOrUpdate<T> {
|
||||
@Value
|
||||
class InsertRoot<T> implements WithEntity<T> {
|
||||
|
||||
private Insert(T entity, RelationalPropertyPath propertyPath, @Nullable DbAction dependingOn) {
|
||||
super(entity, propertyPath, dependingOn);
|
||||
}
|
||||
@NonNull T entity;
|
||||
|
||||
@Override
|
||||
protected void doExecuteWith(Interpreter interpreter) {
|
||||
public void doExecuteWith(Interpreter interpreter) {
|
||||
interpreter.interpret(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an update statement.
|
||||
* Represents an update statement for a single entity that is not the root of an aggregate.
|
||||
*
|
||||
* @param <T> type o the entity for which this represents a database interaction
|
||||
* @param <T> type of the entity for which this represents a database interaction.
|
||||
*/
|
||||
public static class Update<T> extends InsertOrUpdate<T> {
|
||||
@Value
|
||||
class Update<T> implements WithEntity<T> {
|
||||
|
||||
private Update(T entity, RelationalPropertyPath propertyPath, @Nullable DbAction dependingOn) {
|
||||
super(entity, propertyPath, dependingOn);
|
||||
}
|
||||
@NonNull T entity;
|
||||
@NonNull PersistentPropertyPath<RelationalPersistentProperty> propertyPath;
|
||||
|
||||
@Override
|
||||
protected void doExecuteWith(Interpreter interpreter) {
|
||||
public void doExecuteWith(Interpreter interpreter) {
|
||||
interpreter.interpret(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an delete statement, possibly a cascading delete statement, i.e. the delete necessary because one
|
||||
* aggregate root gets deleted.
|
||||
* Represents an insert statement for the root of an aggregate.
|
||||
*
|
||||
* @param <T> type o the entity for which this represents a database interaction
|
||||
* @param <T> type of the entity for which this represents a database interaction.
|
||||
*/
|
||||
@Getter
|
||||
public static class Delete<T> extends DbAction<T> {
|
||||
@Value
|
||||
class UpdateRoot<T> implements WithEntity<T> {
|
||||
|
||||
@NonNull private final T entity;
|
||||
|
||||
@Override
|
||||
public void doExecuteWith(Interpreter interpreter) {
|
||||
interpreter.interpret(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a merge statement for a single entity that is not the root of an aggregate.
|
||||
*
|
||||
* @param <T> type of the entity for which this represents a database interaction.
|
||||
*/
|
||||
@Value
|
||||
class Merge<T> implements WithDependingOn<T>, WithPropertyPath<T> {
|
||||
|
||||
@NonNull T entity;
|
||||
@NonNull PersistentPropertyPath<RelationalPersistentProperty> propertyPath;
|
||||
@NonNull WithEntity<?> dependingOn;
|
||||
|
||||
Map<String, Object> additionalValues = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public void doExecuteWith(Interpreter interpreter) {
|
||||
interpreter.interpret(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a delete statement for all entities that that a reachable via a give path from the aggregate root.
|
||||
*
|
||||
* @param <T> type of the entity for which this represents a database interaction.
|
||||
*/
|
||||
@Value
|
||||
class Delete<T> implements WithPropertyPath<T> {
|
||||
|
||||
@NonNull Object rootId;
|
||||
@NonNull PersistentPropertyPath<RelationalPersistentProperty> propertyPath;
|
||||
|
||||
@Override
|
||||
public void doExecuteWith(Interpreter interpreter) {
|
||||
interpreter.interpret(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a delete statement for a aggregate root.
|
||||
* <p>
|
||||
* Note that deletes for contained entities that reference the root are to be represented by separate
|
||||
* {@link DbAction}s.
|
||||
*
|
||||
* @param <T> type of the entity for which this represents a database interaction.
|
||||
*/
|
||||
@Value
|
||||
class DeleteRoot<T> implements DbAction<T> {
|
||||
|
||||
@NonNull Class<T> entityType;
|
||||
@NonNull Object rootId;
|
||||
|
||||
@Override
|
||||
public void doExecuteWith(Interpreter interpreter) {
|
||||
interpreter.interpret(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an delete statement for all entities that that a reachable via a give path from any aggregate root of a
|
||||
* given type.
|
||||
*
|
||||
* @param <T> type of the entity for which this represents a database interaction.
|
||||
*/
|
||||
@Value
|
||||
class DeleteAll<T> implements WithPropertyPath<T> {
|
||||
|
||||
@NonNull PersistentPropertyPath<RelationalPersistentProperty> propertyPath;
|
||||
|
||||
@Override
|
||||
public void doExecuteWith(Interpreter interpreter) {
|
||||
interpreter.interpret(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a delete statement for all aggregate roots of a given type.
|
||||
* <p>
|
||||
* Note that deletes for contained entities that reference the root are to be represented by separate
|
||||
* {@link DbAction}s.
|
||||
*
|
||||
* @param <T> type of the entity for which this represents a database interaction.
|
||||
*/
|
||||
@Value
|
||||
class DeleteAllRoot<T> implements DbAction<T> {
|
||||
|
||||
@NonNull private final Class<T> entityType;
|
||||
|
||||
@Override
|
||||
public void doExecuteWith(Interpreter interpreter) {
|
||||
interpreter.interpret(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An action depending on another action for providing additional information like the id of a parent entity.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
* @since 1.0
|
||||
*/
|
||||
interface WithDependingOn<T> extends WithPropertyPath<T> {
|
||||
|
||||
/**
|
||||
* Id of the root for which all via {@link #propertyPath} referenced entities shall get deleted
|
||||
* The {@link DbAction} of a parent entity, possibly the aggregate root. This is used to obtain values needed to
|
||||
* persist the entity, that are not part of the current entity, especially the id of the parent, which might only
|
||||
* become available once the parent entity got persisted.
|
||||
*
|
||||
* @return Guaranteed to be not {@code null}.
|
||||
* @see #getAdditionalValues()
|
||||
*/
|
||||
private final Object rootId;
|
||||
WithEntity<?> getDependingOn();
|
||||
|
||||
private Delete(@Nullable Object rootId, Class<T> type, @Nullable T entity, @Nullable RelationalPropertyPath propertyPath,
|
||||
@Nullable DbAction dependingOn) {
|
||||
/**
|
||||
* Additional values to be set during insert or update statements.
|
||||
* <p>
|
||||
* Values come from parent entities but one might also add values manually.
|
||||
*
|
||||
* @return Guaranteed to be not {@code null}.
|
||||
*/
|
||||
Map<String, Object> getAdditionalValues();
|
||||
}
|
||||
|
||||
super(type, entity, propertyPath, dependingOn);
|
||||
/**
|
||||
* A {@link DbAction} that stores the information of a single entity in the database.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
* @since 1.0
|
||||
*/
|
||||
interface WithEntity<T> extends DbAction<T> {
|
||||
|
||||
Assert.notNull(rootId, "rootId must not be null.");
|
||||
|
||||
this.rootId = rootId;
|
||||
}
|
||||
/**
|
||||
* @return the entity to persist. Guaranteed to be not {@code null}.
|
||||
*/
|
||||
T getEntity();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
protected void doExecuteWith(Interpreter interpreter) {
|
||||
interpreter.interpret(this);
|
||||
default Class<T> getEntityType() {
|
||||
return (Class<T>) getEntity().getClass();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an delete statement.
|
||||
* A {@link DbAction} not operation on the root of an aggregate but on its contained entities.
|
||||
*
|
||||
* @param <T> type o the entity for which this represents a database interaction
|
||||
* @author Jens Schauder
|
||||
* @since 1.0
|
||||
*/
|
||||
public static class DeleteAll<T> extends DbAction<T> {
|
||||
interface WithPropertyPath<T> extends DbAction<T> {
|
||||
|
||||
private DeleteAll(Class<T> entityType, @Nullable RelationalPropertyPath propertyPath, @Nullable DbAction dependingOn) {
|
||||
super(entityType, null, propertyPath, dependingOn);
|
||||
}
|
||||
/**
|
||||
* @return the path from the aggregate root to the affected entity
|
||||
*/
|
||||
PersistentPropertyPath<RelationalPersistentProperty> getPropertyPath();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
protected void doExecuteWith(Interpreter interpreter) {
|
||||
interpreter.interpret(this);
|
||||
default Class<T> getEntityType() {
|
||||
return (Class<T>) getPropertyPath().getRequiredLeafProperty().getActualType();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,13 +29,6 @@ public class DbActionExecutionException extends RuntimeException {
|
||||
* @param cause the underlying exception. May not be {@code null}.
|
||||
*/
|
||||
public DbActionExecutionException(DbAction<?> action, Throwable cause) {
|
||||
super( //
|
||||
String.format("Failed to execute %s for instance %s of type %s with path %s", //
|
||||
action.getClass().getSimpleName(), //
|
||||
action.getEntity(), //
|
||||
action.getEntityType(), //
|
||||
action.getPropertyPath() == null ? "" : action.getPropertyPath().toDotPath() //
|
||||
), //
|
||||
cause);
|
||||
super("Failed to execute " + action, cause);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,13 @@ package org.springframework.data.relational.core.conversion;
|
||||
|
||||
import org.springframework.data.relational.core.conversion.DbAction.Delete;
|
||||
import org.springframework.data.relational.core.conversion.DbAction.DeleteAll;
|
||||
import org.springframework.data.relational.core.conversion.DbAction.DeleteAllRoot;
|
||||
import org.springframework.data.relational.core.conversion.DbAction.DeleteRoot;
|
||||
import org.springframework.data.relational.core.conversion.DbAction.Insert;
|
||||
import org.springframework.data.relational.core.conversion.DbAction.InsertRoot;
|
||||
import org.springframework.data.relational.core.conversion.DbAction.Merge;
|
||||
import org.springframework.data.relational.core.conversion.DbAction.Update;
|
||||
import org.springframework.data.relational.core.conversion.DbAction.UpdateRoot;
|
||||
|
||||
/**
|
||||
* An {@link Interpreter} gets called by a {@link AggregateChange} for each {@link DbAction} and is tasked with
|
||||
@@ -31,6 +36,10 @@ import org.springframework.data.relational.core.conversion.DbAction.Update;
|
||||
*/
|
||||
public interface Interpreter {
|
||||
|
||||
<T> void interpret(Insert<T> insert);
|
||||
|
||||
<T> void interpret(InsertRoot<T> insert);
|
||||
|
||||
/**
|
||||
* Interpret an {@link Update}. Interpreting normally means "executing".
|
||||
*
|
||||
@@ -39,9 +48,15 @@ public interface Interpreter {
|
||||
*/
|
||||
<T> void interpret(Update<T> update);
|
||||
|
||||
<T> void interpret(Insert<T> insert);
|
||||
<T> void interpret(UpdateRoot<T> update);
|
||||
|
||||
<T> void interpret(Merge<T> update);
|
||||
|
||||
<T> void interpret(Delete<T> delete);
|
||||
|
||||
<T> void interpret(DeleteRoot<T> deleteRoot);
|
||||
|
||||
<T> void interpret(DeleteAll<T> delete);
|
||||
|
||||
<T> void interpret(DeleteAllRoot<T> DeleteAllRoot);
|
||||
}
|
||||
|
||||
@@ -15,8 +15,15 @@
|
||||
*/
|
||||
package org.springframework.data.relational.core.conversion;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.convert.EntityWriter;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Converts an entity that is about to be deleted into {@link DbAction}s inside a {@link AggregateChange} that need to
|
||||
@@ -25,10 +32,15 @@ import org.springframework.lang.Nullable;
|
||||
* @author Jens Schauder
|
||||
* @since 1.0
|
||||
*/
|
||||
public class RelationalEntityDeleteWriter extends RelationalEntityWriterSupport {
|
||||
public class RelationalEntityDeleteWriter implements EntityWriter<Object, AggregateChange<?>> {
|
||||
|
||||
private final RelationalMappingContext context;
|
||||
|
||||
public RelationalEntityDeleteWriter(RelationalMappingContext context) {
|
||||
super(context);
|
||||
|
||||
Assert.notNull(context, "Context must not be null");
|
||||
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,22 +63,41 @@ public class RelationalEntityDeleteWriter extends RelationalEntityWriterSupport
|
||||
|
||||
private void deleteAll(AggregateChange<?> aggregateChange) {
|
||||
|
||||
context.referencedEntities(aggregateChange.getEntityType(), null)
|
||||
.forEach(p -> aggregateChange.addAction(DbAction.deleteAll(p.getLeafType(), new RelationalPropertyPath(p), null)));
|
||||
List<DbAction> actions = new ArrayList<>();
|
||||
|
||||
aggregateChange.addAction(DbAction.deleteAll(aggregateChange.getEntityType(), null, null));
|
||||
context.findPersistentPropertyPaths(aggregateChange.getEntityType(), PersistentProperty::isEntity)
|
||||
.forEach(p -> actions.add(new DbAction.DeleteAll<>(p)));
|
||||
|
||||
Collections.reverse(actions);
|
||||
|
||||
actions.forEach(aggregateChange::addAction);
|
||||
|
||||
DbAction.DeleteAllRoot<?> result = new DbAction.DeleteAllRoot<>(aggregateChange.getEntityType());
|
||||
aggregateChange.addAction(result);
|
||||
}
|
||||
|
||||
private <T> void deleteById(Object id, AggregateChange<T> aggregateChange) {
|
||||
|
||||
deleteReferencedEntities(id, aggregateChange);
|
||||
|
||||
aggregateChange.addAction(DbAction.delete( //
|
||||
id, //
|
||||
aggregateChange.getEntityType(), //
|
||||
aggregateChange.getEntity(), //
|
||||
null, //
|
||||
null //
|
||||
));
|
||||
aggregateChange.addAction(new DbAction.DeleteRoot<>(aggregateChange.getEntityType(), id));
|
||||
}
|
||||
|
||||
/**
|
||||
* add {@link DbAction.Delete} actions to the {@link AggregateChange} for deleting all referenced entities.
|
||||
*
|
||||
* @param id id of the aggregate root, of which the referenced entities get deleted.
|
||||
* @param aggregateChange the change object to which the actions should get added. Must not be {@code null}
|
||||
*/
|
||||
private void deleteReferencedEntities(Object id, AggregateChange<?> aggregateChange) {
|
||||
|
||||
List<DbAction> actions = new ArrayList<>();
|
||||
|
||||
context.findPersistentPropertyPaths(aggregateChange.getEntityType(), PersistentProperty::isEntity)
|
||||
.forEach(p -> actions.add(new DbAction.Delete<>(id, p)));
|
||||
|
||||
Collections.reverse(actions);
|
||||
|
||||
actions.forEach(aggregateChange::addAction);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,209 +15,240 @@
|
||||
*/
|
||||
package org.springframework.data.relational.core.conversion;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NonNull;
|
||||
import lombok.Value;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.data.mapping.IdentifierAccessor;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.convert.EntityWriter;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.relational.core.conversion.DbAction.Insert;
|
||||
import org.springframework.data.relational.core.conversion.DbAction.Update;
|
||||
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.RelationalPersistentEntity;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.data.util.StreamUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Converts an entity that is about to be saved into {@link DbAction}s inside a {@link AggregateChange} that need to be
|
||||
* executed against the database to recreate the appropriate state in the database.
|
||||
* Converts an aggregate represented by its root into an {@link AggregateChange}.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
* @since 1.0
|
||||
*/
|
||||
public class RelationalEntityWriter extends RelationalEntityWriterSupport {
|
||||
public class RelationalEntityWriter implements EntityWriter<Object, AggregateChange<?>> {
|
||||
|
||||
private final RelationalMappingContext context;
|
||||
|
||||
public RelationalEntityWriter(RelationalMappingContext context) {
|
||||
|
||||
super(context);
|
||||
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an aggregate represented by its aggregate root into a list of {@link DbAction}s and adds them to the
|
||||
* {@link AggregateChange} passed in as an argument.
|
||||
*
|
||||
* @param aggregateRoot the aggregate root to be written to the {@link AggregateChange} Must not be {@code null}.
|
||||
* @param aggregateChange the {@link AggregateChange} to which the {@link DbAction}s get written.
|
||||
*/
|
||||
@Override
|
||||
public void write(Object aggregateRoot, AggregateChange aggregateChange) {
|
||||
public void write(Object root, AggregateChange<?> aggregateChange) {
|
||||
|
||||
Class<?> type = aggregateRoot.getClass();
|
||||
RelationalPropertyPath propertyPath = RelationalPropertyPath.from("", type);
|
||||
new WritingContext(root, aggregateChange).write();
|
||||
}
|
||||
|
||||
PersistentEntity<?, RelationalPersistentProperty> persistentEntity = context.getRequiredPersistentEntity(type);
|
||||
/**
|
||||
* Holds context information for the current write operation.
|
||||
*/
|
||||
private class WritingContext {
|
||||
|
||||
if (persistentEntity.isNew(aggregateRoot)) {
|
||||
private final Object root;
|
||||
private final AggregateChange<?> aggregateChange;
|
||||
private final PersistentPropertyPaths<?, RelationalPersistentProperty> paths;
|
||||
private final Map<PathNode, DbAction> previousActions = new HashMap<>();
|
||||
private Map<PersistentPropertyPath<RelationalPersistentProperty>, List<PathNode>> nodesCache = new HashMap<>();
|
||||
|
||||
Insert<Object> insert = DbAction.insert(aggregateRoot, propertyPath, null);
|
||||
WritingContext(Object root, AggregateChange<?> aggregateChange) {
|
||||
|
||||
this.root = root;
|
||||
this.aggregateChange = aggregateChange;
|
||||
|
||||
paths = context.findPersistentPropertyPaths(aggregateChange.getEntityType(), PersistentProperty::isEntity);
|
||||
|
||||
}
|
||||
|
||||
private void write() {
|
||||
|
||||
if (isNew(root)) {
|
||||
|
||||
setRootAction(new DbAction.InsertRoot<>(aggregateChange.getEntity()));
|
||||
insertReferenced();
|
||||
} else {
|
||||
deleteReferenced();
|
||||
|
||||
setRootAction(new DbAction.UpdateRoot<>(aggregateChange.getEntity()));
|
||||
insertReferenced();
|
||||
}
|
||||
}
|
||||
|
||||
//// Operations on all paths
|
||||
|
||||
private void insertReferenced() {
|
||||
|
||||
paths.forEach(this::insertAll);
|
||||
|
||||
}
|
||||
|
||||
private void insertAll(PersistentPropertyPath<RelationalPersistentProperty> path) {
|
||||
|
||||
from(path).forEach(node -> {
|
||||
|
||||
DbAction.Insert<Object> insert;
|
||||
if (node.path.getRequiredLeafProperty().isQualified()) {
|
||||
|
||||
KeyValue value = (KeyValue) node.getValue();
|
||||
insert = new DbAction.Insert<>(value.value, path, getAction(node.parent));
|
||||
insert.getAdditionalValues().put(node.path.getRequiredLeafProperty().getKeyColumn(), value.key);
|
||||
|
||||
} else {
|
||||
insert = new DbAction.Insert<>(node.getValue(), path, getAction(node.parent));
|
||||
}
|
||||
|
||||
previousActions.put(node, insert);
|
||||
aggregateChange.addAction(insert);
|
||||
});
|
||||
}
|
||||
|
||||
private void deleteReferenced() {
|
||||
|
||||
ArrayList<DbAction> deletes = new ArrayList<>();
|
||||
paths.forEach(path -> deletes.add(0, deleteReferenced(path)));
|
||||
|
||||
deletes.forEach(aggregateChange::addAction);
|
||||
}
|
||||
|
||||
/// Operations on a single path
|
||||
|
||||
private DbAction.Delete<?> deleteReferenced(PersistentPropertyPath<RelationalPersistentProperty> path) {
|
||||
|
||||
Object id = context.getRequiredPersistentEntity(aggregateChange.getEntityType())
|
||||
.getIdentifierAccessor(aggregateChange.getEntity()).getIdentifier();
|
||||
|
||||
return new DbAction.Delete<>(id, path);
|
||||
}
|
||||
|
||||
//// methods not directly related to the creation of DbActions
|
||||
|
||||
private void setRootAction(DbAction<?> insert) {
|
||||
|
||||
previousActions.put(null, insert);
|
||||
aggregateChange.addAction(insert);
|
||||
|
||||
referencedEntities(aggregateRoot) //
|
||||
.forEach( //
|
||||
propertyAndValue -> //
|
||||
insertReferencedEntities( //
|
||||
propertyAndValue, //
|
||||
aggregateChange, //
|
||||
propertyPath.nested(propertyAndValue.property.getName()), //
|
||||
insert) //
|
||||
);
|
||||
} else {
|
||||
|
||||
RelationalPersistentEntity<?> entity = context.getRequiredPersistentEntity(type);
|
||||
IdentifierAccessor identifierAccessor = entity.getIdentifierAccessor(aggregateRoot);
|
||||
|
||||
deleteReferencedEntities(identifierAccessor.getRequiredIdentifier(), aggregateChange);
|
||||
|
||||
Update<Object> update = DbAction.update(aggregateRoot, propertyPath, null);
|
||||
aggregateChange.addAction(update);
|
||||
|
||||
referencedEntities(aggregateRoot).forEach(propertyAndValue -> insertReferencedEntities(propertyAndValue,
|
||||
aggregateChange, propertyPath.nested(propertyAndValue.property.getName()), update));
|
||||
}
|
||||
}
|
||||
|
||||
private void insertReferencedEntities(PropertyAndValue propertyAndValue, AggregateChange aggregateChange,
|
||||
RelationalPropertyPath propertyPath, DbAction dependingOn) {
|
||||
|
||||
Insert<Object> insert;
|
||||
if (propertyAndValue.property.isQualified()) {
|
||||
|
||||
KeyValue valueAsEntry = (KeyValue) propertyAndValue.value;
|
||||
insert = DbAction.insert(valueAsEntry.getValue(), propertyPath, dependingOn);
|
||||
insert.getAdditionalValues().put(propertyAndValue.property.getKeyColumn(), valueAsEntry.getKey());
|
||||
} else {
|
||||
insert = DbAction.insert(propertyAndValue.value, propertyPath, dependingOn);
|
||||
}
|
||||
|
||||
aggregateChange.addAction(insert);
|
||||
referencedEntities(insert.getEntity()) //
|
||||
.forEach(pav -> insertReferencedEntities( //
|
||||
pav, //
|
||||
aggregateChange, //
|
||||
propertyPath.nested(pav.property.getName()), //
|
||||
dependingOn) //
|
||||
);
|
||||
}
|
||||
@Nullable
|
||||
private DbAction.WithEntity<?> getAction(@Nullable PathNode parent) {
|
||||
|
||||
private Stream<PropertyAndValue> referencedEntities(Object o) {
|
||||
|
||||
RelationalPersistentEntity<?> persistentEntity = context.getRequiredPersistentEntity(o.getClass());
|
||||
|
||||
return StreamUtils.createStreamFromIterator(persistentEntity.iterator()) //
|
||||
.filter(PersistentProperty::isEntity) //
|
||||
.flatMap( //
|
||||
p -> referencedEntity(p, persistentEntity.getPropertyAccessor(o)) //
|
||||
.map(e -> new PropertyAndValue(p, e)) //
|
||||
);
|
||||
}
|
||||
|
||||
private Stream<Object> referencedEntity(RelationalPersistentProperty p, PersistentPropertyAccessor propertyAccessor) {
|
||||
|
||||
Class<?> actualType = p.getActualType();
|
||||
RelationalPersistentEntity<?> persistentEntity = context //
|
||||
.getPersistentEntity(actualType);
|
||||
|
||||
if (persistentEntity == null) {
|
||||
return Stream.empty();
|
||||
DbAction action = previousActions.get(parent);
|
||||
if (action != null) {
|
||||
Assert.isInstanceOf(DbAction.WithEntity.class, action,
|
||||
"dependsOn action is not a WithEntity, but " + action.getClass().getSimpleName());
|
||||
return (DbAction.WithEntity<?>) action;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Class<?> type = p.getType();
|
||||
|
||||
if (List.class.isAssignableFrom(type)) {
|
||||
return listPropertyAsStream(p, propertyAccessor);
|
||||
private boolean isNew(Object o) {
|
||||
return context.getRequiredPersistentEntity(o.getClass()).isNew(o);
|
||||
}
|
||||
|
||||
if (Collection.class.isAssignableFrom(type)) {
|
||||
return collectionPropertyAsStream(p, propertyAccessor);
|
||||
private List<WritingContext.PathNode> from(PersistentPropertyPath<RelationalPersistentProperty> path) {
|
||||
|
||||
List<WritingContext.PathNode> nodes = new ArrayList<>();
|
||||
if (path.getLength() == 1) {
|
||||
|
||||
Object value = context //
|
||||
.getRequiredPersistentEntity(aggregateChange.getEntityType()) //
|
||||
.getPropertyAccessor(aggregateChange.getEntity()) //
|
||||
.getProperty(path.getRequiredLeafProperty());
|
||||
|
||||
nodes.addAll(createNodes(path, null, value));
|
||||
|
||||
} else {
|
||||
|
||||
List<PathNode> pathNodes = nodesCache.get(path.getParentPath());
|
||||
pathNodes.forEach(parentNode -> {
|
||||
|
||||
Object value = path.getRequiredLeafProperty().getOwner().getPropertyAccessor(parentNode.value)
|
||||
.getProperty(path.getRequiredLeafProperty());
|
||||
|
||||
nodes.addAll(createNodes(path, parentNode, value));
|
||||
});
|
||||
}
|
||||
|
||||
nodesCache.put(path, nodes);
|
||||
|
||||
return nodes;
|
||||
|
||||
}
|
||||
|
||||
if (Map.class.isAssignableFrom(type)) {
|
||||
return mapPropertyAsStream(p, propertyAccessor);
|
||||
private List<PathNode> createNodes(PersistentPropertyPath<RelationalPersistentProperty> path,
|
||||
@Nullable PathNode parentNode, @Nullable Object value) {
|
||||
|
||||
if (value == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<WritingContext.PathNode> nodes = new ArrayList<>();
|
||||
|
||||
if (path.getRequiredLeafProperty().isQualified()) {
|
||||
|
||||
if (path.getRequiredLeafProperty().isMap()) {
|
||||
((Map<?, ?>) value).forEach((k, v) -> nodes.add(new PathNode(path, parentNode, new KeyValue(k, v))));
|
||||
} else {
|
||||
|
||||
List listValue = (List) value;
|
||||
for (int k = 0; k < listValue.size(); k++) {
|
||||
nodes.add(new PathNode(path, parentNode, new KeyValue(k, listValue.get(k))));
|
||||
}
|
||||
}
|
||||
} else if (path.getRequiredLeafProperty().isCollectionLike()) { // collection value
|
||||
((Collection<?>) value).forEach(v -> nodes.add(new PathNode(path, parentNode, v)));
|
||||
} else { // single entity value
|
||||
nodes.add(new PathNode(path, parentNode, value));
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
return singlePropertyAsStream(p, propertyAccessor);
|
||||
}
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
private class PathNode {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Stream<Object> collectionPropertyAsStream(RelationalPersistentProperty p,
|
||||
PersistentPropertyAccessor propertyAccessor) {
|
||||
private final PersistentPropertyPath<RelationalPersistentProperty> path;
|
||||
@Nullable private final PathNode parent;
|
||||
private final Object value;
|
||||
|
||||
Object property = propertyAccessor.getProperty(p);
|
||||
private PathNode(PersistentPropertyPath<RelationalPersistentProperty> path, @Nullable PathNode parent,
|
||||
Object value) {
|
||||
|
||||
return property == null //
|
||||
? Stream.empty() //
|
||||
: ((Collection<Object>) property).stream();
|
||||
}
|
||||
this.path = path;
|
||||
this.parent = parent;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Stream<Object> listPropertyAsStream(RelationalPersistentProperty p, PersistentPropertyAccessor propertyAccessor) {
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
Object property = propertyAccessor.getProperty(p);
|
||||
|
||||
if (property == null) {
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
// ugly hackery since Java streams don't have a zip method.
|
||||
AtomicInteger index = new AtomicInteger();
|
||||
List<Object> listProperty = (List<Object>) property;
|
||||
|
||||
return listProperty.stream().map(e -> new KeyValue(index.getAndIncrement(), e));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Stream<Object> mapPropertyAsStream(RelationalPersistentProperty p, PersistentPropertyAccessor propertyAccessor) {
|
||||
|
||||
Object property = propertyAccessor.getProperty(p);
|
||||
|
||||
return property == null //
|
||||
? Stream.empty() //
|
||||
: ((Map<Object, Object>) property).entrySet().stream().map(e -> new KeyValue(e.getKey(), e.getValue()));
|
||||
}
|
||||
|
||||
private Stream<Object> singlePropertyAsStream(RelationalPersistentProperty p, PersistentPropertyAccessor propertyAccessor) {
|
||||
|
||||
Object property = propertyAccessor.getProperty(p);
|
||||
if (property == null) {
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
return Stream.of(property);
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds key and value of a {@link Map.Entry} but without any ties to {@link Map} implementations.
|
||||
*/
|
||||
@Data
|
||||
@Value
|
||||
private static class KeyValue {
|
||||
private final Object key;
|
||||
private final Object value;
|
||||
@NonNull Object key;
|
||||
@NonNull Object value;
|
||||
}
|
||||
|
||||
@Data
|
||||
private static class PropertyAndValue {
|
||||
|
||||
private final RelationalPersistentProperty property;
|
||||
private final Object value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017-2018 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
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.relational.core.conversion;
|
||||
|
||||
import org.springframework.data.convert.EntityWriter;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Common infrastructure needed by different implementations of {@link EntityWriter}<Object, AggregateChange>.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
* @since 1.0
|
||||
*/
|
||||
abstract class RelationalEntityWriterSupport implements EntityWriter<Object, AggregateChange<?>> {
|
||||
protected final RelationalMappingContext context;
|
||||
|
||||
RelationalEntityWriterSupport(RelationalMappingContext context) {
|
||||
|
||||
Assert.notNull(context, "Context must not be null");
|
||||
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* add {@link org.springframework.data.relational.core.conversion.DbAction.Delete} actions to the {@link AggregateChange}
|
||||
* for deleting all referenced entities.
|
||||
*
|
||||
* @param id id of the aggregate root, of which the referenced entities get deleted.
|
||||
* @param aggregateChange the change object to which the actions should get added. Must not be {@code null}
|
||||
*/
|
||||
void deleteReferencedEntities(Object id, AggregateChange<?> aggregateChange) {
|
||||
|
||||
context.referencedEntities(aggregateChange.getEntityType(), null).forEach(
|
||||
p -> aggregateChange.addAction(DbAction.delete(id, p.getLeafType(), null, new RelationalPropertyPath(p), null)));
|
||||
}
|
||||
}
|
||||
@@ -65,32 +65,6 @@ public class RelationalMappingContext extends AbstractMappingContext<RelationalP
|
||||
setSimpleTypeHolder(new SimpleTypeHolder(Collections.emptySet(), true));
|
||||
}
|
||||
|
||||
/**
|
||||
* returns all {@link PropertyPath}s reachable from the root type in the order needed for deleting, i.e. the deepest
|
||||
* reference first.
|
||||
*/
|
||||
public List<PropertyPath> referencedEntities(Class<?> rootType, @Nullable PropertyPath path) {
|
||||
|
||||
List<PropertyPath> paths = new ArrayList<>();
|
||||
|
||||
Class<?> currentType = path == null ? rootType : path.getLeafType();
|
||||
RelationalPersistentEntity<?> persistentEntity = getRequiredPersistentEntity(currentType);
|
||||
|
||||
for (RelationalPersistentProperty property : persistentEntity) {
|
||||
if (property.isEntity()) {
|
||||
|
||||
PropertyPath nextPath = path == null ? PropertyPath.from(property.getName(), rootType)
|
||||
: path.nested(property.getName());
|
||||
paths.add(nextPath);
|
||||
paths.addAll(referencedEntities(rootType, nextPath));
|
||||
}
|
||||
}
|
||||
|
||||
Collections.reverse(paths);
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mapping.context.AbstractMappingContext#createPersistentEntity(org.springframework.data.util.TypeInformation)
|
||||
|
||||
Reference in New Issue
Block a user