From ade3324e2c8f578ca256f920998d76bd62a04df2 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Tue, 12 Feb 2019 07:50:05 +0100 Subject: [PATCH] DATAJDBC-326 - Support conversion of backreferences. Ids used as backreferences now get properly converted. Introduced Identifier to hold information about data that needs to be considered for inserts or updates but is not part of the entity. Apart from column names and values they also hold information about the desired JdbcType in order to facilitate conversions. This replaces the Map handed around in the past. Original pull request: #118. --- .../core/CascadingDataAccessStrategy.java | 12 +- .../data/jdbc/core/DataAccessStrategy.java | 19 ++ .../jdbc/core/DefaultDataAccessStrategy.java | 61 ++++--- .../jdbc/core/DefaultJdbcInterpreter.java | 34 ++-- .../core/DelegatingDataAccessStrategy.java | 10 ++ .../jdbc/core/convert/BasicJdbcConverter.java | 8 + .../core/convert/JdbcIdentifierBuilder.java | 89 ++++++++++ .../mybatis/MyBatisDataAccessStrategy.java | 14 ++ .../core/DefaultJdbcInterpreterUnitTests.java | 24 +-- .../core/JdbcIdentifierBuilderUnitTests.java | 167 ++++++++++++++++++ ...dbcRepositoryWithMapsIntegrationTests.java | 2 + .../src/test/resources/logback.xml | 2 +- ...AggregateTemplateIntegrationTests-hsql.sql | 6 +- ...epositoryWithMapsIntegrationTests-hsql.sql | 4 + .../core/conversion/AggregateChange.java | 3 +- .../relational/core/conversion/DbAction.java | 8 +- .../core/conversion/WritingContext.java | 2 +- .../BasicRelationalPersistentProperty.java | 13 ++ .../mapping/RelationalPersistentProperty.java | 2 + .../data/relational/domain/Identifier.java | 91 ++++++++++ .../conversion/AggregateChangeUnitTests.java | 13 +- .../RelationalEntityWriterUnitTests.java | 82 +++++---- ...fierTest.java => IdentifierUnitTests.java} | 2 +- .../domain/IdentifierUnitTests.java | 37 ++++ 24 files changed, 610 insertions(+), 95 deletions(-) create mode 100644 spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcIdentifierBuilder.java create mode 100644 spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcIdentifierBuilderUnitTests.java create mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/domain/Identifier.java rename spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/event/{IdentifierTest.java => IdentifierUnitTests.java} (97%) create mode 100644 spring-data-relational/src/test/java/org/springframework/data/relational/domain/IdentifierUnitTests.java diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/CascadingDataAccessStrategy.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/CascadingDataAccessStrategy.java index 7edb996ce..f78dc5bd3 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/CascadingDataAccessStrategy.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/CascadingDataAccessStrategy.java @@ -21,6 +21,7 @@ import java.util.Map; import java.util.function.Consumer; import java.util.function.Function; +import org.springframework.data.relational.domain.Identifier; import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; @@ -47,6 +48,15 @@ public class CascadingDataAccessStrategy implements DataAccessStrategy { return collect(das -> das.insert(instance, domainType, additionalParameters)); } + /* + * (non-Javadoc) + * @see org.springframework.data.jdbc.core.DataAccessStrategy#insert(java.lang.Object, java.lang.Class, org.springframework.data.jdbc.core.ParentKeys) + */ + @Override + public Object insert(T instance, Class domainType, Identifier identifier) { + return collect(das -> das.insert(instance, domainType, identifier)); + } + /* * (non-Javadoc) * @see org.springframework.data.jdbc.core.DataAccessStrategy#update(java.lang.Object, java.lang.Class) @@ -149,7 +159,7 @@ public class CascadingDataAccessStrategy implements DataAccessStrategy { private T collect(Function function) { // Keep as Eclipse fails to compile if <> is used. - return strategies.stream().collect(new FunctionCollector(function)); + return strategies.stream().collect(new FunctionCollector<>(function)); } private void collectVoid(Consumer consumer) { diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/DataAccessStrategy.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/DataAccessStrategy.java index a307b8b2d..ce731ce14 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/DataAccessStrategy.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/DataAccessStrategy.java @@ -17,6 +17,7 @@ package org.springframework.data.jdbc.core; import java.util.Map; +import org.springframework.data.relational.domain.Identifier; import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.lang.Nullable; @@ -39,9 +40,27 @@ public interface DataAccessStrategy { * to get referenced are contained in this map. Must not be {@code null}. * @param the type of the instance. * @return the id generated by the database if any. + * + * @deprecated use {@link #insert(Object, Class, Identifier)} instead. */ + @Deprecated Object insert(T instance, Class domainType, Map additionalParameters); + + /** + * Inserts a the data of a single entity. Referenced entities don't get handled. + * + * @param instance the instance to be stored. Must not be {@code null}. + * @param domainType the type of the instance. Must not be {@code null}. + * @param identifier information about data that needs to be considered for the insert but which is not part of the entity. + * Namely references back to a parent entity and key/index columns for entities that are stored in a {@link Map} or {@link java.util.List}. + * @param the type of the instance. + * @return the id generated by the database if any. + */ + default Object insert(T instance, Class domainType, Identifier identifier){ + return insert(instance, domainType, identifier.getParametersByName()); + } + /** * Updates the data of a single entity in the database. Referenced entities don't get handled. * diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategy.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategy.java index 7ee0cf839..2f3044415 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategy.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategy.java @@ -29,6 +29,7 @@ import java.util.stream.StreamSupport; import org.springframework.dao.DataRetrievalFailureException; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.jdbc.core.convert.JdbcIdentifierBuilder; import org.springframework.data.jdbc.support.JdbcUtil; import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.mapping.PersistentPropertyPath; @@ -37,6 +38,7 @@ import org.springframework.data.relational.core.conversion.RelationalConverter; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; +import org.springframework.data.relational.domain.Identifier; import org.springframework.data.util.ClassTypeInformation; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; @@ -87,10 +89,24 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy { */ @Override public Object insert(T instance, Class domainType, Map additionalParameters) { + return insert(instance, domainType, JdbcIdentifierBuilder.from(additionalParameters).build()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.jdbc.core.DataAccessStrategy#insert(java.lang.Object, java.lang.Class, java.util.Map) + */ + @Override + public Object insert(T instance, Class domainType, Identifier identifier) { KeyHolder holder = new GeneratedKeyHolder(); RelationalPersistentEntity persistentEntity = getRequiredPersistentEntity(domainType); - Map parameters = new LinkedHashMap<>(additionalParameters); + + Map parameters = new LinkedHashMap<>(); + identifier.forEach(identifierValue -> { + parameters.put(identifierValue.getName(), + converter.writeValue(identifierValue.getValue(), ClassTypeInformation.from(identifierValue.getTargetType()))); + }); MapSqlParameterSource parameterSource = getPropertyMap(instance, persistentEntity, ""); @@ -282,7 +298,8 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy { return result; } - private MapSqlParameterSource getPropertyMap(final S instance, RelationalPersistentEntity persistentEntity, String prefix) { + private MapSqlParameterSource getPropertyMap(final S instance, RelationalPersistentEntity persistentEntity, + String prefix) { MapSqlParameterSource parameters = new MapSqlParameterSource(); @@ -294,23 +311,26 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy { return; } - if(property.isEmbedded()){ + if (property.isEmbedded()) { Object value = propertyAccessor.getProperty(property); - final RelationalPersistentEntity embeddedEntity = context.getPersistentEntity(property.getType()); - final MapSqlParameterSource additionalParameters = getPropertyMap((T)value, (RelationalPersistentEntity) embeddedEntity, prefix + property.getEmbeddedPrefix()); + final RelationalPersistentEntity embeddedEntity = context.getPersistentEntity(property.getType()); + final MapSqlParameterSource additionalParameters = getPropertyMap((T) value, + (RelationalPersistentEntity) embeddedEntity, prefix + property.getEmbeddedPrefix()); parameters.addValues(additionalParameters.getValues()); } else { Object value = propertyAccessor.getProperty(property); Object convertedValue = convertForWrite(property, value); - parameters.addValue(prefix + property.getColumnName(), convertedValue, JdbcUtil.sqlTypeFor(property.getColumnType())); + parameters.addValue(prefix + property.getColumnName(), convertedValue, + JdbcUtil.sqlTypeFor(property.getColumnType())); } }); return parameters; } + @Nullable private Object convertForWrite(RelationalPersistentProperty property, @Nullable Object value) { @@ -327,9 +347,8 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy { String typeName = JDBCType.valueOf(JdbcUtil.sqlTypeFor(componentType)).getName(); - return operations.getJdbcOperations().execute( - (Connection c) -> c.createArrayOf(typeName, (Object[]) convertedValue) - ); + return operations.getJdbcOperations() + .execute((Connection c) -> c.createArrayOf(typeName, (Object[]) convertedValue)); } @SuppressWarnings("unchecked") @@ -354,22 +373,22 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy { @Nullable private Object getIdFromHolder(KeyHolder holder, RelationalPersistentEntity persistentEntity) { - try { - // MySQL just returns one value with a special name - return holder.getKey(); - } catch (DataRetrievalFailureException | InvalidDataAccessApiUsageException e) { - // Postgres returns a value for each column + try { + // MySQL just returns one value with a special name + return holder.getKey(); + } catch (DataRetrievalFailureException | InvalidDataAccessApiUsageException e) { + // Postgres returns a value for each column // MS SQL Server returns a value that might be null. - Map keys = holder.getKeys(); + Map keys = holder.getKeys(); - if (keys == null || persistentEntity.getIdProperty() == null) { - return null; - } + if (keys == null || persistentEntity.getIdProperty() == null) { + return null; + } - return keys.get(persistentEntity.getIdColumn()); - } - } + return keys.get(persistentEntity.getIdColumn()); + } + } private EntityRowMapper getEntityRowMapper(Class domainType) { return new EntityRowMapper<>(getRequiredPersistentEntity(domainType), context, converter, accessStrategy); diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreter.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreter.java index ea6c6603f..b30ca91e4 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreter.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreter.java @@ -18,9 +18,10 @@ package org.springframework.data.jdbc.core; import lombok.RequiredArgsConstructor; import java.util.Collections; -import java.util.HashMap; import java.util.Map; +import org.springframework.data.jdbc.core.convert.JdbcIdentifierBuilder; +import org.springframework.data.relational.domain.Identifier; import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.relational.core.conversion.DbAction; import org.springframework.data.relational.core.conversion.DbAction.Delete; @@ -58,7 +59,7 @@ class DefaultJdbcInterpreter implements Interpreter { @Override public void interpret(Insert insert) { - Object id = accessStrategy.insert(insert.getEntity(), insert.getEntityType(), createAdditionalColumnValues(insert)); + Object id = accessStrategy.insert(insert.getEntity(), insert.getEntityType(), getParentKeys(insert)); insert.setGeneratedId(id); } @@ -101,7 +102,7 @@ class DefaultJdbcInterpreter implements Interpreter { // temporary implementation if (!accessStrategy.update(merge.getEntity(), merge.getEntityType())) { - accessStrategy.insert(merge.getEntity(), merge.getEntityType(), createAdditionalColumnValues(merge)); + accessStrategy.insert(merge.getEntity(), merge.getEntityType(), getParentKeys(merge)); } } @@ -141,27 +142,21 @@ class DefaultJdbcInterpreter implements Interpreter { accessStrategy.deleteAll(deleteAllRoot.getEntityType()); } - private Map createAdditionalColumnValues(DbAction.WithDependingOn action) { - - Map additionalColumnValues = new HashMap<>(); - addDependingOnInformation(action, additionalColumnValues); - additionalColumnValues.putAll(action.getAdditionalValues()); - - return additionalColumnValues; - } - - private void addDependingOnInformation(DbAction.WithDependingOn action, - Map additionalColumnValues) { + private Identifier getParentKeys(DbAction.WithDependingOn action) { DbAction.WithEntity dependingOn = action.getDependingOn(); RelationalPersistentEntity persistentEntity = context.getRequiredPersistentEntity(dependingOn.getEntityType()); - String columnName = getColumnNameForReverseColumn(action); + Object id = getIdFromEntityDependingOn(dependingOn, persistentEntity); + JdbcIdentifierBuilder identifier = JdbcIdentifierBuilder // + .forBackReferences(action.getPropertyPath(), id); - Object identifier = getIdFromEntityDependingOn(dependingOn, persistentEntity); + for (Map.Entry, Object> qualifier : action.getQualifiers().entrySet()) { + identifier = identifier.withQualifier(qualifier.getKey(), qualifier.getValue()); + } - additionalColumnValues.put(columnName, identifier); + return identifier.build(); } @Nullable @@ -182,9 +177,4 @@ class DefaultJdbcInterpreter implements Interpreter { return persistentEntity.getIdentifierAccessor(entity).getIdentifier(); } - private String getColumnNameForReverseColumn(DbAction.WithPropertyPath action) { - - PersistentPropertyPath path = action.getPropertyPath(); - return path.getRequiredLeafProperty().getReverseColumnName(); - } } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/DelegatingDataAccessStrategy.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/DelegatingDataAccessStrategy.java index 0a833f30e..67382311e 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/DelegatingDataAccessStrategy.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/DelegatingDataAccessStrategy.java @@ -17,6 +17,7 @@ package org.springframework.data.jdbc.core; import java.util.Map; +import org.springframework.data.relational.domain.Identifier; import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.util.Assert; @@ -40,6 +41,15 @@ public class DelegatingDataAccessStrategy implements DataAccessStrategy { return delegate.insert(instance, domainType, additionalParameters); } + /* + * (non-Javadoc) + * @see org.springframework.data.jdbc.core.DataAccessStrategy#insert(java.lang.Object, java.lang.Class, org.springframework.data.jdbc.core.ParentKeys) + */ + @Override + public Object insert(T instance, Class domainType, Identifier identifier) { + return delegate.insert(instance, domainType, identifier); + } + /* * (non-Javadoc) * @see org.springframework.data.jdbc.core.DataAccessStrategy#update(java.lang.Object, java.lang.Class) diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java index 5680a51ba..e94f91bf9 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java @@ -20,17 +20,23 @@ import org.slf4j.LoggerFactory; import org.springframework.core.convert.ConverterNotFoundException; import org.springframework.data.convert.CustomConversions; import org.springframework.data.jdbc.core.mapping.AggregateReference; +import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.model.SimpleTypeHolder; import org.springframework.data.relational.core.conversion.BasicRelationalConverter; import org.springframework.data.relational.core.conversion.RelationalConverter; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; +import org.springframework.data.relational.domain.Identifier; import org.springframework.data.util.TypeInformation; import org.springframework.lang.Nullable; import java.sql.Array; import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; /** * {@link RelationalConverter} that uses a {@link MappingContext} to apply basic conversion of relational values to @@ -122,4 +128,6 @@ public class BasicJdbcConverter extends BasicRelationalConverter implements Jdbc return super.writeValue(value, type); } + + } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcIdentifierBuilder.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcIdentifierBuilder.java new file mode 100644 index 000000000..181bddbbc --- /dev/null +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcIdentifierBuilder.java @@ -0,0 +1,89 @@ +/* + * Copyright 2019 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.convert; + +import java.util.Map; + +import org.springframework.data.mapping.PersistentPropertyPath; +import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; +import org.springframework.data.relational.domain.Identifier; +import org.springframework.lang.Nullable; + +/** + * @author Jens Schauder + * @since 1.1 + */ +public class JdbcIdentifierBuilder { + + private Identifier identifier; + + private JdbcIdentifierBuilder(Identifier identifier) { + this.identifier = identifier; + } + + public static JdbcIdentifierBuilder empty() { + return new JdbcIdentifierBuilder(Identifier.empty()); + } + + public static JdbcIdentifierBuilder from(Map additionalParameters) { + + Identifier[] identifier = new Identifier[] { Identifier.empty() }; + + additionalParameters + .forEach((k, v) -> identifier[0] = identifier[0].add(k, v, v == null ? Object.class : v.getClass())); + + return new JdbcIdentifierBuilder(identifier[0]); + } + + /** + * Creates ParentKeys with backreference for the given path and value of the parents id. + */ + public static JdbcIdentifierBuilder forBackReferences(PersistentPropertyPath path, + @Nullable Object value) { + + Identifier identifier = Identifier.simple( // + path.getRequiredLeafProperty().getReverseColumnName(), // + value, // + getLastIdProperty(path).getColumnType() // + ); + + return new JdbcIdentifierBuilder(identifier); + } + + public JdbcIdentifierBuilder withQualifier(PersistentPropertyPath path, Object value) { + + RelationalPersistentProperty leafProperty = path.getRequiredLeafProperty(); + identifier = identifier.add(leafProperty.getKeyColumn(), value, leafProperty.getQualifierColumnType()); + + return this; + } + + public Identifier build() { + return identifier; + } + + private static RelationalPersistentProperty getLastIdProperty( + PersistentPropertyPath path) { + + RelationalPersistentProperty idProperty = path.getRequiredLeafProperty().getOwner().getIdProperty(); + + if (idProperty != null) { + return idProperty; + } + + return getLastIdProperty(path.getParentPath()); + } +} diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategy.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategy.java index 961845580..27c04a40f 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategy.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategy.java @@ -26,6 +26,7 @@ import org.springframework.data.jdbc.core.CascadingDataAccessStrategy; import org.springframework.data.jdbc.core.DataAccessStrategy; import org.springframework.data.jdbc.core.DefaultDataAccessStrategy; import org.springframework.data.jdbc.core.DelegatingDataAccessStrategy; +import org.springframework.data.relational.domain.Identifier; import org.springframework.data.jdbc.core.SqlGeneratorSource; import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.mapping.PropertyPath; @@ -136,6 +137,19 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy { return myBatisContext.getId(); } + /* + * (non-Javadoc) + * @see org.springframework.data.jdbc.core.DataAccessStrategy#insert(java.lang.Object, java.lang.Class, ParentKeys) + */ + @Override + public Object insert(T instance, Class domainType, Identifier identifier) { + + MyBatisContext myBatisContext = new MyBatisContext(null, instance, domainType, identifier.getParametersByName()); + sqlSession().insert(namespace(domainType) + ".insert", myBatisContext); + + return myBatisContext.getId(); + } + /* * (non-Javadoc) * @see org.springframework.data.jdbc.core.DataAccessStrategy#update(java.lang.Object, java.lang.Class) diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreterUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreterUnitTests.java index 1029746ce..be9a14ae9 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreterUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreterUnitTests.java @@ -16,12 +16,9 @@ package org.springframework.data.jdbc.core; import static org.assertj.core.api.Assertions.*; -import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.*; 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; @@ -31,6 +28,7 @@ import org.springframework.data.relational.core.conversion.DbAction.InsertRoot; import org.springframework.data.relational.core.mapping.NamingStrategy; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; +import org.springframework.data.relational.domain.Identifier; /** * Unit tests for {@link DefaultJdbcInterpreter} @@ -67,10 +65,12 @@ public class DefaultJdbcInterpreterUnitTests { interpreter.interpret(insert); - ArgumentCaptor> argumentCaptor = ArgumentCaptor.forClass(Map.class); + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Identifier.class); verify(dataAccessStrategy).insert(eq(element), eq(Element.class), argumentCaptor.capture()); - assertThat(argumentCaptor.getValue()).containsExactly(new SimpleEntry(BACK_REFERENCE, CONTAINER_ID)); + assertThat(argumentCaptor.getValue().getParameters()) // + .extracting("name", "value", "targetType") // + .containsExactly(tuple(BACK_REFERENCE, CONTAINER_ID, Long.class)); } @Test // DATAJDBC-251 @@ -80,10 +80,12 @@ public class DefaultJdbcInterpreterUnitTests { interpreter.interpret(insert); - ArgumentCaptor> argumentCaptor = ArgumentCaptor.forClass(Map.class); + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Identifier.class); verify(dataAccessStrategy).insert(eq(element), eq(Element.class), argumentCaptor.capture()); - assertThat(argumentCaptor.getValue()).containsExactly(new SimpleEntry(BACK_REFERENCE, CONTAINER_ID)); + assertThat(argumentCaptor.getValue().getParameters()) // + .extracting("name", "value", "targetType") // + .containsExactly(tuple(BACK_REFERENCE, CONTAINER_ID, Long.class)); } @Test // DATAJDBC-251 @@ -93,10 +95,12 @@ public class DefaultJdbcInterpreterUnitTests { interpreter.interpret(insert); - ArgumentCaptor> argumentCaptor = ArgumentCaptor.forClass(Map.class); + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Identifier.class); verify(dataAccessStrategy).insert(eq(element), eq(Element.class), argumentCaptor.capture()); - assertThat(argumentCaptor.getValue()).containsExactly(new SimpleEntry(BACK_REFERENCE, CONTAINER_ID)); + assertThat(argumentCaptor.getValue().getParameters()) // + .extracting("name", "value", "targetType") // + .containsExactly(tuple(BACK_REFERENCE, CONTAINER_ID, Long.class)); } static class Container { diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcIdentifierBuilderUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcIdentifierBuilderUnitTests.java new file mode 100644 index 000000000..20c3454fe --- /dev/null +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcIdentifierBuilderUnitTests.java @@ -0,0 +1,167 @@ +/* + * Copyright 2019 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.springframework.data.jdbc.core.PropertyPathUtils.*; + +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.assertj.core.groups.Tuple; +import org.jetbrains.annotations.NotNull; +import org.junit.Test; +import org.springframework.data.annotation.Id; +import org.springframework.data.jdbc.core.convert.JdbcIdentifierBuilder; +import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; +import org.springframework.data.mapping.PersistentPropertyPath; +import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; +import org.springframework.data.relational.domain.Identifier; + +/** + * Unit tests for the {@link JdbcIdentifierBuilder}. + * + * @author Jens Schauder + */ +public class JdbcIdentifierBuilderUnitTests { + + JdbcMappingContext context = new JdbcMappingContext(); + + @Test // DATAJDBC-326 + public void parametersWithStringKeysUseTheValuesType() { + + HashMap parameters = new HashMap<>(); + parameters.put("one", "eins"); + parameters.put("two", 2L); + + Identifier identifier = JdbcIdentifierBuilder.from(parameters).build(); + + assertThat(identifier.getParameters()) // + .extracting("name", "value", "targetType") // + .containsExactlyInAnyOrder( // + tuple("one", "eins", String.class), // + tuple("two", 2L, Long.class) // + ); + } + + @Test // DATAJDBC-326 + public void parametersWithStringKeysUseObjectAsTypeForNull() { + + HashMap parameters = new HashMap<>(); + parameters.put("one", null); + + Identifier identifier = JdbcIdentifierBuilder.from(parameters).build(); + + assertThat(identifier.getParameters()) // + .extracting("name", "value", "targetType") // + .containsExactly( // + tuple("one", null, Object.class) // + ); + } + + @Test // DATAJDBC-326 + public void parametersWithPropertyKeysUseTheParentPropertyJdbcType() { + + Identifier identifier = JdbcIdentifierBuilder.forBackReferences(getPath("child"), "eins").build(); + + assertThat(identifier.getParameters()) // + .extracting("name", "value", "targetType") // + .containsExactly( // + tuple("dummy_entity", "eins", UUID.class) // + ); + } + + @Test // DATAJDBC-326 + public void qualifiersForMaps() { + + PersistentPropertyPath path = getPath("children"); + + Identifier identifier = JdbcIdentifierBuilder // + .forBackReferences(path, "parent-eins") // + .withQualifier(path, "map-key-eins") // + .build(); + + assertThat(identifier.getParameters()) // + .extracting("name", "value", "targetType") // + .containsExactlyInAnyOrder( // + tuple("dummy_entity", "parent-eins", UUID.class), // + tuple("dummy_entity_key", "map-key-eins", String.class) // + ); + } + + @Test // DATAJDBC-326 + public void qualifiersForLists() { + + PersistentPropertyPath path = getPath("moreChildren"); + + Identifier identifier = JdbcIdentifierBuilder // + .forBackReferences(path, "parent-eins") // + .withQualifier(path, "list-index-eins") // + .build(); + + assertThat(identifier.getParameters()) // + .extracting("name", "value", "targetType") // + .containsExactlyInAnyOrder( // + tuple("dummy_entity", "parent-eins", UUID.class), // + tuple("dummy_entity_key", "list-index-eins", Integer.class) // + ); + } + + @Test // DATAJDBC-326 + public void backreferenceAcrossEmbeddable() { + + Identifier identifier = JdbcIdentifierBuilder // + .forBackReferences(getPath("embeddable.child"), "parent-eins") // + .build(); + + assertThat(identifier.getParameters()) // + .extracting("name", "value", "targetType") // + .containsExactly( // + tuple("embeddable", "parent-eins", UUID.class) // + ); + } + + @NotNull + private PersistentPropertyPath getPath(String dotPath) { + return toPath(dotPath, DummyEntity.class, context); + } + + @SuppressWarnings("unused") + static class DummyEntity { + + @Id UUID id; + String one; + Long two; + Child child; + + Map children; + + List moreChildren; + + Embeddable embeddable; + } + + @SuppressWarnings("unused") + static class Embeddable { + Child child; + } + + static class Child {} +} diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryWithMapsIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryWithMapsIntegrationTests.java index 269a426c5..0239595f1 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryWithMapsIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryWithMapsIntegrationTests.java @@ -96,7 +96,9 @@ public class JdbcRepositoryWithMapsIntegrationTests { public void saveAndLoadNonEmptyMap() { Element element1 = new Element(); + element1.content = "element 1"; Element element2 = new Element(); + element2.content = "element 2"; DummyEntity entity = createDummyEntity(); entity.content.put("one", element1); diff --git a/spring-data-jdbc/src/test/resources/logback.xml b/spring-data-jdbc/src/test/resources/logback.xml index e310de95b..f1bfdbaf3 100644 --- a/spring-data-jdbc/src/test/resources/logback.xml +++ b/spring-data-jdbc/src/test/resources/logback.xml @@ -12,7 +12,7 @@ - + \ No newline at end of file diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-hsql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-hsql.sql index a327aecad..4edb29701 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-hsql.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-hsql.sql @@ -8,6 +8,10 @@ CREATE TABLE ONE_TO_ONE_PARENT ( id3 BIGINT GENERATED BY DEFAULT AS IDENTITY(STA CREATE TABLE Child_No_Id (ONE_TO_ONE_PARENT INTEGER PRIMARY KEY, content VARCHAR(30)); CREATE TABLE LIST_PARENT ( id4 BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, NAME VARCHAR(100)); -CREATE TABLE element_no_id ( content VARCHAR(100), LIST_PARENT_key BIGINT, LIST_PARENT BIGINT); +CREATE TABLE ELEMENT_NO_ID ( content VARCHAR(100), LIST_PARENT_KEY BIGINT, LIST_PARENT BIGINT); +ALTER TABLE ELEMENT_NO_ID + ADD FOREIGN KEY (LIST_PARENT) + REFERENCES LIST_PARENT(id4); + CREATE TABLE ARRAY_OWNER (ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY, DIGITS VARCHAR(20) ARRAY[10] NOT NULL, MULTIDIMENSIONAL VARCHAR(20) ARRAY[10] NULL); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryWithMapsIntegrationTests-hsql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryWithMapsIntegrationTests-hsql.sql index 33c747b6f..15d39c175 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryWithMapsIntegrationTests-hsql.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryWithMapsIntegrationTests-hsql.sql @@ -1,2 +1,6 @@ CREATE TABLE dummy_entity ( id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, NAME VARCHAR(100)); CREATE TABLE element (id BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY, content VARCHAR(100), Dummy_Entity_key VARCHAR(100), dummy_entity BIGINT); + +ALTER TABLE ELEMENT + ADD FOREIGN KEY (dummy_entity) + REFERENCES dummy_entity(id); diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/AggregateChange.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/AggregateChange.java index 86e9bc8ac..037d85193 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/AggregateChange.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/AggregateChange.java @@ -114,8 +114,7 @@ public class AggregateChange { if (leafProperty.isQualified()) { - String keyColumn = leafProperty.getKeyColumn(); - Object keyObject = action.getAdditionalValues().get(keyColumn); + Object keyObject = action.getQualifiers().get(propertyPathToEntity); if (List.class.isAssignableFrom(leafProperty.getType())) { setIdInElementOfList(converter, action, generatedId, (List) currentPropertyValue, (int) keyObject); diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DbAction.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DbAction.java index 171b392bf..0daa1d445 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DbAction.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DbAction.java @@ -75,7 +75,7 @@ public interface DbAction { @NonNull final PersistentPropertyPath propertyPath; @NonNull final WithEntity dependingOn; - Map additionalValues = new HashMap<>(); + Map, Object> qualifiers = new HashMap<>(); private Object generatedId; @@ -154,7 +154,7 @@ public interface DbAction { @NonNull PersistentPropertyPath propertyPath; @NonNull WithEntity dependingOn; - Map additionalValues = new HashMap<>(); + Map, Object> qualifiers = new HashMap<>(); @Override public void doExecuteWith(Interpreter interpreter) { @@ -248,7 +248,7 @@ public interface DbAction { * become available once the parent entity got persisted. * * @return Guaranteed to be not {@code null}. - * @see #getAdditionalValues() + * @see #getQualifiers() */ WithEntity getDependingOn(); @@ -259,7 +259,7 @@ public interface DbAction { * * @return Guaranteed to be not {@code null}. */ - Map getAdditionalValues(); + Map, Object> getQualifiers(); @Override default Class getEntityType() { diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/WritingContext.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/WritingContext.java index 10b287405..6de69781e 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/WritingContext.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/WritingContext.java @@ -127,7 +127,7 @@ class WritingContext { @SuppressWarnings("unchecked") Pair value = (Pair) node.getValue(); insert = new DbAction.Insert<>(value.getSecond(), path, getAction(node.getParent())); - insert.getAdditionalValues().put(node.getPath().getRequiredLeafProperty().getKeyColumn(), value.getFirst()); + insert.getQualifiers().put(node.getPath(), value.getFirst()); } else { insert = new DbAction.Insert<>(node.getValue(), path, getAction(node.getParent())); diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentProperty.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentProperty.java index 001f7fb3c..b9b70ea4a 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentProperty.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentProperty.java @@ -195,6 +195,19 @@ public class BasicRelationalPersistentProperty extends AnnotationBasedPersistent return isMap() || isListLike(); } + @Override + public Class getQualifierColumnType() { + + Assert.isTrue(isQualified(), "The qualifier column type is only defined for properties that are qualified"); + + if (isMap()) { + return getTypeInformation().getComponentType().getType(); + } + + // for lists and arrays + return Integer.class; + } + @Override public boolean isOrdered() { return isListLike(); diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalPersistentProperty.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalPersistentProperty.java index 2653be60f..4d0f60dba 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalPersistentProperty.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalPersistentProperty.java @@ -67,6 +67,8 @@ public interface RelationalPersistentProperty extends PersistentProperty getQualifierColumnType(); + /** * Returns whether this property is an ordered property. */ diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/domain/Identifier.java b/spring-data-relational/src/main/java/org/springframework/data/relational/domain/Identifier.java new file mode 100644 index 000000000..3afbeadc2 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/domain/Identifier.java @@ -0,0 +1,91 @@ +/* + * Copyright 2019 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.relational.domain; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Value; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +/** + * {@literal Identifier} represents a multi part id of an entity. Parts or all of the entity might not have a + * representation as a property in the entity but might only be derived from other entities referencing it. + * + * @author Jens Schauder + * @since 1.1 + */ +public final class Identifier { + + private final List keys; + + private Identifier(List keys) { + this.keys = keys; + } + + static public Identifier empty() { + return new Identifier(Collections.emptyList()); + } + + static public Identifier simple(String name, Object value, Class targetType) { + return new Identifier(Collections.singletonList(new SingleIdentifierValue(name, value, targetType))); + } + + public Identifier add(String name, Object value, Class targetType) { + + List keys = new ArrayList<>(this.keys); + keys.add(new SingleIdentifierValue(name, value, targetType)); + return new Identifier(keys); + } + + @Deprecated + public Map getParametersByName() { + + HashMap result = new HashMap<>(); + forEach(v -> result.put(v.name, v.value)); + return result; + } + + public Collection getParameters() { + return keys; + } + + public void forEach(Consumer consumer) { + getParameters().forEach(consumer); + } + + /** + * A single value of an Identifier consisting of the column name, the value and the target type which is to be used to + * store the element in the database. + * + * @author Jens Schauder + * @since 1.1 + */ + @Value + @AllArgsConstructor(access = AccessLevel.PRIVATE) + public static class SingleIdentifierValue { + + String name; + Object value; + Class targetType; + } +} diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/AggregateChangeUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/AggregateChangeUnitTests.java index d894935b9..ef07b7046 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/AggregateChangeUnitTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/AggregateChangeUnitTests.java @@ -27,7 +27,10 @@ import java.util.Set; import org.junit.Test; import org.springframework.data.annotation.Id; import org.springframework.data.mapping.PersistentPropertyAccessor; +import org.springframework.data.mapping.PersistentPropertyPath; +import org.springframework.data.mapping.PersistentPropertyPaths; import org.springframework.data.relational.core.mapping.RelationalMappingContext; +import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; /** * Unit tests for the {@link AggregateChange}. @@ -52,7 +55,7 @@ public class AggregateChangeUnitTests { DbAction.Insert insert = new DbAction.Insert<>(value, context.getPersistentPropertyPath(propertyName, DummyEntity.class), rootInsert); - insert.getAdditionalValues().put("dummy_entity_key", key); + insert.getQualifiers().put(toPath(propertyName, DummyEntity.class), key); return insert; } @@ -112,6 +115,14 @@ public class AggregateChangeUnitTests { .containsExactlyInAnyOrder(tuple("one", 23)); } + PersistentPropertyPath toPath(String path, Class source) { + + PersistentPropertyPaths persistentPropertyPaths = context + .findPersistentPropertyPaths(source, p -> true); + + return persistentPropertyPaths.filter(p -> p.toDotPath().equals(path)).stream().findFirst().orElse(null); + } + private static class DummyEntity { @Id Integer rootId; diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityWriterUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityWriterUnitTests.java index 1d143329c..98e2b08f4 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityWriterUnitTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityWriterUnitTests.java @@ -30,6 +30,8 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; import org.springframework.data.annotation.Id; +import org.springframework.data.mapping.PersistentPropertyPath; +import org.springframework.data.mapping.PersistentPropertyPaths; import org.springframework.data.relational.core.conversion.AggregateChange.Kind; import org.springframework.data.relational.core.conversion.DbAction.Delete; import org.springframework.data.relational.core.conversion.DbAction.Insert; @@ -37,6 +39,7 @@ import org.springframework.data.relational.core.conversion.DbAction.InsertRoot; import org.springframework.data.relational.core.conversion.DbAction.UpdateRoot; import org.springframework.data.relational.core.mapping.Embedded; import org.springframework.data.relational.core.mapping.RelationalMappingContext; +import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; /** * Unit tests for the {@link RelationalEntityWriter} @@ -47,8 +50,13 @@ import org.springframework.data.relational.core.mapping.RelationalMappingContext @RunWith(MockitoJUnitRunner.class) public class RelationalEntityWriterUnitTests { - public static final long SOME_ENTITY_ID = 23L; - RelationalEntityWriter converter = new RelationalEntityWriter(new RelationalMappingContext()); + static final long SOME_ENTITY_ID = 23L; + final RelationalMappingContext context = new RelationalMappingContext(); + final RelationalEntityWriter converter = new RelationalEntityWriter(context); + + final PersistentPropertyPath listContainerElements = toPath("elements", ListContainer.class, context); + + private final PersistentPropertyPath mapContainerElements = toPath("elements", MapContainer.class, context); @Test // DATAJDBC-112 public void newEntityGetsConvertedToOneInsert() { @@ -60,8 +68,8 @@ public class RelationalEntityWriterUnitTests { converter.write(entity, aggregateChange); assertThat(aggregateChange.getActions()) // - .extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath, DbActionTestSupport::actualEntityType, - DbActionTestSupport::isWithDependsOn) // + .extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath, + DbActionTestSupport::actualEntityType, DbActionTestSupport::isWithDependsOn) // .containsExactly( // tuple(InsertRoot.class, SingleReferenceEntity.class, "", SingleReferenceEntity.class, false) // ); @@ -79,8 +87,8 @@ public class RelationalEntityWriterUnitTests { converter.write(entity, aggregateChange); assertThat(aggregateChange.getActions()) // - .extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath, DbActionTestSupport::actualEntityType, - DbActionTestSupport::isWithDependsOn) // + .extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath, + DbActionTestSupport::actualEntityType, DbActionTestSupport::isWithDependsOn) // .containsExactly( // tuple(InsertRoot.class, EmbeddedReferenceEntity.class, "", EmbeddedReferenceEntity.class, false) // ); @@ -98,8 +106,8 @@ public class RelationalEntityWriterUnitTests { converter.write(entity, aggregateChange); assertThat(aggregateChange.getActions()) // - .extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath, DbActionTestSupport::actualEntityType, - DbActionTestSupport::isWithDependsOn) // + .extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath, + DbActionTestSupport::actualEntityType, DbActionTestSupport::isWithDependsOn) // .containsExactly( // tuple(InsertRoot.class, SingleReferenceEntity.class, "", SingleReferenceEntity.class, false), // tuple(Insert.class, Element.class, "other", Element.class, true) // @@ -117,8 +125,8 @@ public class RelationalEntityWriterUnitTests { converter.write(entity, aggregateChange); assertThat(aggregateChange.getActions()) // - .extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath, DbActionTestSupport::actualEntityType, - DbActionTestSupport::isWithDependsOn) // + .extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath, + DbActionTestSupport::actualEntityType, DbActionTestSupport::isWithDependsOn) // .containsExactly( // tuple(Delete.class, Element.class, "other", null, false), // tuple(UpdateRoot.class, SingleReferenceEntity.class, "", SingleReferenceEntity.class, false) // @@ -131,14 +139,14 @@ public class RelationalEntityWriterUnitTests { SingleReferenceEntity entity = new SingleReferenceEntity(SOME_ENTITY_ID); entity.other = new Element(null); - AggregateChange aggregateChange = new AggregateChange<>(Kind.SAVE, SingleReferenceEntity.class, - entity); + AggregateChange aggregateChange = new AggregateChange<>(Kind.SAVE, + SingleReferenceEntity.class, entity); converter.write(entity, aggregateChange); assertThat(aggregateChange.getActions()) // - .extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath, DbActionTestSupport::actualEntityType, - DbActionTestSupport::isWithDependsOn) // + .extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath, + DbActionTestSupport::actualEntityType, DbActionTestSupport::isWithDependsOn) // .containsExactly( // tuple(Delete.class, Element.class, "other", null, false), // tuple(UpdateRoot.class, SingleReferenceEntity.class, "", SingleReferenceEntity.class, false), // @@ -150,14 +158,14 @@ public class RelationalEntityWriterUnitTests { public void newEntityWithEmptySetResultsInSingleInsert() { SetContainer entity = new SetContainer(null); - AggregateChange aggregateChange = new AggregateChange<>( - Kind.SAVE, SetContainer.class, entity); + AggregateChange aggregateChange = new AggregateChange<>(Kind.SAVE, + SetContainer.class, entity); converter.write(entity, aggregateChange); assertThat(aggregateChange.getActions()) // - .extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath, DbActionTestSupport::actualEntityType, - DbActionTestSupport::isWithDependsOn) // + .extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath, + DbActionTestSupport::actualEntityType, DbActionTestSupport::isWithDependsOn) // .containsExactly( // tuple(InsertRoot.class, SetContainer.class, "", SetContainer.class, false)); } @@ -173,8 +181,8 @@ public class RelationalEntityWriterUnitTests { converter.write(entity, aggregateChange); assertThat(aggregateChange.getActions()) - .extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath, DbActionTestSupport::actualEntityType, - DbActionTestSupport::isWithDependsOn) // + .extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath, + DbActionTestSupport::actualEntityType, DbActionTestSupport::isWithDependsOn) // .containsExactly( // tuple(InsertRoot.class, SetContainer.class, "", SetContainer.class, false), // tuple(Insert.class, Element.class, "elements", Element.class, true), // @@ -203,8 +211,8 @@ public class RelationalEntityWriterUnitTests { converter.write(entity, aggregateChange); assertThat(aggregateChange.getActions()) - .extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath, DbActionTestSupport::actualEntityType, - DbActionTestSupport::isWithDependsOn) // + .extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath, + DbActionTestSupport::actualEntityType, DbActionTestSupport::isWithDependsOn) // .containsExactly( // tuple(InsertRoot.class, CascadingReferenceEntity.class, "", CascadingReferenceEntity.class, false), // tuple(Insert.class, CascadingReferenceMiddleElement.class, "other", CascadingReferenceMiddleElement.class, @@ -239,8 +247,8 @@ public class RelationalEntityWriterUnitTests { converter.write(entity, aggregateChange); assertThat(aggregateChange.getActions()) - .extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath, DbActionTestSupport::actualEntityType, - DbActionTestSupport::isWithDependsOn) // + .extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath, + DbActionTestSupport::actualEntityType, DbActionTestSupport::isWithDependsOn) // .containsExactly( // tuple(Delete.class, Element.class, "other.element", null, false), tuple(Delete.class, CascadingReferenceMiddleElement.class, "other", null, false), @@ -264,7 +272,8 @@ public class RelationalEntityWriterUnitTests { converter.write(entity, aggregateChange); - assertThat(aggregateChange.getActions()).extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath) // + assertThat(aggregateChange.getActions()) + .extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath) // .containsExactly( // tuple(InsertRoot.class, MapContainer.class, "")); } @@ -341,7 +350,8 @@ public class RelationalEntityWriterUnitTests { converter.write(entity, aggregateChange); - assertThat(aggregateChange.getActions()).extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath) // + assertThat(aggregateChange.getActions()) + .extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath) // .containsExactly( // tuple(InsertRoot.class, ListContainer.class, "")); } @@ -418,17 +428,29 @@ public class RelationalEntityWriterUnitTests { } private Object getMapKey(DbAction a) { - return a instanceof DbAction.WithDependingOn - ? ((DbAction.WithDependingOn) a).getAdditionalValues().get("map_container_key") + + return a instanceof DbAction.WithDependingOn // + ? ((DbAction.WithDependingOn) a).getQualifiers().get(mapContainerElements) // : null; } private Object getListKey(DbAction a) { - return a instanceof DbAction.WithDependingOn - ? ((DbAction.WithDependingOn) a).getAdditionalValues().get("list_container_key") + + return a instanceof DbAction.WithDependingOn // + ? ((DbAction.WithDependingOn) a).getQualifiers() + .get(listContainerElements) // : null; } + static PersistentPropertyPath toPath(String path, Class source, + RelationalMappingContext context) { + + PersistentPropertyPaths persistentPropertyPaths = context + .findPersistentPropertyPaths(source, p -> true); + + return persistentPropertyPaths.filter(p -> p.toDotPath().equals(path)).stream().findFirst().orElse(null); + } + @RequiredArgsConstructor static class SingleReferenceEntity { diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/event/IdentifierTest.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/event/IdentifierUnitTests.java similarity index 97% rename from spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/event/IdentifierTest.java rename to spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/event/IdentifierUnitTests.java index 992d29566..a740d9533 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/event/IdentifierTest.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/event/IdentifierUnitTests.java @@ -26,7 +26,7 @@ import org.junit.Test; * * @author Jens Schauder */ -public class IdentifierTest { +public class IdentifierUnitTests { @SuppressWarnings("unchecked") @Test diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/domain/IdentifierUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/domain/IdentifierUnitTests.java new file mode 100644 index 000000000..488b839a2 --- /dev/null +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/domain/IdentifierUnitTests.java @@ -0,0 +1,37 @@ +/* + * Copyright 2019 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.relational.domain; + +import org.junit.Test; + +import java.util.AbstractMap; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Jens Schauder + */ +public class IdentifierUnitTests { + + @Test // DATAJDBC-326 + public void getParametersByName() { + + Identifier identifier = Identifier.simple("aName", "aValue", String.class);; + + assertThat(identifier.getParametersByName()) + .containsExactly(new AbstractMap.SimpleEntry<>("aName", "aValue")); + } +} \ No newline at end of file