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:
Jens Schauder
2018-07-23 10:41:16 +02:00
committed by Mark Paluch
parent 962513627c
commit 1085918135
17 changed files with 124 additions and 45 deletions

View File

@@ -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));
}
/*

View File

@@ -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.

View File

@@ -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);
}

View File

@@ -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) {

View File

@@ -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

View File

@@ -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);
});
}
}

View File

@@ -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.

View File

@@ -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

View File

@@ -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;
}
/*

View File

@@ -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) {

View File

@@ -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.
*