From 7a26385b5a1d7d6d54041b2289913ec540d5e2e1 Mon Sep 17 00:00:00 2001 From: Schlagi123 Date: Mon, 21 Jan 2019 11:54:55 +0100 Subject: [PATCH] DATAJDBC-111 - Add support for embeddables. By annotating a reference with `Embedded` that reference will get stored in the same table as the owning entity. Original pull request: #110. --- .../jdbc/core/DefaultDataAccessStrategy.java | 23 +- .../data/jdbc/core/EntityRowMapper.java | 43 ++- .../data/jdbc/core/SqlGenerator.java | 147 +++++++--- .../jdbc/core/EntityRowMapperUnitTests.java | 24 ++ ...qlGeneratorEmbeddedCascadingUnitTests.java | 188 ++++++++++++ .../core/SqlGeneratorEmbeddedUnitTests.java | 155 ++++++++++ .../data/jdbc/core/SqlGeneratorUnitTests.java | 27 +- ...toryEmbeddedCascadingIntegrationTests.java | 262 +++++++++++++++++ ...toryEmbeddedImmutableIntegrationTests.java | 227 +++++++++++++++ ...dbcRepositoryEmbeddedIntegrationTests.java | 236 ++++++++++++++++ ...dedNotInAggregateRootIntegrationTests.java | 257 +++++++++++++++++ ...mbeddedWithCollectionIntegrationTests.java | 267 ++++++++++++++++++ ...EmbeddedWithReferenceIntegrationTests.java | 258 +++++++++++++++++ ...dbcRepositoryConfigExtensionUnitTests.java | 2 - ...EmbeddedCascadingIntegrationTests-hsql.sql | 1 + ...eddedCascadingIntegrationTests-mariadb.sql | 1 + ...mbeddedCascadingIntegrationTests-mssql.sql | 2 + ...mbeddedCascadingIntegrationTests-mysql.sql | 1 + ...ddedCascadingIntegrationTests-postgres.sql | 2 + ...EmbeddedImmutableIntegrationTests-hsql.sql | 1 + ...eddedImmutableIntegrationTests-mariadb.sql | 1 + ...mbeddedImmutableIntegrationTests-mssql.sql | 2 + ...mbeddedImmutableIntegrationTests-mysql.sql | 1 + ...ddedImmutableIntegrationTests-postgres.sql | 2 + ...epositoryEmbeddedIntegrationTests-hsql.sql | 1 + ...sitoryEmbeddedIntegrationTests-mariadb.sql | 1 + ...positoryEmbeddedIntegrationTests-mssql.sql | 2 + ...positoryEmbeddedIntegrationTests-mysql.sql | 1 + ...itoryEmbeddedIntegrationTests-postgres.sql | 2 + ...otInAggregateRootIntegrationTests-hsql.sql | 2 + ...nAggregateRootIntegrationTests-mariadb.sql | 2 + ...tInAggregateRootIntegrationTests-mssql.sql | 4 + ...tInAggregateRootIntegrationTests-mysql.sql | 2 + ...AggregateRootIntegrationTests-postgres.sql | 4 + ...dedWithCollectionIntegrationTests-hsql.sql | 2 + ...WithCollectionIntegrationTests-mariadb.sql | 2 + ...edWithCollectionIntegrationTests-mssql.sql | 4 + ...edWithCollectionIntegrationTests-mysql.sql | 2 + ...ithCollectionIntegrationTests-postgres.sql | 4 + ...ddedWithReferenceIntegrationTests-hsql.sql | 2 + ...dWithReferenceIntegrationTests-mariadb.sql | 2 + ...dedWithReferenceIntegrationTests-mssql.sql | 4 + ...dedWithReferenceIntegrationTests-mysql.sql | 2 + ...WithReferenceIntegrationTests-postgres.sql | 4 + .../RelationalEntityDeleteWriter.java | 3 + .../core/conversion/WritingContext.java | 78 +++-- .../BasicRelationalPersistentProperty.java | 30 +- .../relational/core/mapping/Embedded.java | 22 ++ .../mapping/RelationalPersistentProperty.java | 12 + .../RelationalEntityWriterUnitTests.java | 28 ++ ...RelationalPersistentPropertyUnitTests.java | 34 +++ src/main/asciidoc/jdbc.adoc | 33 ++- 52 files changed, 2328 insertions(+), 91 deletions(-) create mode 100644 spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorEmbeddedCascadingUnitTests.java create mode 100644 spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorEmbeddedUnitTests.java create mode 100644 spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedCascadingIntegrationTests.java create mode 100644 spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedImmutableIntegrationTests.java create mode 100644 spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedIntegrationTests.java create mode 100644 spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests.java create mode 100644 spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedWithCollectionIntegrationTests.java create mode 100644 spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests.java create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedCascadingIntegrationTests-hsql.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedCascadingIntegrationTests-mariadb.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedCascadingIntegrationTests-mssql.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedCascadingIntegrationTests-mysql.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedCascadingIntegrationTests-postgres.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedImmutableIntegrationTests-hsql.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedImmutableIntegrationTests-mariadb.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedImmutableIntegrationTests-mssql.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedImmutableIntegrationTests-mysql.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedImmutableIntegrationTests-postgres.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-hsql.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-mariadb.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-mssql.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-mysql.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-postgres.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests-hsql.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests-mariadb.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests-mssql.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests-mysql.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests-postgres.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithCollectionIntegrationTests-hsql.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithCollectionIntegrationTests-mariadb.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithCollectionIntegrationTests-mssql.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithCollectionIntegrationTests-mysql.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithCollectionIntegrationTests-postgres.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests-hsql.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests-mariadb.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests-mssql.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests-mysql.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests-postgres.sql create mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/Embedded.java 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 cce91538..b04f73a7 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 @@ -50,6 +50,7 @@ import org.springframework.util.Assert; * @author Jens Schauder * @author Mark Paluch * @author Thomas Lang + * @author Bastian Wilhelm */ @RequiredArgsConstructor public class DefaultDataAccessStrategy implements DataAccessStrategy { @@ -89,7 +90,7 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy { RelationalPersistentEntity persistentEntity = getRequiredPersistentEntity(domainType); Map parameters = new LinkedHashMap<>(additionalParameters); - MapSqlParameterSource parameterSource = getPropertyMap(instance, persistentEntity); + MapSqlParameterSource parameterSource = getPropertyMap(instance, persistentEntity, ""); Object idValue = getIdValueOrNull(instance, persistentEntity); RelationalPersistentProperty idProperty = persistentEntity.getIdProperty(); @@ -122,7 +123,7 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy { RelationalPersistentEntity persistentEntity = getRequiredPersistentEntity(domainType); - return operations.update(sql(domainType).getUpdate(), getPropertyMap(instance, persistentEntity)) != 0; + return operations.update(sql(domainType).getUpdate(), getPropertyMap(instance, persistentEntity, "")) != 0; } /* @@ -279,7 +280,7 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy { return result; } - private MapSqlParameterSource getPropertyMap(final S instance, RelationalPersistentEntity persistentEntity) { + private MapSqlParameterSource getPropertyMap(final S instance, RelationalPersistentEntity persistentEntity, String prefix) { MapSqlParameterSource parameters = new MapSqlParameterSource(); @@ -287,14 +288,20 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy { persistentEntity.doWithProperties((PropertyHandler) property -> { - if (property.isEntity()) { + if (property.isEntity() && !property.isEmbedded()) { return; } - Object value = propertyAccessor.getProperty(property); - Object convertedValue = converter.writeValue(value, ClassTypeInformation.from(property.getColumnType())); - parameters.addValue(property.getColumnName(), convertedValue, JdbcUtil.sqlTypeFor(property.getColumnType())); - + if(property.isEmbedded()){ + T value = (T) propertyAccessor.getProperty(property); + final RelationalPersistentEntity embeddedEntity = (RelationalPersistentEntity) context.getPersistentEntity(property.getType()); + final MapSqlParameterSource additionalParameters = getPropertyMap(value, embeddedEntity, prefix + property.getEmbeddedPrefix()); + parameters.addValues(additionalParameters.getValues()); + } else { + Object value = propertyAccessor.getProperty(property); + Object convertedValue = converter.writeValue(value, ClassTypeInformation.from(property.getColumnType())); + parameters.addValue(prefix + property.getColumnName(), convertedValue, JdbcUtil.sqlTypeFor(property.getColumnType())); + } }); return parameters; diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/EntityRowMapper.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/EntityRowMapper.java index 873a794f..6af10051 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/EntityRowMapper.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/EntityRowMapper.java @@ -39,6 +39,7 @@ import org.springframework.util.Assert; * @author Oliver Gierke * @author Mark Paluch * @author Maciej Walkowiak + * @author Bastian Wilhelm */ public class EntityRowMapper implements RowMapper { @@ -105,12 +106,15 @@ public class EntityRowMapper implements RowMapper { } @Nullable - private Object readOrLoadProperty(ResultSet resultSet, @Nullable Object id, RelationalPersistentProperty property, String prefix) { + private Object readOrLoadProperty(ResultSet resultSet, @Nullable Object id, RelationalPersistentProperty property, + String prefix) { if (property.isCollectionLike() && id != null) { return accessStrategy.findAllByProperty(id, property); } else if (property.isMap() && id != null) { return ITERABLE_OF_ENTRY_TO_MAP_CONVERTER.convert(accessStrategy.findAllByProperty(id, property)); + } else if(property.isEmbedded()) { + return readEmbeddedEntityFrom(resultSet, id, property, prefix); } else { return readFrom(resultSet, property, prefix); } @@ -126,9 +130,8 @@ public class EntityRowMapper implements RowMapper { */ @Nullable private Object readFrom(ResultSet resultSet, RelationalPersistentProperty property, String prefix) { - if (property.isEntity()) { - return readEntityFrom(resultSet, property); + return readEntityFrom(resultSet, property, prefix); } Object value = getObjectFromResultSet(resultSet, prefix + property.getColumnName()); @@ -137,9 +140,28 @@ public class EntityRowMapper implements RowMapper { } @Nullable - private S readEntityFrom(ResultSet rs, RelationalPersistentProperty property) { + private S readEmbeddedEntityFrom(ResultSet rs, @Nullable Object id, RelationalPersistentProperty property, String prefix) { + String newPrefix = prefix + property.getEmbeddedPrefix(); - String prefix = property.getName() + "_"; + @SuppressWarnings("unchecked") + RelationalPersistentEntity entity = (RelationalPersistentEntity) context + .getRequiredPersistentEntity(property.getActualType()); + + S instance = createInstance(entity, rs, null, newPrefix); + + PersistentPropertyAccessor accessor = converter.getPropertyAccessor(entity, instance); + + for (RelationalPersistentProperty p : entity) { + accessor.setProperty(p, readOrLoadProperty(rs, id, p, newPrefix)); + } + + return instance; + } + + @Nullable + private S readEntityFrom(ResultSet rs, RelationalPersistentProperty property, String prefix) { + + String newPrefix = prefix + property.getName() + "_"; @SuppressWarnings("unchecked") RelationalPersistentEntity entity = (RelationalPersistentEntity) context @@ -150,22 +172,22 @@ public class EntityRowMapper implements RowMapper { Object idValue = null; if (idProperty != null) { - idValue = readFrom(rs, idProperty, prefix); + idValue = readFrom(rs, idProperty, newPrefix); } if ((idProperty != null // ? idValue // - : getObjectFromResultSet(rs, prefix + property.getReverseColumnName()) // + : getObjectFromResultSet(rs, newPrefix + property.getReverseColumnName()) // ) == null) { return null; } - S instance = createInstance(entity, rs, idValue, prefix); + S instance = createInstance(entity, rs, idValue, newPrefix); PersistentPropertyAccessor accessor = converter.getPropertyAccessor(entity, instance); for (RelationalPersistentProperty p : entity) { - accessor.setProperty(p, readFrom(rs, p, prefix)); + accessor.setProperty(p, readOrLoadProperty(rs, idValue, p, newPrefix)); } return instance; @@ -181,7 +203,8 @@ public class EntityRowMapper implements RowMapper { } } - private S createInstance(RelationalPersistentEntity entity, ResultSet rs, @Nullable Object idValue, String prefix) { + private S createInstance(RelationalPersistentEntity entity, ResultSet rs, @Nullable Object idValue, + String prefix) { return converter.createInstance(entity, parameter -> { diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/SqlGenerator.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/SqlGenerator.java index 50b838e0..defdc2b1 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/SqlGenerator.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/SqlGenerator.java @@ -40,6 +40,7 @@ import org.springframework.util.Assert; * * @author Jens Schauder * @author Yoichi Imai + * @author Bastian Wilhelm */ class SqlGenerator { @@ -67,22 +68,35 @@ class SqlGenerator { this.context = context; this.entity = entity; this.sqlGeneratorSource = sqlGeneratorSource; - initColumnNames(); + initColumnNames(entity, ""); } - private void initColumnNames() { - - entity.doWithProperties((PropertyHandler) p -> { + private void initColumnNames(RelationalPersistentEntity entity, String prefix) { + entity.doWithProperties((PropertyHandler) property -> { // the referencing column of referenced entity is expected to be on the other side of the relation - if (!p.isEntity()) { - columnNames.add(p.getColumnName()); - if (!entity.isIdProperty(p)) { - nonIdColumnNames.add(p.getColumnName()); - } + if (!property.isEntity()) { + initSimpleColumnName(property, prefix); + } else if (property.isEmbedded()) { + initEmbeddedColumnNames(property, prefix); } }); } + private void initSimpleColumnName(RelationalPersistentProperty property, String prefix) { + String columnName = prefix + property.getColumnName(); + columnNames.add(columnName); + if (!entity.isIdProperty(property)) { + nonIdColumnNames.add(columnName); + } + } + + private void initEmbeddedColumnNames(RelationalPersistentProperty property, String prefix) { + final String embeddedPrefix = property.getEmbeddedPrefix(); + final RelationalPersistentEntity embeddedEntity = context.getPersistentEntity(property.getColumnType()); + + initColumnNames(embeddedEntity, prefix + embeddedPrefix); + } + /** * Returns a query for selecting all simple properties of an entitty, including those for one-to-one relationhships. * Results are filtered using an {@code IN}-clause on the id column. @@ -167,8 +181,9 @@ class SqlGenerator { private SelectBuilder createSelectBuilder() { SelectBuilder builder = new SelectBuilder(entity.getTableName()); - addColumnsForSimpleProperties(builder); - addColumnsAndJoinsForOneToOneReferences(builder); + addColumnsForSimpleProperties(entity, "", "", entity, builder); + addColumnsForEmbeddedProperties(entity, "", "", entity, builder); + addColumnsAndJoinsForOneToOneReferences(entity, "", "", entity, builder); return builder; } @@ -177,31 +192,46 @@ class SqlGenerator { * Adds the columns to the provided {@link SelectBuilder} representing simplem properties, including those from * one-to-one relationships. * + * @param rootEntity * @param builder The {@link SelectBuilder} to be modified. */ - private void addColumnsAndJoinsForOneToOneReferences(SelectBuilder builder) { + private void addColumnsAndJoinsForOneToOneReferences(RelationalPersistentEntity entity, String prefix, + String tableAlias, RelationalPersistentEntity rootEntity, SelectBuilder builder) { for (RelationalPersistentProperty property : entity) { if (!property.isEntity() // + || property.isEmbedded() // || Collection.class.isAssignableFrom(property.getType()) // || Map.class.isAssignableFrom(property.getType()) // ) { continue; } - RelationalPersistentEntity refEntity = context.getRequiredPersistentEntity(property.getActualType()); - String joinAlias = property.getName(); - builder.join(jb -> jb.leftOuter().table(refEntity.getTableName()).as(joinAlias) // - .where(property.getReverseColumnName()).eq().column(entity.getTableName(), entity.getIdColumn())); + final RelationalPersistentEntity refEntity = context.getRequiredPersistentEntity(property.getActualType()); + final String joinAlias; - for (RelationalPersistentProperty refProperty : refEntity) { - builder.column( // - cb -> cb.tableAlias(joinAlias) // - .column(refProperty.getColumnName()) // - .as(joinAlias + "_" + refProperty.getColumnName()) // - ); + if (tableAlias.isEmpty()) { + if (prefix.isEmpty()) { + joinAlias = property.getName(); + } else { + joinAlias = prefix + property.getName(); + } + } else { + if (prefix.isEmpty()) { + joinAlias = tableAlias + "_" + property.getName(); + } else { + joinAlias = tableAlias + "_" + prefix + property.getName(); + } } + // final String joinAlias = tableAlias.isEmpty() ? property.getName() : tableAlias + "_" + property.getName(); + builder.join(jb -> jb.leftOuter().table(refEntity.getTableName()).as(joinAlias) // + .where(property.getReverseColumnName()).eq().column(rootEntity.getTableName(), rootEntity.getIdColumn())); + + addColumnsForSimpleProperties(refEntity, "", joinAlias, refEntity, builder); + addColumnsForEmbeddedProperties(refEntity, "", joinAlias, refEntity, builder); + addColumnsAndJoinsForOneToOneReferences(refEntity, "", joinAlias, refEntity, builder); + // if the referenced property doesn't have an id, include the back reference in the select list. // this enables determining if the referenced entity is present or null. if (!refEntity.hasIdProperty()) { @@ -215,18 +245,39 @@ class SqlGenerator { } } - private void addColumnsForSimpleProperties(SelectBuilder builder) { + private void addColumnsForEmbeddedProperties(RelationalPersistentEntity currentEntity, String prefix, + String tableAlias, RelationalPersistentEntity rootEntity, SelectBuilder builder) { + for (RelationalPersistentProperty property : currentEntity) { + if (!property.isEmbedded()) { + continue; + } - for (RelationalPersistentProperty property : entity) { + final String embeddedPrefix = prefix + property.getEmbeddedPrefix(); + final RelationalPersistentEntity embeddedEntity = context + .getRequiredPersistentEntity(property.getColumnType()); + + addColumnsForSimpleProperties(embeddedEntity, embeddedPrefix, tableAlias, rootEntity, builder); + addColumnsForEmbeddedProperties(embeddedEntity, embeddedPrefix, tableAlias, rootEntity, builder); + addColumnsAndJoinsForOneToOneReferences(embeddedEntity, embeddedPrefix, tableAlias, rootEntity, builder); + } + } + + private void addColumnsForSimpleProperties(RelationalPersistentEntity currentEntity, String prefix, + String tableAlias, RelationalPersistentEntity rootEntity, SelectBuilder builder) { + + for (RelationalPersistentProperty property : currentEntity) { if (property.isEntity()) { continue; } + final String column = prefix + property.getColumnName(); + final String as = tableAlias.isEmpty() ? column : tableAlias + "_" + column; + builder.column(cb -> cb // - .tableAlias(entity.getTableName()) // - .column(property.getColumnName()) // - .as(property.getColumnName())); + .tableAlias(tableAlias.isEmpty() ? rootEntity.getTableName() : tableAlias) // + .column(column) // + .as(as)); } } @@ -307,9 +358,8 @@ class SqlGenerator { RelationalPersistentProperty property = path.getBaseProperty(); - String innerMostCondition = String.format("%s IS NOT NULL", property.getReverseColumnName()); - - String condition = cascadeConditions(innerMostCondition, getSubPath(path)); + final String innerMostCondition1 = createInnerMostCondition("%s IS NOT NULL", path); + String condition = cascadeConditions(innerMostCondition1, getSubPath(path)); return String.format("DELETE FROM %s WHERE %s", entityToDelete.getTableName(), condition); } @@ -322,15 +372,23 @@ class SqlGenerator { RelationalPersistentEntity entityToDelete = context .getRequiredPersistentEntity(path.getRequiredLeafProperty().getActualType()); - RelationalPersistentProperty property = path.getBaseProperty(); - - String innerMostCondition = String.format("%s = :rootId", property.getReverseColumnName()); + final String innerMostCondition = createInnerMostCondition("%s = :rootId", path); String condition = cascadeConditions(innerMostCondition, getSubPath(path)); return String.format("DELETE FROM %s WHERE %s", entityToDelete.getTableName(), condition); } + private String createInnerMostCondition(String template, PersistentPropertyPath path) { + PersistentPropertyPath currentPath = path; + while (!currentPath.getParentPath().isEmpty() && !currentPath.getParentPath().getRequiredLeafProperty().isEmbedded()){ + currentPath = currentPath.getParentPath(); + } + + RelationalPersistentProperty property = currentPath.getRequiredLeafProperty(); + return String.format(template, property.getReverseColumnName()); + } + private PersistentPropertyPath getSubPath( PersistentPropertyPath path) { @@ -338,11 +396,21 @@ class SqlGenerator { PersistentPropertyPath ancestor = path; - for (int i = pathLength - 1; i > 0; i--) { - ancestor = path.getParentPath(); + int embeddedDepth = 0; + while (!ancestor.getParentPath().isEmpty() && ancestor.getParentPath().getRequiredLeafProperty().isEmbedded()) { + embeddedDepth++; + ancestor = ancestor.getParentPath(); } - return path.getExtensionForBaseOf(ancestor); + ancestor = path; + + for (int i = pathLength - 1 + embeddedDepth; i > 0; i--) { + ancestor = ancestor.getParentPath(); + } + + final PersistentPropertyPath extensionForBaseOf = path + .getExtensionForBaseOf(ancestor); + return extensionForBaseOf; } private String cascadeConditions(String innerCondition, PersistentPropertyPath path) { @@ -351,8 +419,13 @@ class SqlGenerator { return innerCondition; } + PersistentPropertyPath rootPath = path; + while (rootPath.getLength() > 1) { + rootPath = rootPath.getParentPath(); + } + RelationalPersistentEntity entity = context - .getRequiredPersistentEntity(path.getBaseProperty().getOwner().getTypeInformation()); + .getRequiredPersistentEntity(rootPath.getBaseProperty().getOwner().getTypeInformation()); RelationalPersistentProperty property = path.getRequiredLeafProperty(); return String.format("%s IN (SELECT %s FROM %s WHERE %s)", // diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/EntityRowMapperUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/EntityRowMapperUnitTests.java index ff35091a..ec9d2426 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/EntityRowMapperUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/EntityRowMapperUnitTests.java @@ -46,6 +46,7 @@ import org.springframework.data.jdbc.core.convert.JdbcCustomConversions; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; import org.springframework.data.relational.core.conversion.BasicRelationalConverter; import org.springframework.data.relational.core.conversion.RelationalConverter; +import org.springframework.data.relational.core.mapping.Embedded; import org.springframework.data.relational.core.mapping.NamingStrategy; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; @@ -59,6 +60,7 @@ import org.springframework.util.Assert; * @author Jens Schauder * @author Mark Paluch * @author Maciej Walkowiak + * @author Bastian Wilhelm */ public class EntityRowMapperUnitTests { @@ -147,6 +149,21 @@ public class EntityRowMapperUnitTests { .containsExactly(ID_FOR_ENTITY_NOT_REFERENCING_MAP, "alpha", 24L, "beta"); } + @Test // DATAJDBC-111 + public void simpleEmbeddedGetsProperlyExtracted() throws SQLException { + + ResultSet rs = mockResultSet(asList("id", "name", "prefix_id", "prefix_name"), // + ID_FOR_ENTITY_NOT_REFERENCING_MAP, "alpha", 24L, "beta"); + rs.next(); + + EmbeddedEntity extracted = createRowMapper(EmbeddedEntity.class).mapRow(rs, 1); + + assertThat(extracted) // + .isNotNull() // + .extracting(e -> e.id, e -> e.name, e -> e.children.id, e -> e.children.name) // + .containsExactly(ID_FOR_ENTITY_NOT_REFERENCING_MAP, "alpha", 24L, "beta"); + } + @Test // DATAJDBC-113 public void collectionReferenceGetsLoadedWithAdditionalSelect() throws SQLException { @@ -417,6 +434,13 @@ public class EntityRowMapperUnitTests { List children; } + static class EmbeddedEntity { + + @Id Long id; + String name; + @Embedded("prefix_") Trivial children; + } + private static class DontUseSetter { String value; diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorEmbeddedCascadingUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorEmbeddedCascadingUnitTests.java new file mode 100644 index 00000000..ac9a628d --- /dev/null +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorEmbeddedCascadingUnitTests.java @@ -0,0 +1,188 @@ +/* + * Copyright 2017-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 java.util.Collections.*; + +import org.assertj.core.api.SoftAssertions; +import org.junit.Before; +import org.junit.Test; +import org.springframework.data.annotation.Id; +import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; +import org.springframework.data.relational.core.mapping.Column; +import org.springframework.data.relational.core.mapping.Embedded; +import org.springframework.data.relational.core.mapping.RelationalMappingContext; +import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; + +/** + * Unit tests for the {@link SqlGenerator} in a context of the {@link Embedded} annotation. + * + * @author Bastian Wilhelm + */ +public class SqlGeneratorEmbeddedCascadingUnitTests { + + private SqlGenerator sqlGenerator; + + @Before + public void setUp() { + this.sqlGenerator = createSqlGenerator(DummyEntity.class); + } + + SqlGenerator createSqlGenerator(Class type) { + RelationalMappingContext context = new JdbcMappingContext(); + RelationalPersistentEntity persistentEntity = context.getRequiredPersistentEntity(type); + return new SqlGenerator(context, persistentEntity, new SqlGeneratorSource(context)); + } + + @Test // DATAJDBC-111 + public void findOne() { + final String sql = sqlGenerator.getFindOne(); + + SoftAssertions softAssertions = new SoftAssertions(); + softAssertions.assertThat(sql) + .startsWith("SELECT") + .contains("dummy_entity.id1 AS id1") + .contains("dummy_entity.test AS test") + .contains("dummy_entity.attr1 AS attr1") + .contains("dummy_entity.attr2 AS attr2") + .contains("dummy_entity.prefix2_attr1 AS prefix2_attr1") + .contains("dummy_entity.prefix2_attr2 AS prefix2_attr2") + .contains("dummy_entity.prefix_test AS prefix_test") + .contains("dummy_entity.prefix_attr1 AS prefix_attr1") + .contains("dummy_entity.prefix_attr2 AS prefix_attr2") + .contains("dummy_entity.prefix_prefix2_attr1 AS prefix_prefix2_attr1") + .contains("dummy_entity.prefix_prefix2_attr2 AS prefix_prefix2_attr2") + .contains("WHERE dummy_entity.id1 = :id") + .doesNotContain("JOIN").doesNotContain("embeddable"); + softAssertions.assertAll(); + } + + @Test // DATAJDBC-111 + public void findAll() { + final String sql = sqlGenerator.getFindAll(); + + SoftAssertions softAssertions = new SoftAssertions(); + softAssertions.assertThat(sql) + .startsWith("SELECT") + .contains("dummy_entity.id1 AS id1") + .contains("dummy_entity.test AS test") + .contains("dummy_entity.attr1 AS attr1") + .contains("dummy_entity.attr2 AS attr2") + .contains("dummy_entity.prefix2_attr1 AS prefix2_attr1") + .contains("dummy_entity.prefix2_attr2 AS prefix2_attr2") + .contains("dummy_entity.prefix_test AS prefix_test") + .contains("dummy_entity.prefix_attr1 AS prefix_attr1") + .contains("dummy_entity.prefix_attr2 AS prefix_attr2") + .contains("dummy_entity.prefix_prefix2_attr1 AS prefix_prefix2_attr1") + .contains("dummy_entity.prefix_prefix2_attr2 AS prefix_prefix2_attr2") + .doesNotContain("JOIN").doesNotContain("embeddable"); + softAssertions.assertAll(); + } + + @Test // DATAJDBC-111 + public void findAllInList() { + final String sql = sqlGenerator.getFindAllInList(); + + SoftAssertions softAssertions = new SoftAssertions(); + softAssertions.assertThat(sql) + .startsWith("SELECT") + .contains("dummy_entity.id1 AS id1") + .contains("dummy_entity.test AS test") + .contains("dummy_entity.attr1 AS attr1") + .contains("dummy_entity.attr2 AS attr2") + .contains("dummy_entity.prefix2_attr1 AS prefix2_attr1") + .contains("dummy_entity.prefix2_attr2 AS prefix2_attr2") + .contains("dummy_entity.prefix_test AS prefix_test") + .contains("dummy_entity.prefix_attr1 AS prefix_attr1") + .contains("dummy_entity.prefix_attr2 AS prefix_attr2") + .contains("dummy_entity.prefix_prefix2_attr1 AS prefix_prefix2_attr1") + .contains("dummy_entity.prefix_prefix2_attr2 AS prefix_prefix2_attr2") + .contains("WHERE dummy_entity.id1 in(:ids)") + .doesNotContain("JOIN").doesNotContain("embeddable"); + softAssertions.assertAll(); + } + + @Test // DATAJDBC-111 + public void insert() { + final String sql = sqlGenerator.getInsert(emptySet()); + + SoftAssertions softAssertions = new SoftAssertions(); + softAssertions.assertThat(sql) + .startsWith("INSERT INTO") + .contains("dummy_entity") + .contains(":test") + .contains(":attr1") + .contains(":attr2") + .contains(":prefix2_attr1") + .contains(":prefix2_attr2") + .contains(":prefix_test") + .contains(":prefix_attr1") + .contains(":prefix_attr2") + .contains(":prefix_prefix2_attr1") + .contains(":prefix_prefix2_attr2"); + softAssertions.assertAll(); + } + + @Test // DATAJDBC-111 + public void update() { + final String sql = sqlGenerator.getUpdate(); + + SoftAssertions softAssertions = new SoftAssertions(); + softAssertions.assertThat(sql) + .startsWith("UPDATE") + .contains("dummy_entity") + .contains("test = :test") + .contains("attr1 = :attr1") + .contains("attr2 = :attr2") + .contains("prefix2_attr1 = :prefix2_attr1") + .contains("prefix2_attr2 = :prefix2_attr2") + .contains("prefix_test = :prefix_test") + .contains("prefix_attr1 = :prefix_attr1") + .contains("prefix_attr2 = :prefix_attr2") + .contains("prefix_prefix2_attr1 = :prefix_prefix2_attr1") + .contains("prefix_prefix2_attr2 = :prefix_prefix2_attr2"); + softAssertions.assertAll(); + } + + @SuppressWarnings("unused") + static class DummyEntity { + + @Column("id1") + @Id + Long id; + + @Embedded("prefix_") + CascadedEmbedded prefixedEmbeddable; + + @Embedded + CascadedEmbedded embeddable; + } + + @SuppressWarnings("unused") + static class CascadedEmbedded + { + String test; + @Embedded("prefix2_") Embeddable prefixedEmbeddable; + @Embedded Embeddable embeddable; + } + + @SuppressWarnings("unused") + static class Embeddable + { + Long attr1; + String attr2; + } +} diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorEmbeddedUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorEmbeddedUnitTests.java new file mode 100644 index 00000000..b8952470 --- /dev/null +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorEmbeddedUnitTests.java @@ -0,0 +1,155 @@ +/* + * Copyright 2017-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 org.assertj.core.api.SoftAssertions; +import org.junit.Before; +import org.junit.Test; +import org.springframework.data.annotation.Id; +import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; +import org.springframework.data.jdbc.core.mapping.PersistentPropertyPathTestUtils; +import org.springframework.data.mapping.PersistentPropertyPath; +import org.springframework.data.relational.core.mapping.Column; +import org.springframework.data.relational.core.mapping.Embedded; +import org.springframework.data.relational.core.mapping.NamingStrategy; +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 static java.util.Collections.emptySet; +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Unit tests for the {@link SqlGenerator} in a context of the {@link Embedded} annotation. + * + * @author Bastian Wilhelm + */ +public class SqlGeneratorEmbeddedUnitTests { + + private SqlGenerator sqlGenerator; + + @Before + public void setUp() { + this.sqlGenerator = createSqlGenerator(DummyEntity.class); + } + + SqlGenerator createSqlGenerator(Class type) { + RelationalMappingContext context = new JdbcMappingContext(); + RelationalPersistentEntity persistentEntity = context.getRequiredPersistentEntity(type); + return new SqlGenerator(context, persistentEntity, new SqlGeneratorSource(context)); + } + + @Test // DATAJDBC-111 + public void findOne() { + final String sql = sqlGenerator.getFindOne(); + + SoftAssertions softAssertions = new SoftAssertions(); + softAssertions.assertThat(sql) + .startsWith("SELECT") + .contains("dummy_entity.id1 AS id1") + .contains("dummy_entity.attr1 AS attr1") + .contains("dummy_entity.attr2 AS attr2") + .contains("dummy_entity.prefix_attr1 AS prefix_attr1") + .contains("dummy_entity.prefix_attr2 AS prefix_attr2") + .contains("WHERE dummy_entity.id1 = :id") + .doesNotContain("JOIN").doesNotContain("embeddable"); + softAssertions.assertAll(); + } + + @Test // DATAJDBC-111 + public void findAll() { + final String sql = sqlGenerator.getFindAll(); + + SoftAssertions softAssertions = new SoftAssertions(); + softAssertions.assertThat(sql) + .startsWith("SELECT") + .contains("dummy_entity.id1 AS id1") + .contains("dummy_entity.attr1 AS attr1") + .contains("dummy_entity.attr2 AS attr2") + .contains("dummy_entity.prefix_attr1 AS prefix_attr1") + .contains("dummy_entity.prefix_attr2 AS prefix_attr2") + .doesNotContain("JOIN").doesNotContain("embeddable"); + softAssertions.assertAll(); + } + + @Test // DATAJDBC-111 + public void findAllInList() { + final String sql = sqlGenerator.getFindAllInList(); + + SoftAssertions softAssertions = new SoftAssertions(); + softAssertions.assertThat(sql) + .startsWith("SELECT") + .contains("dummy_entity.id1 AS id1") + .contains("dummy_entity.attr1 AS attr1") + .contains("dummy_entity.attr2 AS attr2") + .contains("dummy_entity.prefix_attr1 AS prefix_attr1") + .contains("dummy_entity.prefix_attr2 AS prefix_attr2") + .contains("WHERE dummy_entity.id1 in(:ids)") + .doesNotContain("JOIN").doesNotContain("embeddable"); + softAssertions.assertAll(); + } + + @Test // DATAJDBC-111 + public void insert() { + final String sql = sqlGenerator.getInsert(emptySet()); + + SoftAssertions softAssertions = new SoftAssertions(); + softAssertions.assertThat(sql) + .startsWith("INSERT INTO") + .contains("dummy_entity") + .contains(":attr1") + .contains(":attr2") + .contains(":prefix_attr1") + .contains(":prefix_attr2"); + softAssertions.assertAll(); + } + + @Test // DATAJDBC-111 + public void update() { + final String sql = sqlGenerator.getUpdate(); + + SoftAssertions softAssertions = new SoftAssertions(); + softAssertions.assertThat(sql) + .startsWith("UPDATE") + .contains("dummy_entity") + .contains("attr1 = :attr1") + .contains("attr2 = :attr2") + .contains("prefix_attr1 = :prefix_attr1") + .contains("prefix_attr2 = :prefix_attr2"); + softAssertions.assertAll(); + } + + @SuppressWarnings("unused") + static class DummyEntity { + + @Column("id1") + @Id + Long id; + + @Embedded("prefix_") + Embeddable prefixedEmbeddable; + + @Embedded + Embeddable embeddable; + } + + @SuppressWarnings("unused") + static class Embeddable + { + Long attr1; + String attr2; + } +} diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorUnitTests.java index 88d870f3..57517c86 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorUnitTests.java @@ -139,7 +139,7 @@ public class SqlGeneratorUnitTests { assertThat(sql).isEqualTo("DELETE FROM element WHERE dummy_entity = :rootId"); } - @Test // DATAJDBC-131 + @Test // DATAJDBC-131, DATAJDBC-111 public void findAllByProperty() { // this would get called when ListParent is the element type of a Set @@ -147,12 +147,15 @@ public class SqlGeneratorUnitTests { assertThat(sql).isEqualTo("SELECT dummy_entity.id1 AS id1, dummy_entity.x_name AS x_name, " // + "dummy_entity.x_other AS x_other, " // - + "ref.x_l1id AS ref_x_l1id, ref.x_content AS ref_x_content, ref.x_further AS ref_x_further " // - + "FROM dummy_entity LEFT OUTER JOIN referenced_entity AS ref ON ref.dummy_entity = dummy_entity.id1 " // + + "ref.x_l1id AS ref_x_l1id, ref.x_content AS ref_x_content, " + + "ref_further.x_l2id AS ref_further_x_l2id, ref_further.x_something AS ref_further_x_something " // + + "FROM dummy_entity " + + "LEFT OUTER JOIN referenced_entity AS ref ON ref.dummy_entity = dummy_entity.id1 " // + + "LEFT OUTER JOIN second_level_referenced_entity AS ref_further ON ref_further.referenced_entity = referenced_entity.x_l1id " // + "WHERE back-ref = :back-ref"); } - @Test // DATAJDBC-131 + @Test // DATAJDBC-131, DATAJDBC-111 public void findAllByPropertyWithKey() { // this would get called when ListParent is th element type of a Map @@ -160,9 +163,12 @@ public class SqlGeneratorUnitTests { assertThat(sql).isEqualTo("SELECT dummy_entity.id1 AS id1, dummy_entity.x_name AS x_name, " // + "dummy_entity.x_other AS x_other, " // - + "ref.x_l1id AS ref_x_l1id, ref.x_content AS ref_x_content, ref.x_further AS ref_x_further, " // + + "ref.x_l1id AS ref_x_l1id, ref.x_content AS ref_x_content, " + + "ref_further.x_l2id AS ref_further_x_l2id, ref_further.x_something AS ref_further_x_something, " // + "dummy_entity.key-column AS key-column " // - + "FROM dummy_entity LEFT OUTER JOIN referenced_entity AS ref ON ref.dummy_entity = dummy_entity.id1 " // + + "FROM dummy_entity " + + "LEFT OUTER JOIN referenced_entity AS ref ON ref.dummy_entity = dummy_entity.id1 " // + + "LEFT OUTER JOIN second_level_referenced_entity AS ref_further ON ref_further.referenced_entity = referenced_entity.x_l1id " // + "WHERE back-ref = :back-ref"); } @@ -171,7 +177,7 @@ public class SqlGeneratorUnitTests { String sql = sqlGenerator.getFindAllByProperty("back-ref", null, true); } - @Test // DATAJDBC-131 + @Test // DATAJDBC-131, DATAJDBC-111 public void findAllByPropertyWithKeyOrdered() { // this would get called when ListParent is th element type of a Map @@ -179,9 +185,12 @@ public class SqlGeneratorUnitTests { assertThat(sql).isEqualTo("SELECT dummy_entity.id1 AS id1, dummy_entity.x_name AS x_name, " // + "dummy_entity.x_other AS x_other, " // - + "ref.x_l1id AS ref_x_l1id, ref.x_content AS ref_x_content, ref.x_further AS ref_x_further, " // + + "ref.x_l1id AS ref_x_l1id, ref.x_content AS ref_x_content, " + + "ref_further.x_l2id AS ref_further_x_l2id, ref_further.x_something AS ref_further_x_something, " // + "dummy_entity.key-column AS key-column " // - + "FROM dummy_entity LEFT OUTER JOIN referenced_entity AS ref ON ref.dummy_entity = dummy_entity.id1 " // + + "FROM dummy_entity " + + "LEFT OUTER JOIN referenced_entity AS ref ON ref.dummy_entity = dummy_entity.id1 " // + + "LEFT OUTER JOIN second_level_referenced_entity AS ref_further ON ref_further.referenced_entity = referenced_entity.x_l1id " // + "WHERE back-ref = :back-ref " + "ORDER BY key-column"); } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedCascadingIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedCascadingIntegrationTests.java new file mode 100644 index 00000000..3929c1fb --- /dev/null +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedCascadingIntegrationTests.java @@ -0,0 +1,262 @@ +/* + * Copyright 2017-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.repository; + +import static java.util.Arrays.*; +import static org.assertj.core.api.Assertions.*; + +import lombok.Data; + +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.annotation.Id; +import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; +import org.springframework.data.jdbc.testing.TestConfiguration; +import org.springframework.data.relational.core.mapping.Embedded; +import org.springframework.data.repository.CrudRepository; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.rules.SpringClassRule; +import org.springframework.test.context.junit4.rules.SpringMethodRule; +import org.springframework.test.jdbc.JdbcTestUtils; +import org.springframework.transaction.annotation.Transactional; + +/** + * Very simple use cases for creation and usage of JdbcRepositories with test {@link Embedded} annotation in Entities. + * + * @author Bastian Wilhelm + */ +@ContextConfiguration +@Transactional +public class JdbcRepositoryEmbeddedCascadingIntegrationTests { + + @Configuration + @Import(TestConfiguration.class) + static class Config { + + @Autowired JdbcRepositoryFactory factory; + + @Bean + Class testClass() { + return JdbcRepositoryEmbeddedCascadingIntegrationTests.class; + } + + @Bean + DummyEntityRepository dummyEntityRepository() { + return factory.getRepository(DummyEntityRepository.class); + } + + } + + @ClassRule public static final SpringClassRule classRule = new SpringClassRule(); + @Rule public SpringMethodRule methodRule = new SpringMethodRule(); + + @Autowired NamedParameterJdbcTemplate template; + @Autowired DummyEntityRepository repository; + + @Test // DATAJDBC-111 + public void savesAnEntity() { + + DummyEntity entity = repository.save(createDummyEntity()); + + assertThat(JdbcTestUtils.countRowsInTableWhere((JdbcTemplate) template.getJdbcOperations(), "dummy_entity", + "id = " + entity.getId())).isEqualTo(1); + } + + @Test // DATAJDBC-111 + public void saveAndLoadAnEntity() { + + DummyEntity entity = repository.save(createDummyEntity()); + + assertThat(repository.findById(entity.getId())).hasValueSatisfying(it -> { + assertThat(it.getId()).isEqualTo(entity.getId()); + assertThat(it.getPrefixedEmbeddable().getTest()).isEqualTo(entity.getPrefixedEmbeddable().getTest()); + assertThat(it.getPrefixedEmbeddable().getEmbeddable().getAttr()).isEqualTo(entity.getPrefixedEmbeddable().getEmbeddable().getAttr()); + assertThat(it.getEmbeddable().getTest()).isEqualTo(entity.getEmbeddable().getTest()); + assertThat(it.getEmbeddable().getEmbeddable().getAttr()).isEqualTo(entity.getEmbeddable().getEmbeddable().getAttr()); + }); + } + + @Test // DATAJDBC-111 + public void findAllFindsAllEntities() { + + DummyEntity entity = repository.save(createDummyEntity()); + DummyEntity other = repository.save(createDummyEntity()); + + Iterable all = repository.findAll(); + + assertThat(all)// + .extracting(DummyEntity::getId)// + .containsExactlyInAnyOrder(entity.getId(), other.getId()); + } + + @Test // DATAJDBC-111 + public void findByIdReturnsEmptyWhenNoneFound() { + + // NOT saving anything, so DB is empty + assertThat(repository.findById(-1L)).isEmpty(); + } + + @Test // DATAJDBC-111 + public void update() { + + DummyEntity entity = repository.save(createDummyEntity()); + + entity.getPrefixedEmbeddable().setTest("something else"); + entity.getPrefixedEmbeddable().getEmbeddable().setAttr(3L); + DummyEntity saved = repository.save(entity); + + assertThat(repository.findById(entity.getId())).hasValueSatisfying(it -> { + assertThat(it.getPrefixedEmbeddable().getTest()).isEqualTo(saved.getPrefixedEmbeddable().getTest()); + assertThat(it.getPrefixedEmbeddable().getEmbeddable().getAttr()).isEqualTo(saved.getPrefixedEmbeddable().getEmbeddable().getAttr()); + }); + } + + @Test // DATAJDBC-111 + public void updateMany() { + + DummyEntity entity = repository.save(createDummyEntity()); + DummyEntity other = repository.save(createDummyEntity()); + + entity.getEmbeddable().setTest("something else"); + other.getEmbeddable().setTest("others Name"); + + entity.getPrefixedEmbeddable().getEmbeddable().setAttr(3L); + other.getPrefixedEmbeddable().getEmbeddable().setAttr(5L); + + repository.saveAll(asList(entity, other)); + + assertThat(repository.findAll()) // + .extracting(d -> d.getEmbeddable().getTest()) // + .containsExactlyInAnyOrder(entity.getEmbeddable().getTest(), other.getEmbeddable().getTest()); + + assertThat(repository.findAll()) // + .extracting(d -> d.getPrefixedEmbeddable().getEmbeddable().getAttr()) // + .containsExactlyInAnyOrder(entity.getPrefixedEmbeddable().getEmbeddable().getAttr(), other.getPrefixedEmbeddable().getEmbeddable().getAttr()); + } + + @Test // DATAJDBC-111 + public void deleteById() { + + DummyEntity one = repository.save(createDummyEntity()); + DummyEntity two = repository.save(createDummyEntity()); + DummyEntity three = repository.save(createDummyEntity()); + + repository.deleteById(two.getId()); + + assertThat(repository.findAll()) // + .extracting(DummyEntity::getId) // + .containsExactlyInAnyOrder(one.getId(), three.getId()); + } + + @Test // DATAJDBC-111 + public void deleteByEntity() { + DummyEntity one = repository.save(createDummyEntity()); + DummyEntity two = repository.save(createDummyEntity()); + DummyEntity three = repository.save(createDummyEntity()); + + repository.delete(one); + + assertThat(repository.findAll()) // + .extracting(DummyEntity::getId) // + .containsExactlyInAnyOrder(two.getId(), three.getId()); + } + + @Test // DATAJDBC-111 + public void deleteByList() { + + DummyEntity one = repository.save(createDummyEntity()); + DummyEntity two = repository.save(createDummyEntity()); + DummyEntity three = repository.save(createDummyEntity()); + + repository.deleteAll(asList(one, three)); + + assertThat(repository.findAll()) // + .extracting(DummyEntity::getId) // + .containsExactlyInAnyOrder(two.getId()); + } + + @Test // DATAJDBC-111 + public void deleteAll() { + + repository.save(createDummyEntity()); + repository.save(createDummyEntity()); + repository.save(createDummyEntity()); + + assertThat(repository.findAll()).isNotEmpty(); + + repository.deleteAll(); + + assertThat(repository.findAll()).isEmpty(); + } + + + private static DummyEntity createDummyEntity() { + DummyEntity entity = new DummyEntity(); + + final CascadedEmbeddable prefixedCascadedEmbeddable = new CascadedEmbeddable(); + prefixedCascadedEmbeddable.setTest("c1"); + + final Embeddable embeddable1 = new Embeddable(); + embeddable1.setAttr(1L); + prefixedCascadedEmbeddable.setEmbeddable(embeddable1); + + entity.setPrefixedEmbeddable(prefixedCascadedEmbeddable); + + + final CascadedEmbeddable cascadedEmbeddable = new CascadedEmbeddable(); + cascadedEmbeddable.setTest("c2"); + + final Embeddable embeddable2 = new Embeddable(); + embeddable2.setAttr(2L); + cascadedEmbeddable.setEmbeddable(embeddable2); + + entity.setEmbeddable(cascadedEmbeddable); + + return entity; + } + + interface DummyEntityRepository extends CrudRepository {} + + @Data + static class DummyEntity { + + @Id Long id; + + @Embedded("prefix_") CascadedEmbeddable prefixedEmbeddable; + + @Embedded CascadedEmbeddable embeddable; + } + + @Data + static class CascadedEmbeddable { + String test; + + @Embedded("prefix2_") + Embeddable embeddable; + } + + @Data + static class Embeddable { + Long attr; + } +} diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedImmutableIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedImmutableIntegrationTests.java new file mode 100644 index 00000000..cc9972fd --- /dev/null +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedImmutableIntegrationTests.java @@ -0,0 +1,227 @@ +/* + * Copyright 2017-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.repository; + +import static java.util.Arrays.*; +import static org.assertj.core.api.Assertions.*; + +import lombok.Data; + +import lombok.Value; +import lombok.experimental.Wither; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.annotation.Id; +import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; +import org.springframework.data.jdbc.testing.TestConfiguration; +import org.springframework.data.relational.core.mapping.Embedded; +import org.springframework.data.repository.CrudRepository; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.rules.SpringClassRule; +import org.springframework.test.context.junit4.rules.SpringMethodRule; +import org.springframework.test.jdbc.JdbcTestUtils; +import org.springframework.transaction.annotation.Transactional; + +/** + * Very simple use cases for creation and usage of JdbcRepositories with {@link Embedded} annotation in Entities. + * + * @author Bastian Wilhelm + */ +@ContextConfiguration +@Transactional +public class JdbcRepositoryEmbeddedImmutableIntegrationTests { + + @Configuration + @Import(TestConfiguration.class) + static class Config { + + @Autowired JdbcRepositoryFactory factory; + + @Bean + Class testClass() { + return JdbcRepositoryEmbeddedImmutableIntegrationTests.class; + } + + @Bean + DummyEntityRepository dummyEntityRepository() { + return factory.getRepository(DummyEntityRepository.class); + } + + } + + @ClassRule public static final SpringClassRule classRule = new SpringClassRule(); + @Rule public SpringMethodRule methodRule = new SpringMethodRule(); + + @Autowired NamedParameterJdbcTemplate template; + @Autowired DummyEntityRepository repository; + + @Test // DATAJDBC-111 + public void savesAnEntity() { + + DummyEntity entity = repository.save(createDummyEntity()); + + assertThat(JdbcTestUtils.countRowsInTableWhere((JdbcTemplate) template.getJdbcOperations(), "dummy_entity", + "id = " + entity.getId())).isEqualTo(1); + } + + @Test // DATAJDBC-111 + public void saveAndLoadAnEntity() { + + DummyEntity entity = repository.save(createDummyEntity()); + + assertThat(repository.findById(entity.getId())).hasValueSatisfying(it -> { + assertThat(it.getId()).isEqualTo(entity.getId()); + assertThat(it.getPrefixedEmbeddable().getAttr1()).isEqualTo(entity.getPrefixedEmbeddable().getAttr1()); + assertThat(it.getPrefixedEmbeddable().getAttr2()).isEqualTo(entity.getPrefixedEmbeddable().getAttr2()); + }); + } + + @Test // DATAJDBC-111 + public void findAllFindsAllEntities() { + + DummyEntity entity = repository.save(createDummyEntity()); + DummyEntity other = repository.save(createDummyEntity()); + + Iterable all = repository.findAll(); + + assertThat(all)// + .extracting(DummyEntity::getId)// + .containsExactlyInAnyOrder(entity.getId(), other.getId()); + } + + @Test // DATAJDBC-111 + public void findByIdReturnsEmptyWhenNoneFound() { + + // NOT saving anything, so DB is empty + assertThat(repository.findById(-1L)).isEmpty(); + } + + @Test // DATAJDBC-111 + public void update() { + + DummyEntity entity = repository.save(createDummyEntity()); + + entity.setPrefixedEmbeddable(entity.getPrefixedEmbeddable().withAttr2("something else")); + DummyEntity saved = repository.save(entity); + + assertThat(repository.findById(entity.getId())).hasValueSatisfying(it -> { + assertThat(it.getPrefixedEmbeddable().getAttr2()).isEqualTo(saved.getPrefixedEmbeddable().getAttr2()); + }); + } + + @Test // DATAJDBC-111 + public void updateMany() { + + DummyEntity entity = repository.save(createDummyEntity()); + DummyEntity other = repository.save(createDummyEntity()); + + entity.setPrefixedEmbeddable(entity.getPrefixedEmbeddable().withAttr2("something else")); + other.setPrefixedEmbeddable(entity.getPrefixedEmbeddable().withAttr2("others Name")); + + repository.saveAll(asList(entity, other)); + + assertThat(repository.findAll()) // + .extracting(d -> d.getPrefixedEmbeddable().getAttr2()) // + .containsExactlyInAnyOrder(entity.getPrefixedEmbeddable().getAttr2(), other.getPrefixedEmbeddable().getAttr2()); + } + + @Test // DATAJDBC-111 + public void deleteById() { + + DummyEntity one = repository.save(createDummyEntity()); + DummyEntity two = repository.save(createDummyEntity()); + DummyEntity three = repository.save(createDummyEntity()); + + repository.deleteById(two.getId()); + + assertThat(repository.findAll()) // + .extracting(DummyEntity::getId) // + .containsExactlyInAnyOrder(one.getId(), three.getId()); + } + + @Test // DATAJDBC-111 + public void deleteByEntity() { + DummyEntity one = repository.save(createDummyEntity()); + DummyEntity two = repository.save(createDummyEntity()); + DummyEntity three = repository.save(createDummyEntity()); + + repository.delete(one); + + assertThat(repository.findAll()) // + .extracting(DummyEntity::getId) // + .containsExactlyInAnyOrder(two.getId(), three.getId()); + } + + @Test // DATAJDBC-111 + public void deleteByList() { + + DummyEntity one = repository.save(createDummyEntity()); + DummyEntity two = repository.save(createDummyEntity()); + DummyEntity three = repository.save(createDummyEntity()); + + repository.deleteAll(asList(one, three)); + + assertThat(repository.findAll()) // + .extracting(DummyEntity::getId) // + .containsExactlyInAnyOrder(two.getId()); + } + + @Test // DATAJDBC-111 + public void deleteAll() { + + repository.save(createDummyEntity()); + repository.save(createDummyEntity()); + repository.save(createDummyEntity()); + + assertThat(repository.findAll()).isNotEmpty(); + + repository.deleteAll(); + + assertThat(repository.findAll()).isEmpty(); + } + + private static DummyEntity createDummyEntity() { + DummyEntity entity = new DummyEntity(); + + entity.setPrefixedEmbeddable(new Embeddable(1L, "test1")); + + return entity; + } + + interface DummyEntityRepository extends CrudRepository {} + + @Data + static class DummyEntity { + + @Id Long id; + + @Embedded("prefix_") Embeddable prefixedEmbeddable; + } + + @Value + @Wither + private static class Embeddable { + Long attr1; + String attr2; + } +} diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedIntegrationTests.java new file mode 100644 index 00000000..28a7cf83 --- /dev/null +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedIntegrationTests.java @@ -0,0 +1,236 @@ +/* + * Copyright 2017-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.repository; + +import static java.util.Arrays.*; +import static org.assertj.core.api.Assertions.*; + +import lombok.Data; + +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.annotation.Id; +import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; +import org.springframework.data.jdbc.testing.TestConfiguration; +import org.springframework.data.relational.core.mapping.Embedded; +import org.springframework.data.repository.CrudRepository; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.rules.SpringClassRule; +import org.springframework.test.context.junit4.rules.SpringMethodRule; +import org.springframework.test.jdbc.JdbcTestUtils; +import org.springframework.transaction.annotation.Transactional; + +/** + * Very simple use cases for creation and usage of JdbcRepositories with {@link Embedded} annotation in Entities. + * + * @author Bastian Wilhelm + */ +@ContextConfiguration +@Transactional +public class JdbcRepositoryEmbeddedIntegrationTests { + + @Configuration + @Import(TestConfiguration.class) + static class Config { + + @Autowired JdbcRepositoryFactory factory; + + @Bean + Class testClass() { + return JdbcRepositoryEmbeddedIntegrationTests.class; + } + + @Bean + DummyEntityRepository dummyEntityRepository() { + return factory.getRepository(DummyEntityRepository.class); + } + + } + + @ClassRule public static final SpringClassRule classRule = new SpringClassRule(); + @Rule public SpringMethodRule methodRule = new SpringMethodRule(); + + @Autowired NamedParameterJdbcTemplate template; + @Autowired DummyEntityRepository repository; + + @Test // DATAJDBC-111 + public void savesAnEntity() { + + DummyEntity entity = repository.save(createDummyEntity()); + + assertThat(JdbcTestUtils.countRowsInTableWhere((JdbcTemplate) template.getJdbcOperations(), "dummy_entity", + "id = " + entity.getId())).isEqualTo(1); + } + + @Test // DATAJDBC-111 + public void saveAndLoadAnEntity() { + + DummyEntity entity = repository.save(createDummyEntity()); + + assertThat(repository.findById(entity.getId())).hasValueSatisfying(it -> { + assertThat(it.getId()).isEqualTo(entity.getId()); + assertThat(it.getPrefixedEmbeddable().getAttr1()).isEqualTo(entity.getPrefixedEmbeddable().getAttr1()); + assertThat(it.getPrefixedEmbeddable().getAttr2()).isEqualTo(entity.getPrefixedEmbeddable().getAttr2()); + assertThat(it.getEmbeddable().getAttr1()).isEqualTo(entity.getEmbeddable().getAttr1()); + assertThat(it.getEmbeddable().getAttr2()).isEqualTo(entity.getEmbeddable().getAttr2()); + }); + } + + @Test // DATAJDBC-111 + public void findAllFindsAllEntities() { + + DummyEntity entity = repository.save(createDummyEntity()); + DummyEntity other = repository.save(createDummyEntity()); + + Iterable all = repository.findAll(); + + assertThat(all)// + .extracting(DummyEntity::getId)// + .containsExactlyInAnyOrder(entity.getId(), other.getId()); + } + + @Test // DATAJDBC-111 + public void findByIdReturnsEmptyWhenNoneFound() { + + // NOT saving anything, so DB is empty + assertThat(repository.findById(-1L)).isEmpty(); + } + + @Test // DATAJDBC-111 + public void update() { + + DummyEntity entity = repository.save(createDummyEntity()); + + entity.getPrefixedEmbeddable().setAttr2("something else"); + DummyEntity saved = repository.save(entity); + + assertThat(repository.findById(entity.getId())).hasValueSatisfying(it -> { + assertThat(it.getPrefixedEmbeddable().getAttr2()).isEqualTo(saved.getPrefixedEmbeddable().getAttr2()); + }); + } + + @Test // DATAJDBC-111 + public void updateMany() { + + DummyEntity entity = repository.save(createDummyEntity()); + DummyEntity other = repository.save(createDummyEntity()); + + entity.getEmbeddable().setAttr2("something else"); + other.getEmbeddable().setAttr2("others Name"); + + repository.saveAll(asList(entity, other)); + + assertThat(repository.findAll()) // + .extracting(d -> d.getEmbeddable().getAttr2()) // + .containsExactlyInAnyOrder(entity.getEmbeddable().getAttr2(), other.getEmbeddable().getAttr2()); + } + + @Test // DATAJDBC-111 + public void deleteById() { + + DummyEntity one = repository.save(createDummyEntity()); + DummyEntity two = repository.save(createDummyEntity()); + DummyEntity three = repository.save(createDummyEntity()); + + repository.deleteById(two.getId()); + + assertThat(repository.findAll()) // + .extracting(DummyEntity::getId) // + .containsExactlyInAnyOrder(one.getId(), three.getId()); + } + + @Test // DATAJDBC-111 + public void deleteByEntity() { + DummyEntity one = repository.save(createDummyEntity()); + DummyEntity two = repository.save(createDummyEntity()); + DummyEntity three = repository.save(createDummyEntity()); + + repository.delete(one); + + assertThat(repository.findAll()) // + .extracting(DummyEntity::getId) // + .containsExactlyInAnyOrder(two.getId(), three.getId()); + } + + @Test // DATAJDBC-111 + public void deleteByList() { + + DummyEntity one = repository.save(createDummyEntity()); + DummyEntity two = repository.save(createDummyEntity()); + DummyEntity three = repository.save(createDummyEntity()); + + repository.deleteAll(asList(one, three)); + + assertThat(repository.findAll()) // + .extracting(DummyEntity::getId) // + .containsExactlyInAnyOrder(two.getId()); + } + + @Test // DATAJDBC-111 + public void deleteAll() { + + repository.save(createDummyEntity()); + repository.save(createDummyEntity()); + repository.save(createDummyEntity()); + + assertThat(repository.findAll()).isNotEmpty(); + + repository.deleteAll(); + + assertThat(repository.findAll()).isEmpty(); + } + + private static DummyEntity createDummyEntity() { + DummyEntity entity = new DummyEntity(); + + final Embeddable prefixedEmbeddable = new Embeddable(); + prefixedEmbeddable.setAttr1(1L); + prefixedEmbeddable.setAttr2("test1"); + entity.setPrefixedEmbeddable(prefixedEmbeddable); + + final Embeddable embeddable = new Embeddable(); + embeddable.setAttr1(2L); + embeddable.setAttr2("test2"); + entity.setEmbeddable(embeddable); + + return entity; + } + + interface DummyEntityRepository extends CrudRepository {} + + @Data + static class DummyEntity { + + @Id Long id; + + @Embedded("prefix_") Embeddable prefixedEmbeddable; + + @Embedded Embeddable embeddable; + } + + @Data + static class Embeddable { + Long attr1; + String attr2; + } +} diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests.java new file mode 100644 index 00000000..74c3100e --- /dev/null +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests.java @@ -0,0 +1,257 @@ +/* + * Copyright 2017-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.repository; + +import static java.util.Arrays.*; +import static org.assertj.core.api.Assertions.*; + +import lombok.Data; + +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.annotation.Id; +import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; +import org.springframework.data.jdbc.testing.TestConfiguration; +import org.springframework.data.relational.core.mapping.Column; +import org.springframework.data.relational.core.mapping.Embedded; +import org.springframework.data.repository.CrudRepository; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.rules.SpringClassRule; +import org.springframework.test.context.junit4.rules.SpringMethodRule; +import org.springframework.test.jdbc.JdbcTestUtils; +import org.springframework.transaction.annotation.Transactional; + +/** + * Very simple use cases for creation and usage of JdbcRepositories with test {@link Embedded} annotation in Entities. + * + * @author Bastian Wilhelm + */ +@ContextConfiguration +@Transactional +public class JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests { + + @Configuration + @Import(TestConfiguration.class) + static class Config { + + @Autowired JdbcRepositoryFactory factory; + + @Bean + Class testClass() { + return JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests.class; + } + + @Bean + DummyEntityRepository dummyEntityRepository() { + return factory.getRepository(DummyEntityRepository.class); + } + + } + + @ClassRule public static final SpringClassRule classRule = new SpringClassRule(); + @Rule public SpringMethodRule methodRule = new SpringMethodRule(); + + @Autowired NamedParameterJdbcTemplate template; + @Autowired DummyEntityRepository repository; + + @Test // DATAJDBC-111 + public void savesAnEntity() { + + DummyEntity entity = repository.save(createDummyEntity()); + + assertThat(JdbcTestUtils.countRowsInTableWhere((JdbcTemplate) template.getJdbcOperations(), "dummy_entity", + "id = " + entity.getId())).isEqualTo(1); + + assertThat(JdbcTestUtils.countRowsInTableWhere((JdbcTemplate) template.getJdbcOperations(), "dummy_entity2", + "id = " + entity.getId())).isEqualTo(1); + } + + @Test // DATAJDBC-111 + public void saveAndLoadAnEntity() { + + DummyEntity entity = repository.save(createDummyEntity()); + + assertThat(repository.findById(entity.getId())).hasValueSatisfying(it -> { + assertThat(it.getId()).isEqualTo(entity.getId()); + assertThat(it.getDummyEntity2().getTest()).isEqualTo(entity.getDummyEntity2().getTest()); + assertThat(it.getDummyEntity2().getEmbeddable().getAttr()).isEqualTo(entity.getDummyEntity2().getEmbeddable().getAttr()); + }); + } + + @Test // DATAJDBC-111 + public void findAllFindsAllEntities() { + + DummyEntity entity = repository.save(createDummyEntity()); + DummyEntity other = repository.save(createDummyEntity()); + + Iterable all = repository.findAll(); + + assertThat(all)// + .extracting(DummyEntity::getId)// + .containsExactlyInAnyOrder(entity.getId(), other.getId()); + } + + @Test // DATAJDBC-111 + public void findByIdReturnsEmptyWhenNoneFound() { + + // NOT saving anything, so DB is empty + assertThat(repository.findById(-1L)).isEmpty(); + } + + @Test // DATAJDBC-111 + public void update() { + + DummyEntity entity = repository.save(createDummyEntity()); + + entity.getDummyEntity2().setTest("something else"); + entity.getDummyEntity2().getEmbeddable().setAttr(3L); + DummyEntity saved = repository.save(entity); + + assertThat(repository.findById(entity.getId())).hasValueSatisfying(it -> { + assertThat(it.getDummyEntity2().getTest()).isEqualTo(saved.getDummyEntity2().getTest()); + assertThat(it.getDummyEntity2().getEmbeddable().getAttr()).isEqualTo(saved.getDummyEntity2().getEmbeddable().getAttr()); + }); + } + + @Test // DATAJDBC-111 + public void updateMany() { + + DummyEntity entity = repository.save(createDummyEntity()); + DummyEntity other = repository.save(createDummyEntity()); + + entity.getDummyEntity2().setTest("something else"); + other.getDummyEntity2().setTest("others Name"); + + entity.getDummyEntity2().getEmbeddable().setAttr(3L); + other.getDummyEntity2().getEmbeddable().setAttr(5L); + + repository.saveAll(asList(entity, other)); + + assertThat(repository.findAll()) // + .extracting(d -> d.getDummyEntity2().getTest()) // + .containsExactlyInAnyOrder(entity.getDummyEntity2().getTest(), other.getDummyEntity2().getTest()); + + assertThat(repository.findAll()) // + .extracting(d -> d.getDummyEntity2().getEmbeddable().getAttr()) // + .containsExactlyInAnyOrder(entity.getDummyEntity2().getEmbeddable().getAttr(), other.getDummyEntity2().getEmbeddable().getAttr()); + } + + @Test // DATAJDBC-111 + public void deleteById() { + + DummyEntity one = repository.save(createDummyEntity()); + DummyEntity two = repository.save(createDummyEntity()); + DummyEntity three = repository.save(createDummyEntity()); + + repository.deleteById(two.getId()); + + assertThat(repository.findAll()) // + .extracting(DummyEntity::getId) // + .containsExactlyInAnyOrder(one.getId(), three.getId()); + } + + @Test // DATAJDBC-111 + public void deleteByEntity() { + DummyEntity one = repository.save(createDummyEntity()); + DummyEntity two = repository.save(createDummyEntity()); + DummyEntity three = repository.save(createDummyEntity()); + + repository.delete(one); + + assertThat(repository.findAll()) // + .extracting(DummyEntity::getId) // + .containsExactlyInAnyOrder(two.getId(), three.getId()); + } + + @Test // DATAJDBC-111 + public void deleteByList() { + + DummyEntity one = repository.save(createDummyEntity()); + DummyEntity two = repository.save(createDummyEntity()); + DummyEntity three = repository.save(createDummyEntity()); + + repository.deleteAll(asList(one, three)); + + assertThat(repository.findAll()) // + .extracting(DummyEntity::getId) // + .containsExactlyInAnyOrder(two.getId()); + } + + @Test // DATAJDBC-111 + public void deleteAll() { + + repository.save(createDummyEntity()); + repository.save(createDummyEntity()); + repository.save(createDummyEntity()); + + assertThat(repository.findAll()).isNotEmpty(); + + repository.deleteAll(); + + assertThat(repository.findAll()).isEmpty(); + } + + private static DummyEntity createDummyEntity() { + DummyEntity entity = new DummyEntity(); + + entity.setTest("rootTest"); + + final DummyEntity2 dummyEntity2 = new DummyEntity2(); + dummyEntity2.setTest("c1"); + + final Embeddable embeddable = new Embeddable(); + embeddable.setAttr(1L); + dummyEntity2.setEmbeddable(embeddable); + + entity.setDummyEntity2(dummyEntity2); + + return entity; + } + + interface DummyEntityRepository extends CrudRepository {} + + @Data + static class DummyEntity { + @Id Long id; + + String test; + + @Column("id") + DummyEntity2 dummyEntity2; + } + + @Data + static class DummyEntity2 { + @Id Long id; + + String test; + + @Embedded("prefix_") + Embeddable embeddable; + } + + @Data + static class Embeddable { + Long attr; + } +} diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedWithCollectionIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedWithCollectionIntegrationTests.java new file mode 100644 index 00000000..3556c70f --- /dev/null +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedWithCollectionIntegrationTests.java @@ -0,0 +1,267 @@ +/* + * Copyright 2017-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.repository; + +import static java.util.Arrays.*; +import static org.assertj.core.api.Assertions.*; + +import lombok.Data; + +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.annotation.Id; +import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; +import org.springframework.data.jdbc.testing.TestConfiguration; +import org.springframework.data.relational.core.mapping.Column; +import org.springframework.data.relational.core.mapping.Embedded; +import org.springframework.data.repository.CrudRepository; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.rules.SpringClassRule; +import org.springframework.test.context.junit4.rules.SpringMethodRule; +import org.springframework.test.jdbc.JdbcTestUtils; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; + +/** + * Very simple use cases for creation and usage of JdbcRepositories with test {@link Embedded} annotation in Entities. + * + * @author Bastian Wilhelm + */ +@ContextConfiguration +@Transactional +public class JdbcRepositoryEmbeddedWithCollectionIntegrationTests { + + @Configuration + @Import(TestConfiguration.class) + static class Config { + + @Autowired JdbcRepositoryFactory factory; + + @Bean + Class testClass() { + return JdbcRepositoryEmbeddedWithCollectionIntegrationTests.class; + } + + @Bean + DummyEntityRepository dummyEntityRepository() { + return factory.getRepository(DummyEntityRepository.class); + } + + } + + @ClassRule public static final SpringClassRule classRule = new SpringClassRule(); + @Rule public SpringMethodRule methodRule = new SpringMethodRule(); + + @Autowired NamedParameterJdbcTemplate template; + @Autowired DummyEntityRepository repository; + + @Test // DATAJDBC-111 + public void savesAnEntity() { + + DummyEntity entity = repository.save(createDummyEntity()); + + assertThat(JdbcTestUtils.countRowsInTableWhere((JdbcTemplate) template.getJdbcOperations(), "dummy_entity", + "id = " + entity.getId())).isEqualTo(1); + + assertThat(JdbcTestUtils.countRowsInTableWhere((JdbcTemplate) template.getJdbcOperations(), "dummy_entity2", + "id = " + entity.getId())).isEqualTo(2); + } + + @Test // DATAJDBC-111 + public void saveAndLoadAnEntity() { + + DummyEntity entity = repository.save(createDummyEntity()); + + assertThat(repository.findById(entity.getId())).hasValueSatisfying(it -> { + assertThat(it.getId()).isEqualTo(entity.getId()); + assertThat(it.getEmbeddable().getTest()).isEqualTo(entity.getEmbeddable().getTest()); + assertThat(it.getEmbeddable().getList().size()).isEqualTo(entity.getEmbeddable().getList().size()); + assertThat(it.getEmbeddable().getList().get(0).getTest()).isEqualTo(entity.getEmbeddable().getList().get(0).getTest()); + assertThat(it.getEmbeddable().getList().get(1).getTest()).isEqualTo(entity.getEmbeddable().getList().get(1).getTest()); + }); + } + + @Test // DATAJDBC-111 + public void findAllFindsAllEntities() { + + DummyEntity entity = repository.save(createDummyEntity()); + DummyEntity other = repository.save(createDummyEntity()); + + Iterable all = repository.findAll(); + + assertThat(all)// + .extracting(DummyEntity::getId)// + .containsExactlyInAnyOrder(entity.getId(), other.getId()); + } + + @Test // DATAJDBC-111 + public void findByIdReturnsEmptyWhenNoneFound() { + + // NOT saving anything, so DB is empty + assertThat(repository.findById(-1L)).isEmpty(); + } + + @Test // DATAJDBC-111 + public void update() { + + DummyEntity entity = repository.save(createDummyEntity()); + + entity.getEmbeddable().setTest("something else"); + entity.getEmbeddable().getList().get(0).setTest("another"); + DummyEntity saved = repository.save(entity); + + assertThat(repository.findById(entity.getId())).hasValueSatisfying(it -> { + assertThat(it.getId()).isEqualTo(saved.getId()); + assertThat(it.getEmbeddable().getTest()).isEqualTo(saved.getEmbeddable().getTest()); + assertThat(it.getEmbeddable().getList().size()).isEqualTo(saved.getEmbeddable().getList().size()); + assertThat(it.getEmbeddable().getList().get(0).getTest()).isEqualTo(saved.getEmbeddable().getList().get(0).getTest()); + assertThat(it.getEmbeddable().getList().get(1).getTest()).isEqualTo(saved.getEmbeddable().getList().get(1).getTest()); + }); + } + + @Test // DATAJDBC-111 + public void updateMany() { + + DummyEntity entity = repository.save(createDummyEntity()); + DummyEntity other = repository.save(createDummyEntity()); + + entity.getEmbeddable().setTest("something else"); + other.getEmbeddable().setTest("others Name"); + + entity.getEmbeddable().getList().get(0).setTest("else"); + other.getEmbeddable().getList().get(0).setTest("Name"); + + repository.saveAll(asList(entity, other)); + + assertThat(repository.findAll()) // + .extracting(d -> d.getEmbeddable().getTest()) // + .containsExactlyInAnyOrder(entity.getEmbeddable().getTest(), other.getEmbeddable().getTest()); + + assertThat(repository.findAll()) // + .extracting(d -> d.getEmbeddable().getList().get(0).getTest()) // + .containsExactlyInAnyOrder(entity.getEmbeddable().getList().get(0).getTest(), other.getEmbeddable().getList().get(0).getTest()); + } + + @Test // DATAJDBC-111 + public void deleteById() { + + DummyEntity one = repository.save(createDummyEntity()); + DummyEntity two = repository.save(createDummyEntity()); + DummyEntity three = repository.save(createDummyEntity()); + + repository.deleteById(two.getId()); + + assertThat(repository.findAll()) // + .extracting(DummyEntity::getId) // + .containsExactlyInAnyOrder(one.getId(), three.getId()); + } + + @Test // DATAJDBC-111 + public void deleteByEntity() { + DummyEntity one = repository.save(createDummyEntity()); + DummyEntity two = repository.save(createDummyEntity()); + DummyEntity three = repository.save(createDummyEntity()); + + repository.delete(one); + + assertThat(repository.findAll()) // + .extracting(DummyEntity::getId) // + .containsExactlyInAnyOrder(two.getId(), three.getId()); + } + + @Test // DATAJDBC-111 + public void deleteByList() { + + DummyEntity one = repository.save(createDummyEntity()); + DummyEntity two = repository.save(createDummyEntity()); + DummyEntity three = repository.save(createDummyEntity()); + + repository.deleteAll(asList(one, three)); + + assertThat(repository.findAll()) // + .extracting(DummyEntity::getId) // + .containsExactlyInAnyOrder(two.getId()); + } + + @Test // DATAJDBC-111 + public void deleteAll() { + + repository.save(createDummyEntity()); + repository.save(createDummyEntity()); + repository.save(createDummyEntity()); + + assertThat(repository.findAll()).isNotEmpty(); + + repository.deleteAll(); + + assertThat(repository.findAll()).isEmpty(); + } + + private static DummyEntity createDummyEntity() { + DummyEntity entity = new DummyEntity(); + entity.setTest("root"); + + final Embeddable embeddable = new Embeddable(); + embeddable.setTest("embedded"); + + final DummyEntity2 dummyEntity21 = new DummyEntity2(); + dummyEntity21.setTest("entity1"); + + final DummyEntity2 dummyEntity22 = new DummyEntity2(); + dummyEntity22.setTest("entity2"); + + embeddable.getList().add(dummyEntity21); + embeddable.getList().add(dummyEntity22); + + entity.setEmbeddable(embeddable); + + return entity; + } + + interface DummyEntityRepository extends CrudRepository {} + + @Data + private static class DummyEntity { + @Id Long id; + + String test; + + @Embedded("prefix_") + Embeddable embeddable; + } + + @Data + private static class Embeddable { + @Column(value = "id", keyColumn = "order_key") + List list = new ArrayList<>(); + + String test; + } + + @Data + private static class DummyEntity2 { + String test; + } +} diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests.java new file mode 100644 index 00000000..a2ca6275 --- /dev/null +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests.java @@ -0,0 +1,258 @@ +/* + * Copyright 2017-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.repository; + +import static java.util.Arrays.*; +import static org.assertj.core.api.Assertions.*; + +import lombok.Data; + +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.annotation.Id; +import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; +import org.springframework.data.jdbc.testing.TestConfiguration; +import org.springframework.data.relational.core.mapping.Column; +import org.springframework.data.relational.core.mapping.Embedded; +import org.springframework.data.repository.CrudRepository; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.rules.SpringClassRule; +import org.springframework.test.context.junit4.rules.SpringMethodRule; +import org.springframework.test.jdbc.JdbcTestUtils; +import org.springframework.transaction.annotation.Transactional; + +/** + * Very simple use cases for creation and usage of JdbcRepositories with test {@link Embedded} annotation in Entities. + * + * @author Bastian Wilhelm + */ +@ContextConfiguration +@Transactional +public class JdbcRepositoryEmbeddedWithReferenceIntegrationTests { + + @Configuration + @Import(TestConfiguration.class) + static class Config { + + @Autowired JdbcRepositoryFactory factory; + + @Bean + Class testClass() { + return JdbcRepositoryEmbeddedWithReferenceIntegrationTests.class; + } + + @Bean + DummyEntityRepository dummyEntityRepository() { + return factory.getRepository(DummyEntityRepository.class); + } + + } + + @ClassRule public static final SpringClassRule classRule = new SpringClassRule(); + @Rule public SpringMethodRule methodRule = new SpringMethodRule(); + + @Autowired NamedParameterJdbcTemplate template; + @Autowired DummyEntityRepository repository; + + @Test // DATAJDBC-111 + public void savesAnEntity() { + + DummyEntity entity = repository.save(createDummyEntity()); + + assertThat(JdbcTestUtils.countRowsInTableWhere((JdbcTemplate) template.getJdbcOperations(), "dummy_entity", + "id = " + entity.getId())).isEqualTo(1); + + assertThat(JdbcTestUtils.countRowsInTableWhere((JdbcTemplate) template.getJdbcOperations(), "dummy_entity2", + "id = " + entity.getId())).isEqualTo(1); + } + + @Test // DATAJDBC-111 + public void saveAndLoadAnEntity() { + + DummyEntity entity = repository.save(createDummyEntity()); + + assertThat(repository.findById(entity.getId())).hasValueSatisfying(it -> { + assertThat(it.getId()).isEqualTo(entity.getId()); + assertThat(it.getEmbeddable().getTest()).isEqualTo(entity.getEmbeddable().getTest()); + assertThat(it.getEmbeddable().getDummyEntity2().getTest()).isEqualTo(entity.getEmbeddable().getDummyEntity2().getTest()); + }); + } + + @Test // DATAJDBC-111 + public void findAllFindsAllEntities() { + + DummyEntity entity = repository.save(createDummyEntity()); + DummyEntity other = repository.save(createDummyEntity()); + + Iterable all = repository.findAll(); + + assertThat(all)// + .extracting(DummyEntity::getId)// + .containsExactlyInAnyOrder(entity.getId(), other.getId()); + } + + @Test // DATAJDBC-111 + public void findByIdReturnsEmptyWhenNoneFound() { + + // NOT saving anything, so DB is empty + assertThat(repository.findById(-1L)).isEmpty(); + } + + @Test // DATAJDBC-111 + public void update() { + + DummyEntity entity = repository.save(createDummyEntity()); + + entity.getEmbeddable().setTest("something else"); + entity.getEmbeddable().getDummyEntity2().setTest("another"); + DummyEntity saved = repository.save(entity); + + assertThat(repository.findById(entity.getId())).hasValueSatisfying(it -> { + assertThat(it.getEmbeddable().getTest()).isEqualTo(saved.getEmbeddable().getTest()); + assertThat(it.getEmbeddable().getDummyEntity2().getTest()).isEqualTo(saved.getEmbeddable().getDummyEntity2().getTest()); + }); + } + + @Test // DATAJDBC-111 + public void updateMany() { + + DummyEntity entity = repository.save(createDummyEntity()); + DummyEntity other = repository.save(createDummyEntity()); + + entity.getEmbeddable().setTest("something else"); + other.getEmbeddable().setTest("others Name"); + + entity.getEmbeddable().getDummyEntity2().setTest("else"); + other.getEmbeddable().getDummyEntity2().setTest("Name"); + + repository.saveAll(asList(entity, other)); + + assertThat(repository.findAll()) // + .extracting(d -> d.getEmbeddable().getTest()) // + .containsExactlyInAnyOrder(entity.getEmbeddable().getTest(), other.getEmbeddable().getTest()); + + assertThat(repository.findAll()) // + .extracting(d -> d.getEmbeddable().getDummyEntity2().getTest()) // + .containsExactlyInAnyOrder(entity.getEmbeddable().getDummyEntity2().getTest(), other.getEmbeddable().getDummyEntity2().getTest()); + } + + @Test // DATAJDBC-111 + public void deleteById() { + + DummyEntity one = repository.save(createDummyEntity()); + DummyEntity two = repository.save(createDummyEntity()); + DummyEntity three = repository.save(createDummyEntity()); + + repository.deleteById(two.getId()); + + assertThat(repository.findAll()) // + .extracting(DummyEntity::getId) // + .containsExactlyInAnyOrder(one.getId(), three.getId()); + } + + @Test // DATAJDBC-111 + public void deleteByEntity() { + DummyEntity one = repository.save(createDummyEntity()); + DummyEntity two = repository.save(createDummyEntity()); + DummyEntity three = repository.save(createDummyEntity()); + + repository.delete(one); + + assertThat(repository.findAll()) // + .extracting(DummyEntity::getId) // + .containsExactlyInAnyOrder(two.getId(), three.getId()); + } + + @Test // DATAJDBC-111 + public void deleteByList() { + + DummyEntity one = repository.save(createDummyEntity()); + DummyEntity two = repository.save(createDummyEntity()); + DummyEntity three = repository.save(createDummyEntity()); + + repository.deleteAll(asList(one, three)); + + assertThat(repository.findAll()) // + .extracting(DummyEntity::getId) // + .containsExactlyInAnyOrder(two.getId()); + } + + @Test // DATAJDBC-111 + public void deleteAll() { + + repository.save(createDummyEntity()); + repository.save(createDummyEntity()); + repository.save(createDummyEntity()); + + assertThat(repository.findAll()).isNotEmpty(); + + repository.deleteAll(); + + assertThat(repository.findAll()).isEmpty(); + } + + private static DummyEntity createDummyEntity() { + DummyEntity entity = new DummyEntity(); + entity.setTest("root"); + + final Embeddable embeddable = new Embeddable(); + embeddable.setTest("embedded"); + + final DummyEntity2 dummyEntity2 = new DummyEntity2(); + dummyEntity2.setTest("entity"); + + embeddable.setDummyEntity2(dummyEntity2); + + entity.setEmbeddable(embeddable); + + return entity; + } + + interface DummyEntityRepository extends CrudRepository {} + + @Data + private static class DummyEntity { + @Id Long id; + + String test; + + @Embedded("prefix_") + Embeddable embeddable; + } + + @Data + private static class Embeddable { + @Column("id") + DummyEntity2 dummyEntity2; + + String test; + } + + @Data + private static class DummyEntity2 { + + @Id Long id; + + String test; + } +} diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/JdbcRepositoryConfigExtensionUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/JdbcRepositoryConfigExtensionUnitTests.java index 4d6ce575..43fec775 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/JdbcRepositoryConfigExtensionUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/JdbcRepositoryConfigExtensionUnitTests.java @@ -115,8 +115,6 @@ public class JdbcRepositoryConfigExtensionUnitTests { assertThat(jdbcOperations) // .isInstanceOf(RuntimeBeanReference.class) // .extracting(rbr -> ((RuntimeBeanReference) rbr).getBeanName()).contains("two"); - - System.out.println(jdbcOperations); } @Test // DATAJDBC-293 diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedCascadingIntegrationTests-hsql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedCascadingIntegrationTests-hsql.sql new file mode 100644 index 00000000..b6619706 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedCascadingIntegrationTests-hsql.sql @@ -0,0 +1 @@ +CREATE TABLE dummy_entity ( id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, TEST VARCHAR(100), PREFIX2_ATTR BIGINT, PREFIX_TEST VARCHAR(100), PREFIX_PREFIX2_ATTR BIGINT) diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedCascadingIntegrationTests-mariadb.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedCascadingIntegrationTests-mariadb.sql new file mode 100644 index 00000000..2faa643a --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedCascadingIntegrationTests-mariadb.sql @@ -0,0 +1 @@ +CREATE TABLE dummy_entity (id BIGINT AUTO_INCREMENT PRIMARY KEY, TEST VARCHAR(100), PREFIX2_ATTR BIGINT, PREFIX_TEST VARCHAR(100), PREFIX_PREFIX2_ATTR BIGINT); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedCascadingIntegrationTests-mssql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedCascadingIntegrationTests-mssql.sql new file mode 100644 index 00000000..2832a7ac --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedCascadingIntegrationTests-mssql.sql @@ -0,0 +1,2 @@ +DROP TABLE IF EXISTS dummy_entity; +CREATE TABLE dummy_entity (id BIGINT IDENTITY PRIMARY KEY, TEST VARCHAR(100), PREFIX2_ATTR BIGINT, PREFIX_TEST VARCHAR(100), PREFIX_PREFIX2_ATTR BIGINT); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedCascadingIntegrationTests-mysql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedCascadingIntegrationTests-mysql.sql new file mode 100644 index 00000000..2faa643a --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedCascadingIntegrationTests-mysql.sql @@ -0,0 +1 @@ +CREATE TABLE dummy_entity (id BIGINT AUTO_INCREMENT PRIMARY KEY, TEST VARCHAR(100), PREFIX2_ATTR BIGINT, PREFIX_TEST VARCHAR(100), PREFIX_PREFIX2_ATTR BIGINT); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedCascadingIntegrationTests-postgres.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedCascadingIntegrationTests-postgres.sql new file mode 100644 index 00000000..a5d589d4 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedCascadingIntegrationTests-postgres.sql @@ -0,0 +1,2 @@ +DROP TABLE dummy_entity; +CREATE TABLE dummy_entity (id SERIAL PRIMARY KEY, TEST VARCHAR(100), PREFIX2_ATTR BIGINT, PREFIX_TEST VARCHAR(100), PREFIX_PREFIX2_ATTR BIGINT); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedImmutableIntegrationTests-hsql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedImmutableIntegrationTests-hsql.sql new file mode 100644 index 00000000..1000cc55 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedImmutableIntegrationTests-hsql.sql @@ -0,0 +1 @@ +CREATE TABLE dummy_entity ( id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, PREFIX_ATTR1 BIGINT, PREFIX_ATTR2 VARCHAR(100)) diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedImmutableIntegrationTests-mariadb.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedImmutableIntegrationTests-mariadb.sql new file mode 100644 index 00000000..c3519e1a --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedImmutableIntegrationTests-mariadb.sql @@ -0,0 +1 @@ +CREATE TABLE dummy_entity (id BIGINT AUTO_INCREMENT PRIMARY KEY, PREFIX_ATTR1 BIGINT, PREFIX_ATTR2 VARCHAR(100)); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedImmutableIntegrationTests-mssql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedImmutableIntegrationTests-mssql.sql new file mode 100644 index 00000000..cdc0c880 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedImmutableIntegrationTests-mssql.sql @@ -0,0 +1,2 @@ +DROP TABLE IF EXISTS dummy_entity; +CREATE TABLE dummy_entity (id BIGINT IDENTITY PRIMARY KEY, PREFIX_ATTR1 BIGINT, PREFIX_ATTR2 VARCHAR(100)); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedImmutableIntegrationTests-mysql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedImmutableIntegrationTests-mysql.sql new file mode 100644 index 00000000..c3519e1a --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedImmutableIntegrationTests-mysql.sql @@ -0,0 +1 @@ +CREATE TABLE dummy_entity (id BIGINT AUTO_INCREMENT PRIMARY KEY, PREFIX_ATTR1 BIGINT, PREFIX_ATTR2 VARCHAR(100)); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedImmutableIntegrationTests-postgres.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedImmutableIntegrationTests-postgres.sql new file mode 100644 index 00000000..dc84d871 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedImmutableIntegrationTests-postgres.sql @@ -0,0 +1,2 @@ +DROP TABLE dummy_entity; +CREATE TABLE dummy_entity (id SERIAL PRIMARY KEY, PREFIX_ATTR1 BIGINT, PREFIX_ATTR2 VARCHAR(100)); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-hsql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-hsql.sql new file mode 100644 index 00000000..35cb6a1f --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-hsql.sql @@ -0,0 +1 @@ +CREATE TABLE dummy_entity ( id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, ATTR1 BIGINT, ATTR2 VARCHAR(100), PREFIX_ATTR1 BIGINT, PREFIX_ATTR2 VARCHAR(100)) diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-mariadb.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-mariadb.sql new file mode 100644 index 00000000..925f601b --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-mariadb.sql @@ -0,0 +1 @@ +CREATE TABLE dummy_entity (id BIGINT AUTO_INCREMENT PRIMARY KEY, ATTR1 BIGINT, ATTR2 VARCHAR(100), PREFIX_ATTR1 BIGINT, PREFIX_ATTR2 VARCHAR(100)); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-mssql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-mssql.sql new file mode 100644 index 00000000..2eb4f770 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-mssql.sql @@ -0,0 +1,2 @@ +DROP TABLE IF EXISTS dummy_entity; +CREATE TABLE dummy_entity (id BIGINT IDENTITY PRIMARY KEY, ATTR1 BIGINT, ATTR2 VARCHAR(100), PREFIX_ATTR1 BIGINT, PREFIX_ATTR2 VARCHAR(100)); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-mysql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-mysql.sql new file mode 100644 index 00000000..925f601b --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-mysql.sql @@ -0,0 +1 @@ +CREATE TABLE dummy_entity (id BIGINT AUTO_INCREMENT PRIMARY KEY, ATTR1 BIGINT, ATTR2 VARCHAR(100), PREFIX_ATTR1 BIGINT, PREFIX_ATTR2 VARCHAR(100)); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-postgres.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-postgres.sql new file mode 100644 index 00000000..3b298205 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-postgres.sql @@ -0,0 +1,2 @@ +DROP TABLE dummy_entity; +CREATE TABLE dummy_entity (id SERIAL PRIMARY KEY, ATTR1 BIGINT, ATTR2 VARCHAR(100), PREFIX_ATTR1 BIGINT, PREFIX_ATTR2 VARCHAR(100)); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests-hsql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests-hsql.sql new file mode 100644 index 00000000..60af8e60 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests-hsql.sql @@ -0,0 +1,2 @@ +CREATE TABLE dummy_entity ( id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, TEST VARCHAR(100)) +CREATE TABLE dummy_entity2 ( id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, TEST VARCHAR(100), PREFIX_ATTR BIGINT) diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests-mariadb.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests-mariadb.sql new file mode 100644 index 00000000..c9a201e6 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests-mariadb.sql @@ -0,0 +1,2 @@ +CREATE TABLE dummy_entity (id BIGINT AUTO_INCREMENT PRIMARY KEY, TEST VARCHAR(100)); +CREATE TABLE dummy_entity2 (id BIGINT AUTO_INCREMENT PRIMARY KEY, TEST VARCHAR(100), PREFIX_ATTR BIGINT); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests-mssql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests-mssql.sql new file mode 100644 index 00000000..054f0d51 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests-mssql.sql @@ -0,0 +1,4 @@ +DROP TABLE IF EXISTS dummy_entity; +CREATE TABLE dummy_entity (id BIGINT IDENTITY PRIMARY KEY, TEST VARCHAR(100)); +DROP TABLE IF EXISTS dummy_entity2; +CREATE TABLE dummy_entity2 (id BIGINT PRIMARY KEY, TEST VARCHAR(100), PREFIX_ATTR BIGINT); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests-mysql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests-mysql.sql new file mode 100644 index 00000000..c9a201e6 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests-mysql.sql @@ -0,0 +1,2 @@ +CREATE TABLE dummy_entity (id BIGINT AUTO_INCREMENT PRIMARY KEY, TEST VARCHAR(100)); +CREATE TABLE dummy_entity2 (id BIGINT AUTO_INCREMENT PRIMARY KEY, TEST VARCHAR(100), PREFIX_ATTR BIGINT); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests-postgres.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests-postgres.sql new file mode 100644 index 00000000..13d7c6a1 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests-postgres.sql @@ -0,0 +1,4 @@ +DROP TABLE dummy_entity; +CREATE TABLE dummy_entity (id SERIAL PRIMARY KEY, TEST VARCHAR(100)); +DROP TABLE dummy_entity2; +CREATE TABLE dummy_entity2 (id SERIAL PRIMARY KEY, TEST VARCHAR(100), PREFIX_ATTR BIGINT); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithCollectionIntegrationTests-hsql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithCollectionIntegrationTests-hsql.sql new file mode 100644 index 00000000..92804820 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithCollectionIntegrationTests-hsql.sql @@ -0,0 +1,2 @@ +CREATE TABLE dummy_entity ( id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, TEST VARCHAR(100), PREFIX_TEST VARCHAR(100)); +CREATE TABLE dummy_entity2 ( id BIGINT, ORDER_KEY BIGINT, TEST VARCHAR(100), PRIMARY KEY(id, ORDER_KEY)) diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithCollectionIntegrationTests-mariadb.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithCollectionIntegrationTests-mariadb.sql new file mode 100644 index 00000000..d4fc5f8f --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithCollectionIntegrationTests-mariadb.sql @@ -0,0 +1,2 @@ +CREATE TABLE dummy_entity (id BIGINT AUTO_INCREMENT PRIMARY KEY, TEST VARCHAR(100), PREFIX_TEST VARCHAR(100)); +CREATE TABLE dummy_entity2 (id BIGINT, ORDER_KEY BIGINT, TEST VARCHAR(100), PRIMARY KEY(id, ORDER_KEY)); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithCollectionIntegrationTests-mssql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithCollectionIntegrationTests-mssql.sql new file mode 100644 index 00000000..327e50be --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithCollectionIntegrationTests-mssql.sql @@ -0,0 +1,4 @@ +DROP TABLE IF EXISTS dummy_entity; +CREATE TABLE dummy_entity (id BIGINT IDENTITY PRIMARY KEY, TEST VARCHAR(100), PREFIX_TEST VARCHAR(100)); +DROP TABLE IF EXISTS dummy_entity2; +CREATE TABLE dummy_entity2 (id BIGINT, ORDER_KEY BIGINT, TEST VARCHAR(100), CONSTRAINT dummym_entity2_pk PRIMARY KEY(id, ORDER_KEY)); \ No newline at end of file diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithCollectionIntegrationTests-mysql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithCollectionIntegrationTests-mysql.sql new file mode 100644 index 00000000..d4fc5f8f --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithCollectionIntegrationTests-mysql.sql @@ -0,0 +1,2 @@ +CREATE TABLE dummy_entity (id BIGINT AUTO_INCREMENT PRIMARY KEY, TEST VARCHAR(100), PREFIX_TEST VARCHAR(100)); +CREATE TABLE dummy_entity2 (id BIGINT, ORDER_KEY BIGINT, TEST VARCHAR(100), PRIMARY KEY(id, ORDER_KEY)); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithCollectionIntegrationTests-postgres.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithCollectionIntegrationTests-postgres.sql new file mode 100644 index 00000000..fa9cff08 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithCollectionIntegrationTests-postgres.sql @@ -0,0 +1,4 @@ +DROP TABLE dummy_entity; +CREATE TABLE dummy_entity (id SERIAL PRIMARY KEY, TEST VARCHAR(100), PREFIX_TEST VARCHAR(100)); +DROP TABLE dummy_entity2; +CREATE TABLE dummy_entity2 (id BIGINT, ORDER_KEY BIGINT, TEST VARCHAR(100), PRIMARY KEY (id, ORDER_KEY)); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests-hsql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests-hsql.sql new file mode 100644 index 00000000..14a9e03e --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests-hsql.sql @@ -0,0 +1,2 @@ +CREATE TABLE dummy_entity ( id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, TEST VARCHAR(100), PREFIX_TEST VARCHAR(100)); +CREATE TABLE dummy_entity2 ( id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, TEST VARCHAR(100)) diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests-mariadb.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests-mariadb.sql new file mode 100644 index 00000000..dd4f082b --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests-mariadb.sql @@ -0,0 +1,2 @@ +CREATE TABLE dummy_entity (id BIGINT AUTO_INCREMENT PRIMARY KEY, TEST VARCHAR(100), PREFIX_TEST VARCHAR(100)); +CREATE TABLE dummy_entity2 (id BIGINT AUTO_INCREMENT PRIMARY KEY, TEST VARCHAR(100)); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests-mssql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests-mssql.sql new file mode 100644 index 00000000..a437ee14 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests-mssql.sql @@ -0,0 +1,4 @@ +DROP TABLE IF EXISTS dummy_entity; +CREATE TABLE dummy_entity (id BIGINT IDENTITY PRIMARY KEY, TEST VARCHAR(100), PREFIX_TEST VARCHAR(100)); +DROP TABLE IF EXISTS dummy_entity2; +CREATE TABLE dummy_entity2 (id BIGINT PRIMARY KEY, TEST VARCHAR(100)); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests-mysql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests-mysql.sql new file mode 100644 index 00000000..dd4f082b --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests-mysql.sql @@ -0,0 +1,2 @@ +CREATE TABLE dummy_entity (id BIGINT AUTO_INCREMENT PRIMARY KEY, TEST VARCHAR(100), PREFIX_TEST VARCHAR(100)); +CREATE TABLE dummy_entity2 (id BIGINT AUTO_INCREMENT PRIMARY KEY, TEST VARCHAR(100)); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests-postgres.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests-postgres.sql new file mode 100644 index 00000000..c4c8cef0 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests-postgres.sql @@ -0,0 +1,4 @@ +DROP TABLE dummy_entity; +CREATE TABLE dummy_entity (id SERIAL PRIMARY KEY, TEST VARCHAR(100), PREFIX_TEST VARCHAR(100)); +DROP TABLE dummy_entity2; +CREATE TABLE dummy_entity2 (id SERIAL PRIMARY KEY, TEST VARCHAR(100)); diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityDeleteWriter.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityDeleteWriter.java index f164a769..8d4041c4 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityDeleteWriter.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityDeleteWriter.java @@ -31,6 +31,7 @@ import org.springframework.util.Assert; * * @author Jens Schauder * @author Mark Paluch + * @author Bastian Wilhelm */ public class RelationalEntityDeleteWriter implements EntityWriter> { @@ -66,6 +67,7 @@ public class RelationalEntityDeleteWriter implements EntityWriter> actions = new ArrayList<>(); context.findPersistentPropertyPaths(entityType, PersistentProperty::isEntity) + .filter(p -> !p.getRequiredLeafProperty().isEmbedded()) .forEach(p -> actions.add(new DbAction.DeleteAll<>(p))); Collections.reverse(actions); @@ -95,6 +97,7 @@ public class RelationalEntityDeleteWriter implements EntityWriter> actions = new ArrayList<>(); context.findPersistentPropertyPaths(aggregateChange.getEntityType(), PersistentProperty::isEntity) + .filter(p -> !p.getRequiredLeafProperty().isEmbedded()) .forEach(p -> actions.add(new DbAction.Delete<>(id, p))); Collections.reverse(actions); 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 f57db61b..f33f0da5 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 @@ -15,7 +15,6 @@ */ package org.springframework.data.relational.core.conversion; -import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.mapping.PersistentPropertyPaths; import org.springframework.data.relational.core.mapping.RelationalMappingContext; @@ -30,11 +29,13 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Stack; /** * Holds context information for the current save operation. * * @author Jens Schauder + * @author Bastian Wilhelm */ class WritingContext { @@ -52,7 +53,7 @@ class WritingContext { this.root = root; this.entity = aggregateChange.getEntity(); this.entityType = aggregateChange.getEntityType(); - this.paths = context.findPersistentPropertyPaths(entityType, PersistentProperty::isEntity); + this.paths = context.findPersistentPropertyPaths(entityType, (p) -> p.isEntity() && !p.isEmbedded()); } /** @@ -120,20 +121,18 @@ class WritingContext { List> actions = new ArrayList<>(); from(path).forEach(node -> { + DbAction.Insert insert; + if (node.getPath().getRequiredLeafProperty().isQualified()) { - DbAction.Insert insert; - if (node.getPath().getRequiredLeafProperty().isQualified()) { + Pair value = (Pair) node.getValue(); + insert = new DbAction.Insert<>(value.getSecond(), path, getAction(node.getParent())); + insert.getAdditionalValues().put(node.getPath().getRequiredLeafProperty().getKeyColumn(), value.getFirst()); - Pair value = (Pair) node.getValue(); - insert = new DbAction.Insert<>(value.getSecond(), path, getAction(node.getParent())); - insert.getAdditionalValues().put(node.getPath().getRequiredLeafProperty().getKeyColumn(), value.getFirst()); - - } else { - insert = new DbAction.Insert<>(node.getValue(), path, getAction(node.getParent())); - } - - previousActions.put(node, insert); - actions.add(insert); + } else { + insert = new DbAction.Insert<>(node.getValue(), path, getAction(node.getParent())); + } + previousActions.put(node, insert); + actions.add(insert); }); return actions; @@ -192,13 +191,9 @@ class WritingContext { List nodes = new ArrayList<>(); - if (path.getLength() == 1) { - - Object value = context // - .getRequiredPersistentEntity(entityType) // - .getPropertyAccessor(entity) // - .getProperty(path.getRequiredLeafProperty()); + if (dependsOnRootIgnoringEmbeddables(path)) { + Object value = getFromRootValue(path); nodes.addAll(createNodes(path, null, value)); } else { @@ -218,6 +213,44 @@ class WritingContext { return nodes; } + private boolean dependsOnRootIgnoringEmbeddables(PersistentPropertyPath path){ + PersistentPropertyPath currentPath = path.getParentPath(); + + while (!currentPath.isEmpty()){ + if(!currentPath.getRequiredLeafProperty().isEmbedded()){ + return false; + } + currentPath = currentPath.getParentPath(); + } + + return true; + } + + @Nullable + private Object getFromRootValue(PersistentPropertyPath path){ + final Stack> stack = new Stack<>(); + PersistentPropertyPath currentPath = path; + + while (!currentPath.isEmpty()){ + stack.push(currentPath); + currentPath = currentPath.getParentPath(); + } + + + Object value = entity; + while (!stack.empty() && value != null){ + currentPath = stack.pop(); + final RelationalPersistentProperty property = currentPath.getRequiredLeafProperty(); + + value = context // + .getRequiredPersistentEntity(property.getOwner().getType()) // + .getPropertyAccessor(value) // + .getProperty(property); + } + + return value; + } + private List createNodes( PersistentPropertyPath path, @Nullable PathNode parentNode, @Nullable Object value) { @@ -227,8 +260,9 @@ class WritingContext { } List nodes = new ArrayList<>(); - - if (path.getRequiredLeafProperty().isQualified()) { + if(path.getRequiredLeafProperty().isEmbedded()){ + nodes.add(new PathNode(path, parentNode, value)); + } else if (path.getRequiredLeafProperty().isQualified()) { if (path.getRequiredLeafProperty().isMap()) { ((Map) value) 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 647c719b..5f34cdfd 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 @@ -40,6 +40,7 @@ import org.springframework.util.StringUtils; * @author Jens Schauder * @author Greg Turnquist * @author Florian Lüdiger + * @author Bastian Wilhelm */ public class BasicRelationalPersistentProperty extends AnnotationBasedPersistentProperty implements RelationalPersistentProperty { @@ -56,6 +57,8 @@ public class BasicRelationalPersistentProperty extends AnnotationBasedPersistent private final RelationalMappingContext context; private final Lazy> columnName; private final Lazy> keyColumnName; + private final Lazy isEmbedded; + private final Lazy embeddedPrefix; /** * Creates a new {@link AnnotationBasedPersistentProperty}. @@ -74,6 +77,17 @@ public class BasicRelationalPersistentProperty extends AnnotationBasedPersistent this.context = context; + this.isEmbedded = Lazy.of(() -> Optional.ofNullable( + findAnnotation(Embedded.class)) + .isPresent() + ); + + this.embeddedPrefix = Lazy.of(() -> Optional.ofNullable( + findAnnotation(Embedded.class)) + .map(Embedded::value) + .orElse("") + ); + this.columnName = Lazy.of(() -> Optional.ofNullable( // findAnnotation(Column.class)) // .map(Column::value) // @@ -168,7 +182,21 @@ public class BasicRelationalPersistentProperty extends AnnotationBasedPersistent return isListLike(); } - private boolean isListLike() { + @Override + public boolean isEmbedded() { + return isEmbedded.get(); + } + + @Override + public String getEmbeddedPrefix() { + if(isEmbedded()){ + return embeddedPrefix.get(); + } else { + return null; + } + } + + private boolean isListLike() { return isCollectionLike() && !Set.class.isAssignableFrom(this.getType()); } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/Embedded.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/Embedded.java new file mode 100644 index 00000000..5b169a20 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/Embedded.java @@ -0,0 +1,22 @@ +package org.springframework.data.relational.core.mapping; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * The annotation to configure a value object as embedded in the current table. + * + * @author Bastian Wilhelm + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE }) +@Documented +public @interface Embedded { + /** + * @return prefix for columns in the embedded value object. Default is an empty String + */ + String value() default ""; +} 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 bae40ddb..d624f843 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 @@ -23,6 +23,7 @@ import org.springframework.lang.Nullable; * * @author Jens Schauder * @author Oliver Gierke + * @author Bastian Wilhelm */ public interface RelationalPersistentProperty extends PersistentProperty { @@ -68,4 +69,15 @@ public interface RelationalPersistentProperty extends PersistentProperty aggregateChange = // + new AggregateChange<>(Kind.SAVE, EmbeddedReferenceEntity.class, entity); + + converter.write(entity, aggregateChange); + + assertThat(aggregateChange.getActions()) // + .extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath, DbActionTestSupport::actualEntityType, + DbActionTestSupport::isWithDependsOn) // + .containsExactly( // + tuple(InsertRoot.class, EmbeddedReferenceEntity.class, "", EmbeddedReferenceEntity.class, false) // + ); + } + @Test // DATAJDBC-112 public void newEntityWithReferenceGetsConvertedToTwoInserts() { @@ -417,6 +438,13 @@ public class RelationalEntityWriterUnitTests { String name; } + @RequiredArgsConstructor + static class EmbeddedReferenceEntity { + + @Id final Long id; + @Embedded("prefix_") Element other; + } + @RequiredArgsConstructor static class ReferenceWoIdEntity { diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentPropertyUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentPropertyUnitTests.java index d0a322b2..95610a6f 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentPropertyUnitTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentPropertyUnitTests.java @@ -36,6 +36,7 @@ import org.springframework.data.mapping.PropertyHandler; * @author Jens Schauder * @author Oliver Gierke * @author Florian Lüdiger + * @author Bastian Wilhelm */ public class BasicRelationalPersistentPropertyUnitTests { @@ -99,6 +100,26 @@ public class BasicRelationalPersistentPropertyUnitTests { assertThat(listProperty.getKeyColumn()).isEqualTo("dummy_key_column_name"); } + @Test // DATAJDBC-111 + public void detectsEmbeddedEntity() { + + final RelationalPersistentEntity requiredPersistentEntity = context.getRequiredPersistentEntity(DummyEntity.class); + + assertThat(requiredPersistentEntity.getRequiredPersistentProperty("someList").isEmbedded()).isFalse(); + assertThat(requiredPersistentEntity.getRequiredPersistentProperty("someList").getEmbeddedPrefix()).isNull(); + + assertThat(requiredPersistentEntity.getRequiredPersistentProperty("id").isEmbedded()).isFalse(); + assertThat(requiredPersistentEntity.getRequiredPersistentProperty("id").getEmbeddedPrefix()).isNull(); + + assertThat(requiredPersistentEntity.getRequiredPersistentProperty("embeddableEntity").isEmbedded()).isTrue(); + assertThat(requiredPersistentEntity.getRequiredPersistentProperty("embeddableEntity").getEmbeddedPrefix()).isEmpty(); + + assertThat(requiredPersistentEntity.getRequiredPersistentProperty("prefixedEmbeddableEntity").isEmbedded()).isTrue(); + assertThat(requiredPersistentEntity.getRequiredPersistentProperty("prefixedEmbeddableEntity").getEmbeddedPrefix()).isEqualTo("prefix"); + } + + + private void checkTargetType(SoftAssertions softly, RelationalPersistentEntity persistentEntity, String propertyName, Class expected) { @@ -123,6 +144,13 @@ public class BasicRelationalPersistentPropertyUnitTests { // DATACMNS-106 private @Column("dummy_name") String name; + // DATAJDBC-111 + private @Embedded EmbeddableEntity embeddableEntity; + + // DATAJDBC-111 + private @Embedded("prefix") EmbeddableEntity prefixedEmbeddableEntity; + + @Column("dummy_last_updated_at") public LocalDateTime getLocalDateTime() { return localDateTime; @@ -141,4 +169,10 @@ public class BasicRelationalPersistentPropertyUnitTests { private enum SomeEnum { ALPHA } + + // DATAJDBC-111 + @Data + private static class EmbeddableEntity{ + private final String embeddedTest; + } } diff --git a/src/main/asciidoc/jdbc.adoc b/src/main/asciidoc/jdbc.adoc index b9bd3ed8..c4ed7bff 100644 --- a/src/main/asciidoc/jdbc.adoc +++ b/src/main/asciidoc/jdbc.adoc @@ -120,10 +120,11 @@ The properties of the following types are currently supported: * Anything your database driver accepts. -* References to other entities. They are considered a one-to-one relationship. -It is optional for such entities to have an `id` attribute. +* References to other entities. They are considered a one-to-one relationship, or an embedded type. +It is optional for one-to-one relationship entities to have an `id` attribute. The table of the referenced entity is expected to have an additional column named the same as the table of the referencing entity. You can change this name by implementing `NamingStrategy.getReverseColumnName(RelationalPersistentProperty property)`. +Embedded entities do not have an `id`. * `Set` is considered a one-to-many relationship. The table of the referenced entity is expected to have an additional column named the same as the table of the referencing entity. @@ -271,6 +272,34 @@ public class MySubEntity { ---- ==== +[[jdbc.entity-persistence.embedded-entities]] +=== `Embedded entities` + +Embedded entities are used to have value objects in your java data model, even if there is only one table in your database. +In the following example you see, that `MyEntity` is mapped with the `@Embedded` annotation. +The consequence of this is, that in the database a table `my_entity` with the two columns `id` and `name` (from the `EmbeddedEntity` class) is expected. + +==== +[source, java] +---- +public class MyEntity { + @Id + Integer id; + + @Embedded + EmbeddedEntity embeddedEntity; +} + +public class EmbeddedEntity { + String name; +} +---- +==== + +If you need a value object multiple times in an entity, this can be achieved with the optional `value` element of the `@Embedded` annotation. +This element represents a prefix and is prepend for each column name in the embedded object. + + [[jdbc.entity-persistence.state-detection-strategies]] === Entity State Detection Strategies