DATAJDBC-233 - Changed API in order to support immutable entities.
Note: Immutable entities still aren't supported yet, these are just the API changes plus implementations that work for mutable entities. The API of DbActions will need further evolution. Original pull request: #80.
This commit is contained in:
committed by
Mark Paluch
parent
962513627c
commit
1085918135
@@ -43,8 +43,8 @@ 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> void insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) {
|
||||
collectVoid(das -> das.insert(instance, domainType, additionalParameters));
|
||||
public <T> T insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) {
|
||||
return collect(das -> das.insert(instance, domainType, additionalParameters));
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -39,7 +39,7 @@ public interface DataAccessStrategy {
|
||||
* to get referenced are contained in this map. Must not be {@code null}.
|
||||
* @param <T> the type of the instance.
|
||||
*/
|
||||
<T> void insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters);
|
||||
<T> T 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.
|
||||
|
||||
@@ -79,7 +79,7 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) {
|
||||
public <T> T insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) {
|
||||
|
||||
KeyHolder holder = new GeneratedKeyHolder();
|
||||
RelationalPersistentEntity<T> persistentEntity = getRequiredPersistentEntity(domainType);
|
||||
@@ -106,7 +106,7 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
|
||||
holder //
|
||||
);
|
||||
|
||||
setIdFromJdbc(instance, holder, persistentEntity);
|
||||
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.
|
||||
@@ -114,6 +114,8 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
|
||||
if (idProperty != null && idValue == null && persistentEntity.isNew(instance)) {
|
||||
throw new IllegalStateException(String.format(ENTITY_NEW_AFTER_INSERT, persistentEntity));
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -321,18 +323,22 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
|
||||
|| (idProperty.getType() == long.class && idValue.equals(0L));
|
||||
}
|
||||
|
||||
private <S> void setIdFromJdbc(S instance, KeyHolder holder, RelationalPersistentEntity<S> persistentEntity) {
|
||||
private <S> S setIdFromJdbc(S instance, KeyHolder holder, RelationalPersistentEntity<S> persistentEntity) {
|
||||
|
||||
try {
|
||||
|
||||
PersistentPropertyAccessor<S> accessor = converter.getPropertyAccessor(persistentEntity, instance);
|
||||
|
||||
getIdFromHolder(holder, persistentEntity).ifPresent(it -> {
|
||||
|
||||
PersistentPropertyAccessor<S> accessor = converter.getPropertyAccessor(persistentEntity, instance);
|
||||
RelationalPersistentProperty idProperty = persistentEntity.getRequiredIdProperty();
|
||||
|
||||
accessor.setProperty(idProperty, it);
|
||||
|
||||
});
|
||||
|
||||
return accessor.getBean();
|
||||
|
||||
} catch (NonTransientDataAccessException e) {
|
||||
throw new UnableToSetId("Unable to set id of " + instance, e);
|
||||
}
|
||||
|
||||
@@ -57,7 +57,9 @@ class DefaultJdbcInterpreter implements Interpreter {
|
||||
*/
|
||||
@Override
|
||||
public <T> void interpret(Insert<T> insert) {
|
||||
accessStrategy.insert(insert.getEntity(), insert.getEntityType(), createAdditionalColumnValues(insert));
|
||||
|
||||
T entity = accessStrategy.insert(insert.getEntity(), insert.getEntityType(), createAdditionalColumnValues(insert));
|
||||
insert.setResultingEntity(entity);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -66,7 +68,9 @@ class DefaultJdbcInterpreter implements Interpreter {
|
||||
*/
|
||||
@Override
|
||||
public <T> void interpret(InsertRoot<T> insert) {
|
||||
accessStrategy.insert(insert.getEntity(), insert.getEntityType(), Collections.emptyMap());
|
||||
|
||||
T entity = accessStrategy.insert(insert.getEntity(), insert.getEntityType(), Collections.emptyMap());
|
||||
insert.setResultingEntity(entity);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -162,7 +166,14 @@ class DefaultJdbcInterpreter implements Interpreter {
|
||||
@Nullable
|
||||
private Object getIdFromEntityDependingOn(DbAction.WithEntity<?> dependingOn,
|
||||
RelationalPersistentEntity<?> persistentEntity) {
|
||||
return persistentEntity.getIdentifierAccessor(dependingOn.getEntity()).getIdentifier();
|
||||
|
||||
Object entity = dependingOn.getEntity();
|
||||
|
||||
if (dependingOn instanceof DbAction.WithResultEntity) {
|
||||
entity = ((DbAction.WithResultEntity<?>) dependingOn).getResultingEntity();
|
||||
}
|
||||
|
||||
return persistentEntity.getIdentifierAccessor(entity).getIdentifier();
|
||||
}
|
||||
|
||||
private String getColumnNameForReverseColumn(DbAction.WithPropertyPath<?> action) {
|
||||
|
||||
@@ -32,8 +32,8 @@ public class DelegatingDataAccessStrategy implements DataAccessStrategy {
|
||||
private DataAccessStrategy delegate;
|
||||
|
||||
@Override
|
||||
public <T> void insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) {
|
||||
delegate.insert(instance, domainType, additionalParameters);
|
||||
public <T> T insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) {
|
||||
return delegate.insert(instance, domainType, additionalParameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -142,14 +142,11 @@ public class EntityRowMapper<T> implements RowMapper<T> {
|
||||
return converter.createInstance(entity, parameter -> {
|
||||
|
||||
String parameterName = parameter.getName();
|
||||
Assert.notNull(parameterName, "A constructor parameter name must not be null to be used with Spring Data JDBC");
|
||||
String column = prefix + entity.getRequiredPersistentProperty(parameterName).getColumnName();
|
||||
|
||||
try {
|
||||
return rs.getObject(column);
|
||||
} catch (SQLException o_O) {
|
||||
throw new MappingException(String.format("Couldn't read column %s from ResultSet.", column), o_O);
|
||||
}
|
||||
Assert.notNull(parameterName, "A constructor parameter name must not be null to be used with Spring Data JDBC");
|
||||
|
||||
RelationalPersistentProperty property = entity.getRequiredPersistentProperty(parameterName);
|
||||
return readFrom(rs, property, prefix);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ public interface JdbcAggregateOperations {
|
||||
* @param instance the aggregate root of the aggregate to be saved. Must not be {@code null}.
|
||||
* @param <T> the type of the aggregate root.
|
||||
*/
|
||||
<T> void save(T instance);
|
||||
<T> T save(T instance);
|
||||
|
||||
/**
|
||||
* Deletes a single Aggregate including all entities contained in that aggregate.
|
||||
|
||||
@@ -66,7 +66,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void save(T instance) {
|
||||
public <T> T save(T instance) {
|
||||
|
||||
Assert.notNull(instance, "Aggregate instance must not be null!");
|
||||
|
||||
@@ -83,15 +83,17 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
|
||||
|
||||
change.executeWith(interpreter);
|
||||
|
||||
Object identifier = identifierAccessor.getIdentifier();
|
||||
Object identifier = entity.getIdentifierAccessor(change.getEntity()).getIdentifier();
|
||||
|
||||
Assert.notNull(identifier, "After saving the identifier must not be null");
|
||||
|
||||
publisher.publishEvent(new AfterSaveEvent( //
|
||||
Identifier.of(identifier), //
|
||||
instance, //
|
||||
change.getEntity(), //
|
||||
change //
|
||||
));
|
||||
|
||||
return (T) change.getEntity();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -128,9 +128,12 @@ 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> void insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) {
|
||||
public <T> T insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) {
|
||||
|
||||
sqlSession().insert(namespace(domainType) + ".insert",
|
||||
new MyBatisContext(null, instance, domainType, additionalParameters));
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -27,7 +27,6 @@ import java.util.List;
|
||||
* @author Jens Schauder
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public class AggregateChange<T> {
|
||||
|
||||
@@ -37,12 +36,28 @@ public class AggregateChange<T> {
|
||||
private final Class<T> entityType;
|
||||
|
||||
/** Aggregate root, to which the change applies, if available */
|
||||
private final T entity;
|
||||
private T entity;
|
||||
|
||||
private final List<DbAction<?>> actions = new ArrayList<>();
|
||||
|
||||
public AggregateChange(Kind kind, Class<T> entityType, T entity) {
|
||||
|
||||
this.kind = kind;
|
||||
this.entityType = entityType;
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void executeWith(Interpreter interpreter) {
|
||||
actions.forEach(a -> a.executeWith(interpreter));
|
||||
|
||||
actions.forEach(a -> {
|
||||
|
||||
a.executeWith(interpreter);
|
||||
|
||||
if (a instanceof DbAction.InsertRoot && a.getEntityType().equals(entityType)) {
|
||||
entity = (T) ((DbAction.InsertRoot<?>) a).getResultingEntity();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void addAction(DbAction<?> action) {
|
||||
|
||||
@@ -15,7 +15,11 @@
|
||||
*/
|
||||
package org.springframework.data.relational.core.conversion;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import lombok.Value;
|
||||
|
||||
import java.util.HashMap;
|
||||
@@ -64,15 +68,20 @@ public interface DbAction<T> {
|
||||
*
|
||||
* @param <T> type of the entity for which this represents a database interaction.
|
||||
*/
|
||||
@Value
|
||||
class Insert<T> implements WithDependingOn<T>, WithEntity<T> {
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@RequiredArgsConstructor
|
||||
class Insert<T> implements WithDependingOn<T>, WithEntity<T>, WithResultEntity<T> {
|
||||
|
||||
@NonNull T entity;
|
||||
@NonNull PersistentPropertyPath<RelationalPersistentProperty> propertyPath;
|
||||
@NonNull WithEntity<?> dependingOn;
|
||||
@NonNull private final T entity;
|
||||
@NonNull private final PersistentPropertyPath<RelationalPersistentProperty> propertyPath;
|
||||
@NonNull private final WithEntity<?> dependingOn;
|
||||
|
||||
Map<String, Object> additionalValues = new HashMap<>();
|
||||
|
||||
private T resultingEntity;
|
||||
|
||||
@Override
|
||||
public void doExecuteWith(Interpreter interpreter) {
|
||||
interpreter.interpret(this);
|
||||
@@ -89,10 +98,15 @@ public interface DbAction<T> {
|
||||
*
|
||||
* @param <T> type of the entity for which this represents a database interaction.
|
||||
*/
|
||||
@Value
|
||||
class InsertRoot<T> implements WithEntity<T> {
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@RequiredArgsConstructor
|
||||
class InsertRoot<T> implements WithEntity<T>, WithResultEntity<T> {
|
||||
|
||||
@NonNull T entity;
|
||||
@NonNull private final T entity;
|
||||
|
||||
private T resultingEntity;
|
||||
|
||||
@Override
|
||||
public void doExecuteWith(Interpreter interpreter) {
|
||||
@@ -272,6 +286,26 @@ public interface DbAction<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link DbAction} that may "update" its entity. In order to support immutable entities this requires at least
|
||||
* potentially creating a new instance, which this interface makes available.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
interface WithResultEntity<T> extends WithEntity<T> {
|
||||
|
||||
/**
|
||||
* @return the entity to persist. Guaranteed to be not {@code null}.
|
||||
*/
|
||||
T getResultingEntity();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
default Class<T> getEntityType() {
|
||||
return (Class<T>) getEntity().getClass();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link DbAction} not operation on the root of an aggregate but on its contained entities.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user