DATAJDBC-145 - Back-reference in insert now honours NamingStrategy.

Added a property path to DbActions in order to make them aware of the property it is related to.
Then used the JdbcPersistentProperty based on that get the name of the back-reference name of that property back to the root entity of that property.
This gets the NamingStrategy properly involved, as it should.

Instead of using the vanilla PropertyPath a new JdbcPropertyPath is introduced.
It allows for an empty path avoiding various null-checks.

Upgraded SD-Commons dependency to 2.1.x in order to utilise DATACMNS-1199.
Removed the now superfluous PropertyPaths and used PropertyPath methods instead.

Related issues:
DATACMNS-1204
DATACMSN-1199
This commit is contained in:
Jens Schauder
2017-10-30 13:56:47 +01:00
committed by Greg Turnquist
parent eeafc04fde
commit 207278c891
13 changed files with 283 additions and 164 deletions

View File

@@ -26,6 +26,8 @@ import org.springframework.data.jdbc.core.conversion.DbAction.Update;
import org.springframework.data.jdbc.core.conversion.Interpreter;
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.util.Assert;
/**
* {@link Interpreter} for {@link DbAction}s using a {@link DataAccessStrategy} for performing actual database
@@ -60,15 +62,15 @@ class DefaultJdbcInterpreter implements Interpreter {
if (delete.getPropertyPath() == null) {
accessStrategy.delete(delete.getRootId(), delete.getEntityType());
} else {
accessStrategy.delete(delete.getRootId(), delete.getPropertyPath());
accessStrategy.delete(delete.getRootId(), delete.getPropertyPath().getPath());
}
}
@Override
public <T> void interpret(DeleteAll<T> delete) {
if (delete.getEntityType() == null) {
accessStrategy.deleteAll(delete.getPropertyPath());
accessStrategy.deleteAll(delete.getPropertyPath().getPath());
} else {
accessStrategy.deleteAll(delete.getEntityType());
}
@@ -84,16 +86,32 @@ class DefaultJdbcInterpreter implements Interpreter {
}
private <T> void addDependingOnInformation(Insert<T> insert, Map<String, Object> additionalColumnValues) {
DbAction dependingOn = insert.getDependingOn();
if (dependingOn != null) {
JdbcPersistentEntity<?> persistentEntity = context.getRequiredPersistentEntity(dependingOn.getEntityType());
String columnName = persistentEntity.getTableName();
Object entity = dependingOn.getEntity();
Object identifier = persistentEntity.getIdentifierAccessor(entity).getIdentifier();
additionalColumnValues.put(columnName, identifier);
if (dependingOn == null) {
return;
}
JdbcPersistentEntity<?> persistentEntity = context.getRequiredPersistentEntity(dependingOn.getEntityType());
String columnName = getColumnNameForReverseColumn(insert, persistentEntity);
Object identifier = getIdFromEntityDependingOn(dependingOn, persistentEntity);
additionalColumnValues.put(columnName, identifier);
}
private Object getIdFromEntityDependingOn(DbAction dependingOn, JdbcPersistentEntity<?> persistentEntity) {
return persistentEntity.getIdentifierAccessor(dependingOn.getEntity()).getIdentifier();
}
private <T> String getColumnNameForReverseColumn(Insert<T> insert, JdbcPersistentEntity<?> persistentEntity) {
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();
}
}

View File

@@ -26,7 +26,6 @@ import java.util.stream.Stream;
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty;
import org.springframework.data.jdbc.mapping.model.PropertyPaths;
import org.springframework.data.jdbc.repository.SimpleJdbcRepository;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.mapping.PropertyPath;
@@ -267,7 +266,7 @@ class SqlGenerator {
return String.format("DELETE FROM %s", entity.getTableName());
}
JdbcPersistentEntity<?> entityToDelete = context.getRequiredPersistentEntity(PropertyPaths.getLeafType(path));
JdbcPersistentEntity<?> entityToDelete = context.getRequiredPersistentEntity(path.getLeafType());
JdbcPersistentEntity<?> owningEntity = context.getRequiredPersistentEntity(path.getOwningType());
JdbcPersistentProperty property = owningEntity.getRequiredPersistentProperty(path.getSegment());
@@ -285,7 +284,7 @@ class SqlGenerator {
String createDeleteByPath(PropertyPath path) {
JdbcPersistentEntity<?> entityToDelete = context.getRequiredPersistentEntity(PropertyPaths.getLeafType(path));
JdbcPersistentEntity<?> entityToDelete = context.getRequiredPersistentEntity(path.getLeafType());
JdbcPersistentEntity<?> owningEntity = context.getRequiredPersistentEntity(path.getOwningType());
JdbcPersistentProperty property = owningEntity.getRequiredPersistentProperty(path.getSegment());

View File

@@ -21,7 +21,6 @@ import lombok.ToString;
import java.util.HashMap;
import java.util.Map;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.util.Assert;
/**
@@ -43,6 +42,11 @@ public abstract class DbAction<T> {
*/
private final T entity;
/**
* The path from the Aggregate Root to the entity affected by this {@link DbAction}.
*/
private final JdbcPropertyPath 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
@@ -57,27 +61,28 @@ public abstract class DbAction<T> {
*/
private final DbAction dependingOn;
private DbAction(Class<T> entityType, T entity, DbAction dependingOn) {
private DbAction(Class<T> entityType, T entity, JdbcPropertyPath propertyPath, DbAction dependingOn) {
this.entityType = entityType;
this.entity = entity;
this.propertyPath = propertyPath;
this.dependingOn = dependingOn;
}
public static <T> Insert<T> insert(T entity, DbAction dependingOn) {
return new Insert<>(entity, dependingOn);
public static <T> Insert<T> insert(T entity, JdbcPropertyPath propertyPath, DbAction dependingOn) {
return new Insert<>(entity, propertyPath, dependingOn);
}
public static <T> Update<T> update(T entity, DbAction dependingOn) {
return new Update<>(entity, dependingOn);
public static <T> Update<T> update(T entity, JdbcPropertyPath propertyPath, DbAction dependingOn) {
return new Update<>(entity, propertyPath, dependingOn);
}
public static <T> Delete<T> delete(Object id, Class<T> type, T entity, PropertyPath propertyPath,
public static <T> Delete<T> delete(Object id, Class<T> type, T entity, JdbcPropertyPath propertyPath,
DbAction dependingOn) {
return new Delete<>(id, type, entity, propertyPath, dependingOn);
}
public static <T> DeleteAll<T> deleteAll(Class<T> type, PropertyPath propertyPath, DbAction dependingOn) {
public static <T> DeleteAll<T> deleteAll(Class<T> type, JdbcPropertyPath propertyPath, DbAction dependingOn) {
return new DeleteAll<>(type, propertyPath, dependingOn);
}
@@ -90,8 +95,9 @@ public abstract class DbAction<T> {
*/
abstract static class InsertOrUpdate<T> extends DbAction<T> {
InsertOrUpdate(T entity, DbAction dependingOn) {
super((Class<T>) entity.getClass(), entity, dependingOn);
@SuppressWarnings("unchecked")
InsertOrUpdate(T entity, JdbcPropertyPath propertyPath, DbAction dependingOn) {
super((Class<T>) entity.getClass(), entity, propertyPath, dependingOn);
}
}
@@ -102,8 +108,8 @@ public abstract class DbAction<T> {
*/
public static class Insert<T> extends InsertOrUpdate<T> {
private Insert(T entity, DbAction dependingOn) {
super(entity, dependingOn);
private Insert(T entity, JdbcPropertyPath propertyPath, DbAction dependingOn) {
super(entity, propertyPath, dependingOn);
}
@Override
@@ -119,8 +125,8 @@ public abstract class DbAction<T> {
*/
public static class Update<T> extends InsertOrUpdate<T> {
private Update(T entity, DbAction dependingOn) {
super(entity, dependingOn);
private Update(T entity, JdbcPropertyPath propertyPath, DbAction dependingOn) {
super(entity, propertyPath, dependingOn);
}
@Override
@@ -143,20 +149,13 @@ public abstract class DbAction<T> {
*/
private final Object rootId;
/**
* {@link PropertyPath} which connects the aggregate root with the entities to be deleted. If this is the action to
* delete the root enity itself, this is {@literal null}.
*/
private final PropertyPath propertyPath;
private Delete(Object rootId, Class<T> type, T entity, JdbcPropertyPath propertyPath, DbAction dependingOn) {
private Delete(Object rootId, Class<T> type, T entity, PropertyPath propertyPath, DbAction dependingOn) {
super(type, entity, dependingOn);
super(type, entity, propertyPath, dependingOn);
Assert.notNull(rootId, "rootId must not be null.");
this.rootId = rootId;
this.propertyPath = propertyPath;
}
@Override
@@ -170,19 +169,10 @@ public abstract class DbAction<T> {
*
* @param <T> type o the entity for which this represents a database interaction
*/
@Getter
public static class DeleteAll<T> extends DbAction<T> {
/**
*
*/
private final PropertyPath propertyPath;
private DeleteAll(Class<T> entityType, PropertyPath propertyPath, DbAction dependingOn) {
super(entityType, null, dependingOn);
this.propertyPath = propertyPath;
private DeleteAll(Class<T> entityType, JdbcPropertyPath propertyPath, DbAction dependingOn) {
super(entityType, null, propertyPath, dependingOn);
}
@Override

View File

@@ -16,7 +16,6 @@
package org.springframework.data.jdbc.core.conversion;
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
import org.springframework.data.jdbc.mapping.model.PropertyPaths;
/**
* Converts an entity that is about to be deleted into {@link DbAction}s inside a {@link AggregateChange} that need to be
@@ -43,7 +42,7 @@ public class JdbcEntityDeleteWriter extends JdbcEntityWriterSupport {
private void deleteAll(AggregateChange aggregateChange) {
context.referencedEntities(aggregateChange.getEntityType(), null)
.forEach(p -> aggregateChange.addAction(DbAction.deleteAll(PropertyPaths.getLeafType(p), p, null)));
.forEach(p -> aggregateChange.addAction(DbAction.deleteAll(p.getLeafType(), new JdbcPropertyPath(p), null)));
aggregateChange.addAction(DbAction.deleteAll(aggregateChange.getEntityType(), null, null));
}
@@ -54,5 +53,4 @@ public class JdbcEntityDeleteWriter extends JdbcEntityWriterSupport {
aggregateChange.addAction(DbAction.delete(id, aggregateChange.getEntityType(), aggregateChange.getEntity(), null, null));
}
}

View File

@@ -52,76 +52,85 @@ public class JdbcEntityWriter extends JdbcEntityWriterSupport {
private void write(Object o, AggregateChange aggregateChange, DbAction dependingOn) {
JdbcPersistentEntityInformation<Object, ?> entityInformation = context
.getRequiredPersistentEntityInformation((Class<Object>) o.getClass());
Class<Object> type = (Class<Object>) o.getClass();
JdbcPersistentEntityInformation<Object, ?> entityInformation = context.getRequiredPersistentEntityInformation(type);
JdbcPropertyPath propertyPath = JdbcPropertyPath.from("", type);
if (entityInformation.isNew(o)) {
Insert<Object> insert = DbAction.insert(o, dependingOn);
Insert<Object> insert = DbAction.insert(o, propertyPath, dependingOn);
aggregateChange.addAction(insert);
referencedEntities(o).forEach(propertyAndValue -> saveReferencedEntities(propertyAndValue, aggregateChange, insert));
referencedEntities(o).forEach(propertyAndValue -> saveReferencedEntities(propertyAndValue, aggregateChange,
propertyPath.nested(propertyAndValue.property.getName()), insert));
} else {
deleteReferencedEntities(entityInformation.getRequiredId(o), aggregateChange);
Update<Object> update = DbAction.update(o, dependingOn);
Update<Object> update = DbAction.update(o, propertyPath, dependingOn);
aggregateChange.addAction(update);
referencedEntities(o).forEach(propertyAndValue -> insertReferencedEntities(propertyAndValue, aggregateChange, update));
referencedEntities(o).forEach(
propertyAndValue -> insertReferencedEntities(propertyAndValue, aggregateChange, propertyPath.nested(propertyAndValue.property.getName()), update));
}
}
private void saveReferencedEntities(PropertyAndValue propertyAndValue, AggregateChange aggregateChange, DbAction dependingOn) {
private void saveReferencedEntities(PropertyAndValue propertyAndValue, AggregateChange aggregateChange,
JdbcPropertyPath propertyPath, DbAction dependingOn) {
saveActions(propertyAndValue, dependingOn).forEach(a -> {
saveActions(propertyAndValue, propertyPath, dependingOn).forEach(a -> {
aggregateChange.addAction(a);
referencedEntities(propertyAndValue.value).forEach(pav -> saveReferencedEntities(pav, aggregateChange, a));
referencedEntities(propertyAndValue.value)
.forEach(pav -> saveReferencedEntities(pav, aggregateChange, propertyPath.nested(pav.property.getName()), a));
});
}
private Stream<DbAction> saveActions(PropertyAndValue propertyAndValue, DbAction dependingOn) {
private Stream<DbAction> saveActions(PropertyAndValue propertyAndValue, JdbcPropertyPath propertyPath,
DbAction dependingOn) {
if (Map.Entry.class.isAssignableFrom(ClassUtils.getUserClass(propertyAndValue.value))) {
return mapEntrySaveAction(propertyAndValue, dependingOn);
return mapEntrySaveAction(propertyAndValue, propertyPath, dependingOn);
}
return Stream.of(singleSaveAction(propertyAndValue.value, dependingOn));
return Stream.of(singleSaveAction(propertyAndValue.value, propertyPath, dependingOn));
}
private Stream<DbAction> mapEntrySaveAction(PropertyAndValue propertyAndValue, DbAction dependingOn) {
private Stream<DbAction> mapEntrySaveAction(PropertyAndValue propertyAndValue, JdbcPropertyPath propertyPath,
DbAction dependingOn) {
Map.Entry<Object, Object> entry = (Map.Entry) propertyAndValue.value;
DbAction action = singleSaveAction(entry.getValue(), dependingOn);
DbAction action = singleSaveAction(entry.getValue(), propertyPath, dependingOn);
action.getAdditionalValues().put(propertyAndValue.property.getKeyColumn(), entry.getKey());
return Stream.of(action);
}
private <T> DbAction singleSaveAction(T t, DbAction dependingOn) {
private <T> DbAction singleSaveAction(T t, JdbcPropertyPath propertyPath, DbAction dependingOn) {
JdbcPersistentEntityInformation<T, ?> entityInformation = context
.getRequiredPersistentEntityInformation((Class<T>) ClassUtils.getUserClass(t));
return entityInformation.isNew(t) ? DbAction.insert(t, dependingOn) : DbAction.update(t, dependingOn);
return entityInformation.isNew(t) ? DbAction.insert(t, propertyPath, dependingOn)
: DbAction.update(t, propertyPath, dependingOn);
}
private void insertReferencedEntities(PropertyAndValue propertyAndValue, AggregateChange aggregateChange, DbAction dependingOn) {
private void insertReferencedEntities(PropertyAndValue propertyAndValue, AggregateChange aggregateChange,
JdbcPropertyPath propertyPath, DbAction dependingOn) {
Insert<Object> insert;
if (propertyAndValue.property.isQualified()) {
Entry<Object, Object> valueAsEntry = (Entry<Object, Object>) propertyAndValue.value;
insert = DbAction.insert(valueAsEntry.getValue(), dependingOn);
insert = DbAction.insert(valueAsEntry.getValue(), propertyPath, dependingOn);
insert.getAdditionalValues().put(propertyAndValue.property.getKeyColumn(), valueAsEntry.getKey());
} else {
insert = DbAction.insert(propertyAndValue.value, dependingOn);
insert = DbAction.insert(propertyAndValue.value, propertyPath, dependingOn);
}
aggregateChange.addAction(insert);
referencedEntities(insert.getEntity())
.forEach(pav -> insertReferencedEntities(pav, aggregateChange, dependingOn));
.forEach(pav -> insertReferencedEntities(pav, aggregateChange, propertyPath.nested(pav.property.getName()), dependingOn));
}
private Stream<PropertyAndValue> referencedEntities(Object o) {
@@ -133,7 +142,7 @@ public class JdbcEntityWriter extends JdbcEntityWriterSupport {
.flatMap( //
p -> referencedEntity(p, persistentEntity.getPropertyAccessor(o)) //
.map(e -> new PropertyAndValue(p, e)) //
);
);
}
private Stream<Object> referencedEntity(JdbcPersistentProperty p, PersistentPropertyAccessor propertyAccessor) {

View File

@@ -17,7 +17,6 @@ package org.springframework.data.jdbc.core.conversion;
import org.springframework.data.convert.EntityWriter;
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
import org.springframework.data.jdbc.mapping.model.PropertyPaths;
/**
* Common infrastructure needed by different implementations of {@link EntityWriter}<Object, AggregateChange>.
@@ -41,6 +40,6 @@ abstract class JdbcEntityWriterSupport implements EntityWriter<Object, Aggregate
void deleteReferencedEntities(Object id, AggregateChange aggregateChange) {
context.referencedEntities(aggregateChange.getEntityType(), null)
.forEach(p -> aggregateChange.addAction(DbAction.delete(id, PropertyPaths.getLeafType(p), null, p, null)));
.forEach(p -> aggregateChange.addAction(DbAction.delete(id, p.getLeafType(), null, new JdbcPropertyPath(p), null)));
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright 2017 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.jdbc.core.conversion;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.util.StringUtils;
/**
* A replacement for {@link org.springframework.data.mapping.PropertyPath} as long as it doesn't support objects with
* empty path.
*
* See https://jira.spring.io/browse/DATACMNS-1204.
*
* @author Jens Schauder
*/
public class JdbcPropertyPath {
private final PropertyPath path;
private final Class<?> rootType;
JdbcPropertyPath(PropertyPath path) {
this.path = path;
this.rootType = null;
}
private JdbcPropertyPath(Class<?> type) {
this.path = null;
this.rootType = type;
}
public static JdbcPropertyPath from(String source, Class<?> type) {
if (StringUtils.isEmpty(source)) {
return new JdbcPropertyPath(type);
} else {
return new JdbcPropertyPath(PropertyPath.from(source, type));
}
}
public JdbcPropertyPath nested(String name) {
return path == null ? new JdbcPropertyPath(PropertyPath.from(name, rootType)) : new JdbcPropertyPath(path.nested(name));
}
public PropertyPath getPath() {
return path;
}
public String toDotPath() {
return path == null ? "" : path.toDotPath();
}
}

View File

@@ -61,14 +61,14 @@ public class JdbcMappingContext extends AbstractMappingContext<JdbcPersistentEnt
List<PropertyPath> paths = new ArrayList<>();
Class<?> currentType = path == null ? rootType : PropertyPaths.getLeafType(path);
Class<?> currentType = path == null ? rootType : path.getLeafType();
JdbcPersistentEntity<?> persistentEntity = getRequiredPersistentEntity(currentType);
for (JdbcPersistentProperty property : persistentEntity) {
if (property.isEntity()) {
PropertyPath nextPath = path == null ? PropertyPath.from(property.getName(), rootType)
: PropertyPaths.extendBy(path, property.getColumnName());
: path.nested(property.getColumnName());
paths.add(nextPath);
paths.addAll(referencedEntities(rootType, nextPath));
}

View File

@@ -1,46 +0,0 @@
/*
* Copyright 2017 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.jdbc.mapping.model;
import lombok.experimental.UtilityClass;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.util.Assert;
/**
* Utilities for working with {@link PropertyPath}s.
*
* @author Jens Schauder
*/
@UtilityClass
public class PropertyPaths {
public static Class<?> getLeafType(PropertyPath path) {
if (path.hasNext()) {
return getLeafType(path.next());
}
return path.getType();
}
public static PropertyPath extendBy(PropertyPath path, String name) {
Assert.notNull(path, "Path must not be null.");
Assert.hasText(name, "Name must not be empty");
return PropertyPath.from(path.toDotPath() + "." + name, path.getOwningType());
}
}