Skip to content

Commit e1450ea

Browse files
committed
DATAJDBC-241 - Support for immutable entities.
Immutable entities can now be saved and loaded and the immutability will be honored. This feature is currently limited to a single level of reference. In order to implement this the logic for updating IDs with those generated from the database got moved out of the DefaultDataAccessStrategy into the AggregateChange. As part of that move DataAccessStrategy.save now returns a generated id, if available. See also: DATAJDBC-248.
1 parent b75bd3d commit e1450ea

23 files changed

+645
-118
lines changed

src/main/java/org/springframework/data/jdbc/core/CascadingDataAccessStrategy.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public CascadingDataAccessStrategy(List<DataAccessStrategy> strategies) {
4343
* @see org.springframework.data.jdbc.core.DataAccessStrategy#insert(java.lang.Object, java.lang.Class, java.util.Map)
4444
*/
4545
@Override
46-
public <T> T insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) {
46+
public <T> Object insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) {
4747
return collect(das -> das.insert(instance, domainType, additionalParameters));
4848
}
4949

src/main/java/org/springframework/data/jdbc/core/DataAccessStrategy.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,9 @@ public interface DataAccessStrategy {
3838
* @param additionalParameters name-value pairs of additional parameters. Especially ids of parent entities that need
3939
* to get referenced are contained in this map. Must not be {@code null}.
4040
* @param <T> the type of the instance.
41-
* @return the instance after insert into. The returned instance may be different to the given {@code instance} as
42-
* this method can apply changes to the return object.
41+
* @return the id generated by the database if any.
4342
*/
44-
<T> T insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters);
43+
<T> Object insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters);
4544

4645
/**
4746
* Updates the data of a single entity in the database. Referenced entities don't get handled.

src/main/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategy.java

Lines changed: 6 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,11 @@
2121
import java.util.HashMap;
2222
import java.util.LinkedHashMap;
2323
import java.util.Map;
24-
import java.util.Optional;
2524
import java.util.stream.Collectors;
2625
import java.util.stream.StreamSupport;
2726

2827
import org.springframework.dao.EmptyResultDataAccessException;
2928
import org.springframework.dao.InvalidDataAccessApiUsageException;
30-
import org.springframework.dao.NonTransientDataAccessException;
3129
import org.springframework.data.jdbc.support.JdbcUtil;
3230
import org.springframework.data.mapping.PersistentPropertyAccessor;
3331
import org.springframework.data.mapping.PersistentPropertyPath;
@@ -83,7 +81,7 @@ public DefaultDataAccessStrategy(SqlGeneratorSource sqlGeneratorSource, Relation
8381
* @see org.springframework.data.jdbc.core.DataAccessStrategy#insert(java.lang.Object, java.lang.Class, java.util.Map)
8482
*/
8583
@Override
86-
public <T> T insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) {
84+
public <T> Object insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) {
8785

8886
KeyHolder holder = new GeneratedKeyHolder();
8987
RelationalPersistentEntity<T> persistentEntity = getRequiredPersistentEntity(domainType);
@@ -110,16 +108,7 @@ public <T> T insert(T instance, Class<T> domainType, Map<String, Object> additio
110108
holder //
111109
);
112110

113-
instance = setIdFromJdbc(instance, holder, persistentEntity);
114-
115-
// if there is an id property and it was null before the save
116-
// The database should have created an id and provided it.
117-
118-
if (idProperty != null && idValue == null && persistentEntity.isNew(instance)) {
119-
throw new IllegalStateException(String.format(ENTITY_NEW_AFTER_INSERT, persistentEntity));
120-
}
121-
122-
return instance;
111+
return getIdFromHolder(holder, persistentEntity);
123112
}
124113

125114
/*
@@ -327,41 +316,20 @@ private static <S, ID> boolean isIdPropertyNullOrScalarZero(@Nullable ID idValue
327316
|| (idProperty.getType() == long.class && idValue.equals(0L));
328317
}
329318

330-
private <S> S setIdFromJdbc(S instance, KeyHolder holder, RelationalPersistentEntity<S> persistentEntity) {
331-
332-
try {
333-
334-
PersistentPropertyAccessor<S> accessor = converter.getPropertyAccessor(persistentEntity, instance);
335-
336-
getIdFromHolder(holder, persistentEntity).ifPresent(it -> {
337-
338-
RelationalPersistentProperty idProperty = persistentEntity.getRequiredIdProperty();
339-
340-
accessor.setProperty(idProperty, it);
341-
342-
});
343-
344-
return accessor.getBean();
345-
346-
} catch (NonTransientDataAccessException e) {
347-
throw new UnableToSetId("Unable to set id of " + instance, e);
348-
}
349-
}
350-
351-
private <S> Optional<Object> getIdFromHolder(KeyHolder holder, RelationalPersistentEntity<S> persistentEntity) {
319+
private <S> Object getIdFromHolder(KeyHolder holder, RelationalPersistentEntity<S> persistentEntity) {
352320

353321
try {
354322
// MySQL just returns one value with a special name
355-
return Optional.ofNullable(holder.getKey());
323+
return holder.getKey();
356324
} catch (InvalidDataAccessApiUsageException e) {
357325
// Postgres returns a value for each column
358326
Map<String, Object> keys = holder.getKeys();
359327

360328
if (keys == null || persistentEntity.getIdProperty() == null) {
361-
return Optional.empty();
329+
return null;
362330
}
363331

364-
return Optional.ofNullable(keys.get(persistentEntity.getIdColumn()));
332+
return keys.get(persistentEntity.getIdColumn());
365333
}
366334
}
367335

src/main/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreter.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,9 @@ class DefaultJdbcInterpreter implements Interpreter {
5858
@Override
5959
public <T> void interpret(Insert<T> insert) {
6060

61-
T entity = accessStrategy.insert(insert.getEntity(), insert.getEntityType(), createAdditionalColumnValues(insert));
62-
insert.setResultingEntity(entity);
61+
Object id = accessStrategy.insert(insert.getEntity(), insert.getEntityType(), createAdditionalColumnValues(insert));
62+
63+
insert.setGeneratedId(id);
6364
}
6465

6566
/*
@@ -69,16 +70,16 @@ public <T> void interpret(Insert<T> insert) {
6970
@Override
7071
public <T> void interpret(InsertRoot<T> insert) {
7172

72-
T entity = accessStrategy.insert(insert.getEntity(), insert.getEntityType(), Collections.emptyMap());
73-
insert.setResultingEntity(entity);
73+
Object id = accessStrategy.insert(insert.getEntity(), insert.getEntityType(), Collections.emptyMap());
74+
insert.setGeneratedId(id);
7475
}
7576

7677
/*
7778
* (non-Javadoc)
7879
* @see org.springframework.data.relational.core.conversion.Interpreter#interpret(org.springframework.data.relational.core.conversion.DbAction.Update)
7980
*/
8081
@Override
81-
public <T> void interpret(Update<T> update) {
82+
public <T> void interpret(Update<T> update ) {
8283
accessStrategy.update(update.getEntity(), update.getEntityType());
8384
}
8485

@@ -169,8 +170,8 @@ private Object getIdFromEntityDependingOn(DbAction.WithEntity<?> dependingOn,
169170

170171
Object entity = dependingOn.getEntity();
171172

172-
if (dependingOn instanceof DbAction.WithResultEntity) {
173-
entity = ((DbAction.WithResultEntity<?>) dependingOn).getResultingEntity();
173+
if (dependingOn instanceof DbAction.WithGeneratedId) {
174+
return ((DbAction.WithGeneratedId<?>) dependingOn).getGeneratedId();
174175
}
175176

176177
return persistentEntity.getIdentifierAccessor(entity).getIdentifier();

src/main/java/org/springframework/data/jdbc/core/DelegatingDataAccessStrategy.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public class DelegatingDataAccessStrategy implements DataAccessStrategy {
3636
* @see org.springframework.data.jdbc.core.DataAccessStrategy#insert(java.lang.Object, java.lang.Class, java.util.Map)
3737
*/
3838
@Override
39-
public <T> T insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) {
39+
public <T> Object insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) {
4040
return delegate.insert(instance, domainType, additionalParameters);
4141
}
4242

src/main/java/org/springframework/data/jdbc/core/JdbcAggregateTemplate.java

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.springframework.data.relational.core.conversion.AggregateChange;
2323
import org.springframework.data.relational.core.conversion.AggregateChange.Kind;
2424
import org.springframework.data.relational.core.conversion.Interpreter;
25+
import org.springframework.data.relational.core.conversion.RelationalConverter;
2526
import org.springframework.data.relational.core.conversion.RelationalEntityDeleteWriter;
2627
import org.springframework.data.relational.core.conversion.RelationalEntityWriter;
2728
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
@@ -46,6 +47,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
4647

4748
private final ApplicationEventPublisher publisher;
4849
private final RelationalMappingContext context;
50+
private final RelationalConverter converter;
4951
private final Interpreter interpreter;
5052

5153
private final RelationalEntityWriter jdbcEntityWriter;
@@ -62,14 +64,16 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
6264
* @param dataAccessStrategy must not be {@literal null}.
6365
*/
6466
public JdbcAggregateTemplate(ApplicationEventPublisher publisher, RelationalMappingContext context,
65-
DataAccessStrategy dataAccessStrategy) {
67+
RelationalConverter converter, DataAccessStrategy dataAccessStrategy) {
6668

6769
Assert.notNull(publisher, "ApplicationEventPublisher must not be null!");
6870
Assert.notNull(context, "RelationalMappingContext must not be null!");
71+
Assert.notNull(converter, "RelationalConverter must not be null!");
6972
Assert.notNull(dataAccessStrategy, "DataAccessStrategy must not be null!");
7073

7174
this.publisher = publisher;
7275
this.context = context;
76+
this.converter = converter;
7377
this.accessStrategy = dataAccessStrategy;
7478

7579
this.jdbcEntityWriter = new RelationalEntityWriter(context);
@@ -86,8 +90,8 @@ public <T> T save(T instance) {
8690

8791
Assert.notNull(instance, "Aggregate instance must not be null!");
8892

89-
RelationalPersistentEntity<?> entity = context.getRequiredPersistentEntity(instance.getClass());
90-
IdentifierAccessor identifierAccessor = entity.getIdentifierAccessor(instance);
93+
RelationalPersistentEntity<?> persistentEntity = context.getRequiredPersistentEntity(instance.getClass());
94+
IdentifierAccessor identifierAccessor = persistentEntity.getIdentifierAccessor(instance);
9195

9296
AggregateChange<T> change = createChange(instance);
9397

@@ -97,9 +101,9 @@ public <T> T save(T instance) {
97101
change //
98102
));
99103

100-
change.executeWith(interpreter);
104+
change.executeWith(interpreter, context, converter);
101105

102-
Object identifier = entity.getIdentifierAccessor(change.getEntity()).getIdentifier();
106+
Object identifier = persistentEntity.getIdentifierAccessor(change.getEntity()).getIdentifier();
103107

104108
Assert.notNull(identifier, "After saving the identifier must not be null");
105109

@@ -198,7 +202,7 @@ public <S> void deleteById(Object id, Class<S> domainType) {
198202
public void deleteAll(Class<?> domainType) {
199203

200204
AggregateChange<?> change = createDeletingChange(domainType);
201-
change.executeWith(interpreter);
205+
change.executeWith(interpreter, context, converter);
202206
}
203207

204208
private void deleteTree(Object id, @Nullable Object entity, Class<?> domainType) {
@@ -209,7 +213,7 @@ private void deleteTree(Object id, @Nullable Object entity, Class<?> domainType)
209213
Optional<Object> optionalEntity = Optional.ofNullable(entity);
210214
publisher.publishEvent(new BeforeDeleteEvent(specifiedId, optionalEntity, change));
211215

212-
change.executeWith(interpreter);
216+
change.executeWith(interpreter, context, converter);
213217

214218
publisher.publishEvent(new AfterDeleteEvent(specifiedId, optionalEntity, change));
215219
}

src/main/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategy.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,12 +128,13 @@ public void setNamespaceStrategy(NamespaceStrategy namespaceStrategy) {
128128
* @see org.springframework.data.jdbc.core.DataAccessStrategy#insert(java.lang.Object, java.lang.Class, java.util.Map)
129129
*/
130130
@Override
131-
public <T> T insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) {
132-
131+
public <T> Object insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) {
132+
133+
MyBatisContext myBatisContext = new MyBatisContext(null, instance, domainType, additionalParameters);
133134
sqlSession().insert(namespace(domainType) + ".insert",
134-
new MyBatisContext(null, instance, domainType, additionalParameters));
135+
myBatisContext);
135136

136-
return instance;
137+
return myBatisContext.getId();
137138
}
138139

139140
/*

src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ public <T, ID> EntityInformation<T, ID> getEntityInformation(Class<T> aClass) {
104104
@Override
105105
protected Object getTargetRepository(RepositoryInformation repositoryInformation) {
106106

107-
JdbcAggregateTemplate template = new JdbcAggregateTemplate(publisher, context, accessStrategy);
107+
JdbcAggregateTemplate template = new JdbcAggregateTemplate(publisher, context, converter, accessStrategy);
108108

109109
return new SimpleJdbcRepository<>(template, context.getPersistentEntity(repositoryInformation.getDomainType()));
110110
}

0 commit comments

Comments
 (0)