diff --git a/pom.xml b/pom.xml index 7fc8f36b75..f46dfe6b14 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.springframework.data spring-data-jdbc - 1.0.0.BUILD-SNAPSHOT + 1.0.0.DATAJDBC-145-SNAPSHOT Spring Data JDBC Spring Data module for JDBC repositories. @@ -15,14 +15,14 @@ org.springframework.data.build spring-data-parent - 2.0.0.BUILD-SNAPSHOT + 2.1.0.BUILD-SNAPSHOT DATAJDBC - 2.0.0.BUILD-SNAPSHOT + 2.1.0.BUILD-SNAPSHOT spring.data.jdbc reuseReports diff --git a/src/main/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreter.java b/src/main/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreter.java index 3109078899..e2ce916c0e 100644 --- a/src/main/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreter.java +++ b/src/main/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreter.java @@ -26,6 +26,8 @@ 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 @@ public void interpret(Delete delete) { 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 void interpret(DeleteAll delete) { - + if (delete.getEntityType() == null) { - accessStrategy.deleteAll(delete.getPropertyPath()); + accessStrategy.deleteAll(delete.getPropertyPath().getPath()); } else { accessStrategy.deleteAll(delete.getEntityType()); } @@ -84,16 +86,32 @@ private Map createAdditionalColumnValues(Insert insert) { } private void addDependingOnInformation(Insert insert, Map additionalColumnValues) { + DbAction dependingOn = insert.getDependingOn(); - if (dependingOn != null) { + if (dependingOn == null) { + return; + } - JdbcPersistentEntity persistentEntity = context.getRequiredPersistentEntity(dependingOn.getEntityType()); - String columnName = persistentEntity.getTableName(); - Object entity = dependingOn.getEntity(); - Object identifier = persistentEntity.getIdentifierAccessor(entity).getIdentifier(); + JdbcPersistentEntity persistentEntity = context.getRequiredPersistentEntity(dependingOn.getEntityType()); - additionalColumnValues.put(columnName, identifier); - } + 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 String getColumnNameForReverseColumn(Insert 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(); } } diff --git a/src/main/java/org/springframework/data/jdbc/core/SqlGenerator.java b/src/main/java/org/springframework/data/jdbc/core/SqlGenerator.java index 37ef48d422..502d6d0f3b 100644 --- a/src/main/java/org/springframework/data/jdbc/core/SqlGenerator.java +++ b/src/main/java/org/springframework/data/jdbc/core/SqlGenerator.java @@ -26,7 +26,6 @@ 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 @@ String createDeleteAllSql(PropertyPath path) { 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 @@ private String createDeleteByListSql() { 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()); diff --git a/src/main/java/org/springframework/data/jdbc/core/conversion/DbAction.java b/src/main/java/org/springframework/data/jdbc/core/conversion/DbAction.java index f25d031ed7..e5f2fee59d 100644 --- a/src/main/java/org/springframework/data/jdbc/core/conversion/DbAction.java +++ b/src/main/java/org/springframework/data/jdbc/core/conversion/DbAction.java @@ -21,7 +21,6 @@ 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 { */ 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 { */ private final DbAction dependingOn; - private DbAction(Class entityType, T entity, DbAction dependingOn) { + private DbAction(Class entityType, T entity, JdbcPropertyPath propertyPath, DbAction dependingOn) { this.entityType = entityType; this.entity = entity; + this.propertyPath = propertyPath; this.dependingOn = dependingOn; } - public static Insert insert(T entity, DbAction dependingOn) { - return new Insert<>(entity, dependingOn); + public static Insert insert(T entity, JdbcPropertyPath propertyPath, DbAction dependingOn) { + return new Insert<>(entity, propertyPath, dependingOn); } - public static Update update(T entity, DbAction dependingOn) { - return new Update<>(entity, dependingOn); + public static Update update(T entity, JdbcPropertyPath propertyPath, DbAction dependingOn) { + return new Update<>(entity, propertyPath, dependingOn); } - public static Delete delete(Object id, Class type, T entity, PropertyPath propertyPath, + public static Delete delete(Object id, Class type, T entity, JdbcPropertyPath propertyPath, DbAction dependingOn) { return new Delete<>(id, type, entity, propertyPath, dependingOn); } - public static DeleteAll deleteAll(Class type, PropertyPath propertyPath, DbAction dependingOn) { + public static DeleteAll deleteAll(Class type, JdbcPropertyPath propertyPath, DbAction dependingOn) { return new DeleteAll<>(type, propertyPath, dependingOn); } @@ -90,8 +95,9 @@ public static DeleteAll deleteAll(Class type, PropertyPath propertyPat */ abstract static class InsertOrUpdate extends DbAction { - InsertOrUpdate(T entity, DbAction dependingOn) { - super((Class) entity.getClass(), entity, dependingOn); + @SuppressWarnings("unchecked") + InsertOrUpdate(T entity, JdbcPropertyPath propertyPath, DbAction dependingOn) { + super((Class) entity.getClass(), entity, propertyPath, dependingOn); } } @@ -102,8 +108,8 @@ abstract static class InsertOrUpdate extends DbAction { */ public static class Insert extends InsertOrUpdate { - 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 @@ void executeWith(Interpreter interpreter) { */ public static class Update extends InsertOrUpdate { - 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 static class Delete extends DbAction { */ 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 type, T entity, PropertyPath propertyPath, DbAction dependingOn) { + private Delete(Object rootId, Class type, T entity, JdbcPropertyPath 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 @@ void executeWith(Interpreter interpreter) { * * @param type o the entity for which this represents a database interaction */ - @Getter public static class DeleteAll extends DbAction { - /** - * - */ - private final PropertyPath propertyPath; - - private DeleteAll(Class entityType, PropertyPath propertyPath, DbAction dependingOn) { - - super(entityType, null, dependingOn); - - this.propertyPath = propertyPath; + private DeleteAll(Class entityType, JdbcPropertyPath propertyPath, DbAction dependingOn) { + super(entityType, null, propertyPath, dependingOn); } @Override diff --git a/src/main/java/org/springframework/data/jdbc/core/conversion/JdbcEntityDeleteWriter.java b/src/main/java/org/springframework/data/jdbc/core/conversion/JdbcEntityDeleteWriter.java index 429c75a914..7aa6cfd40e 100644 --- a/src/main/java/org/springframework/data/jdbc/core/conversion/JdbcEntityDeleteWriter.java +++ b/src/main/java/org/springframework/data/jdbc/core/conversion/JdbcEntityDeleteWriter.java @@ -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 void write(Object id, AggregateChange aggregateChange) { 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 @@ private void deleteById(Object id, AggregateChange aggregateChange) { aggregateChange.addAction(DbAction.delete(id, aggregateChange.getEntityType(), aggregateChange.getEntity(), null, null)); } - } diff --git a/src/main/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriter.java b/src/main/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriter.java index 478ce68b33..c04f152984 100644 --- a/src/main/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriter.java +++ b/src/main/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriter.java @@ -52,76 +52,85 @@ public void write(Object o, AggregateChange aggregateChange) { private void write(Object o, AggregateChange aggregateChange, DbAction dependingOn) { - JdbcPersistentEntityInformation entityInformation = context - .getRequiredPersistentEntityInformation((Class) o.getClass()); + Class type = (Class) o.getClass(); + JdbcPersistentEntityInformation entityInformation = context.getRequiredPersistentEntityInformation(type); + JdbcPropertyPath propertyPath = JdbcPropertyPath.from("", type); if (entityInformation.isNew(o)) { - Insert insert = DbAction.insert(o, dependingOn); + Insert 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 update = DbAction.update(o, dependingOn); + Update 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 saveActions(PropertyAndValue propertyAndValue, DbAction dependingOn) { + private Stream 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 mapEntrySaveAction(PropertyAndValue propertyAndValue, DbAction dependingOn) { + private Stream mapEntrySaveAction(PropertyAndValue propertyAndValue, JdbcPropertyPath propertyPath, + DbAction dependingOn) { Map.Entry 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 DbAction singleSaveAction(T t, DbAction dependingOn) { + private DbAction singleSaveAction(T t, JdbcPropertyPath propertyPath, DbAction dependingOn) { JdbcPersistentEntityInformation entityInformation = context .getRequiredPersistentEntityInformation((Class) 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 insert; if (propertyAndValue.property.isQualified()) { Entry valueAsEntry = (Entry) 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 referencedEntities(Object o) { @@ -133,7 +142,7 @@ private Stream referencedEntities(Object o) { .flatMap( // p -> referencedEntity(p, persistentEntity.getPropertyAccessor(o)) // .map(e -> new PropertyAndValue(p, e)) // - ); + ); } private Stream referencedEntity(JdbcPersistentProperty p, PersistentPropertyAccessor propertyAccessor) { diff --git a/src/main/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriterSupport.java b/src/main/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriterSupport.java index 6fc054527e..e4061c97b3 100644 --- a/src/main/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriterSupport.java +++ b/src/main/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriterSupport.java @@ -17,7 +17,6 @@ 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}. @@ -41,6 +40,6 @@ abstract class JdbcEntityWriterSupport implements EntityWriter 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))); } } diff --git a/src/main/java/org/springframework/data/jdbc/core/conversion/JdbcPropertyPath.java b/src/main/java/org/springframework/data/jdbc/core/conversion/JdbcPropertyPath.java new file mode 100644 index 0000000000..6744071c69 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/core/conversion/JdbcPropertyPath.java @@ -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(); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcMappingContext.java b/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcMappingContext.java index 0d8ae2d80f..eda46bca64 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcMappingContext.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcMappingContext.java @@ -61,14 +61,14 @@ public List referencedEntities(Class rootType, PropertyPath pat List 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)); } diff --git a/src/main/java/org/springframework/data/jdbc/mapping/model/PropertyPaths.java b/src/main/java/org/springframework/data/jdbc/mapping/model/PropertyPaths.java deleted file mode 100644 index 6ab523bcf4..0000000000 --- a/src/main/java/org/springframework/data/jdbc/mapping/model/PropertyPaths.java +++ /dev/null @@ -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()); - } -} diff --git a/src/test/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreterUnitTests.java b/src/test/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreterUnitTests.java new file mode 100644 index 0000000000..832437f5e5 --- /dev/null +++ b/src/test/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreterUnitTests.java @@ -0,0 +1,81 @@ +/* + * 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; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.util.AbstractMap.SimpleEntry; +import java.util.Map; + +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.springframework.data.annotation.Id; +import org.springframework.data.jdbc.core.conversion.DbAction; +import org.springframework.data.jdbc.core.conversion.DbAction.Insert; +import org.springframework.data.jdbc.core.conversion.JdbcPropertyPath; +import org.springframework.data.jdbc.mapping.model.DefaultNamingStrategy; +import org.springframework.data.jdbc.mapping.model.JdbcMappingContext; +import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty; + +/** + * Unit tests for {@link DefaultJdbcInterpreter} + * + * @author Jens Schauder + */ +public class DefaultJdbcInterpreterUnitTests { + + static final long CONTAINER_ID = 23L; + static final String BACK_REFERENCE = "back-reference"; + + JdbcMappingContext context = new JdbcMappingContext(new DefaultNamingStrategy() { + @Override + public String getReverseColumnName(JdbcPersistentProperty property) { + return BACK_REFERENCE; + } + }); + + DataAccessStrategy dataAccessStrategy = mock(DataAccessStrategy.class); + DefaultJdbcInterpreter interpreter = new DefaultJdbcInterpreter(context, dataAccessStrategy); + + @Test // DATAJDBC-145 + public void insertDoesHonourNamingStrategyForBackReference() { + + Container container = new Container(); + container.id = CONTAINER_ID; + + Element element = new Element(); + + Insert containerInsert = DbAction.insert(container, JdbcPropertyPath.from("", Container.class), null); + Insert insert = DbAction.insert(element, JdbcPropertyPath.from("element", Container.class), containerInsert); + + interpreter.interpret(insert); + + ArgumentCaptor> argumentCaptor = ArgumentCaptor.forClass(Map.class); + verify(dataAccessStrategy).insert(eq(element), eq(Element.class), argumentCaptor.capture()); + + assertThat(argumentCaptor.getValue()).containsExactly(new SimpleEntry(BACK_REFERENCE, CONTAINER_ID)); + } + + static class Container { + + @Id Long id; + + Element element; + } + + static class Element {} +} diff --git a/src/test/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriterUnitTests.java b/src/test/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriterUnitTests.java index 64b3ff5a37..a59aec6b6f 100644 --- a/src/test/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriterUnitTests.java +++ b/src/test/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriterUnitTests.java @@ -56,9 +56,9 @@ public void newEntityGetsConvertedToOneInsert() { converter.write(entity, aggregateChange); assertThat(aggregateChange.getActions()) // - .extracting(DbAction::getClass, DbAction::getEntityType) // + .extracting(DbAction::getClass, DbAction::getEntityType, this::extractPath) // .containsExactly( // - tuple(Insert.class, SingleReferenceEntity.class) // + tuple(Insert.class, SingleReferenceEntity.class, "") // ); } @@ -72,10 +72,10 @@ public void existingEntityGetsConvertedToUpdate() { converter.write(entity, aggregateChange); assertThat(aggregateChange.getActions()) // - .extracting(DbAction::getClass, DbAction::getEntityType) // + .extracting(DbAction::getClass, DbAction::getEntityType, this::extractPath) // .containsExactly( // - tuple(Delete.class, Element.class), // - tuple(Update.class, SingleReferenceEntity.class) // + tuple(Delete.class, Element.class, "other"), // + tuple(Update.class, SingleReferenceEntity.class, "") // ); } @@ -91,11 +91,11 @@ public void referenceTriggersDeletePlusInsert() { converter.write(entity, aggregateChange); assertThat(aggregateChange.getActions()) // - .extracting(DbAction::getClass, DbAction::getEntityType) // + .extracting(DbAction::getClass, DbAction::getEntityType, this::extractPath) // .containsExactly( // - tuple(Delete.class, Element.class), // - tuple(Update.class, SingleReferenceEntity.class), // - tuple(Insert.class, Element.class) // + tuple(Delete.class, Element.class, "other"), // + tuple(Update.class, SingleReferenceEntity.class, ""), // + tuple(Insert.class, Element.class, "other") // ); } @@ -107,9 +107,10 @@ public void newEntityWithEmptySetResultsInSingleInsert() { converter.write(entity, aggregateChange); - assertThat(aggregateChange.getActions()).extracting(DbAction::getClass, DbAction::getEntityType) // + assertThat(aggregateChange.getActions()) // + .extracting(DbAction::getClass, DbAction::getEntityType, this::extractPath) // .containsExactly( // - tuple(Insert.class, SetContainer.class)); + tuple(Insert.class, SetContainer.class, "")); } @Test // DATAJDBC-113 @@ -122,11 +123,11 @@ public void newEntityWithSetResultsInAdditionalInsertPerElement() { AggregateChange aggregateChange = new AggregateChange(Kind.SAVE, SetContainer.class, entity); converter.write(entity, aggregateChange); - assertThat(aggregateChange.getActions()).extracting(DbAction::getClass, DbAction::getEntityType) // + assertThat(aggregateChange.getActions()).extracting(DbAction::getClass, DbAction::getEntityType, this::extractPath) // .containsExactly( // - tuple(Insert.class, SetContainer.class), // - tuple(Insert.class, Element.class), // - tuple(Insert.class, Element.class) // + tuple(Insert.class, SetContainer.class, ""), // + tuple(Insert.class, Element.class, "elements"), // + tuple(Insert.class, Element.class, "elements") // ); } @@ -149,15 +150,15 @@ public void cascadingReferencesTriggerCascadingActions() { converter.write(entity, aggregateChange); - assertThat(aggregateChange.getActions()).extracting(DbAction::getClass, DbAction::getEntityType) // + assertThat(aggregateChange.getActions()).extracting(DbAction::getClass, DbAction::getEntityType, this::extractPath) // .containsExactly( // - tuple(Insert.class, CascadingReferenceEntity.class), // - tuple(Insert.class, CascadingReferenceMiddleElement.class), // - tuple(Insert.class, Element.class), // - tuple(Insert.class, Element.class), // - tuple(Insert.class, CascadingReferenceMiddleElement.class), // - tuple(Insert.class, Element.class), // - tuple(Insert.class, Element.class) // + tuple(Insert.class, CascadingReferenceEntity.class, ""), // + tuple(Insert.class, CascadingReferenceMiddleElement.class, "other"), // + tuple(Insert.class, Element.class, "other.element"), // + tuple(Insert.class, Element.class, "other.element"), // + tuple(Insert.class, CascadingReferenceMiddleElement.class, "other"), // + tuple(Insert.class, Element.class, "other.element"), // + tuple(Insert.class, Element.class, "other.element") // ); } @@ -169,9 +170,9 @@ public void newEntityWithEmptyMapResultsInSingleInsert() { converter.write(entity, aggregateChange); - assertThat(aggregateChange.getActions()).extracting(DbAction::getClass, DbAction::getEntityType) // + assertThat(aggregateChange.getActions()).extracting(DbAction::getClass, DbAction::getEntityType, this::extractPath) // .containsExactly( // - tuple(Insert.class, MapContainer.class)); + tuple(Insert.class, MapContainer.class, "")); } @Test // DATAJDBC-131 @@ -185,17 +186,17 @@ public void newEntityWithMapResultsInAdditionalInsertPerElement() { converter.write(entity, aggregateChange); assertThat(aggregateChange.getActions()) - .extracting(DbAction::getClass, DbAction::getEntityType, JdbcEntityWriterUnitTests::getMapKey) // + .extracting(DbAction::getClass, DbAction::getEntityType, this::getMapKey, this::extractPath) // .containsExactlyInAnyOrder( // - tuple(Insert.class, MapContainer.class, null), // - tuple(Insert.class, Element.class, "one"), // - tuple(Insert.class, Element.class, "two") // + tuple(Insert.class, MapContainer.class, null, ""), // + tuple(Insert.class, Element.class, "one", "elements"), // + tuple(Insert.class, Element.class, "two", "elements") // ).containsSubsequence( // container comes before the elements - tuple(Insert.class, MapContainer.class, null), // - tuple(Insert.class, Element.class, "two") // + tuple(Insert.class, MapContainer.class, null, ""), // + tuple(Insert.class, Element.class, "two", "elements") // ).containsSubsequence( // container comes before the elements - tuple(Insert.class, MapContainer.class, null), // - tuple(Insert.class, Element.class, "one") // + tuple(Insert.class, MapContainer.class, null, ""), // + tuple(Insert.class, Element.class, "one", "elements") // ); } @@ -210,11 +211,11 @@ public void mapTriggersDeletePlusInsert() { converter.write(entity, aggregateChange); assertThat(aggregateChange.getActions()) // - .extracting(DbAction::getClass, DbAction::getEntityType, JdbcEntityWriterUnitTests::getMapKey) // + .extracting(DbAction::getClass, DbAction::getEntityType, this::getMapKey, this::extractPath) // .containsExactly( // - tuple(Delete.class, Element.class, null), // - tuple(Update.class, MapContainer.class, null), // - tuple(Insert.class, Element.class, "one") // + tuple(Delete.class, Element.class, null, "elements"), // + tuple(Update.class, MapContainer.class, null, ""), // + tuple(Insert.class, Element.class, "one", "elements") // ); } @@ -226,10 +227,14 @@ private CascadingReferenceMiddleElement createMiddleElement(Element first, Eleme return middleElement1; } - private static Object getMapKey(DbAction a) { + private Object getMapKey(DbAction a) { return a.getAdditionalValues().get("MapContainer_key"); } + private String extractPath(DbAction action) { + return action.getPropertyPath().toDotPath(); + } + @RequiredArgsConstructor static class SingleReferenceEntity { diff --git a/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryManipulateDbActionsIntegrationTests.java b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryManipulateDbActionsIntegrationTests.java index 1615c05c3f..c84173d7c8 100644 --- a/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryManipulateDbActionsIntegrationTests.java +++ b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryManipulateDbActionsIntegrationTests.java @@ -204,7 +204,7 @@ ApplicationListener softDeleteListener() { List actions = event.getChange().getActions(); actions.clear(); - actions.add(DbAction.update(entity, null)); + actions.add(DbAction.update(entity, null, null)); }; } @@ -223,7 +223,7 @@ ApplicationListener logOnSaveListener() { List actions = event.getChange().getActions(); - actions.add(DbAction.insert(log, null)); + actions.add(DbAction.insert(log, null, null)); }; }