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.
This commit is contained in:
Schlagi123
2019-01-21 11:54:55 +01:00
committed by Jens Schauder
parent 9b856876fe
commit 7a26385b5a
52 changed files with 2328 additions and 91 deletions

View File

@@ -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<T> persistentEntity = getRequiredPersistentEntity(domainType);
Map<String, Object> 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<S> 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 <S> MapSqlParameterSource getPropertyMap(final S instance, RelationalPersistentEntity<S> persistentEntity) {
private <S, T> MapSqlParameterSource getPropertyMap(final S instance, RelationalPersistentEntity<S> persistentEntity, String prefix) {
MapSqlParameterSource parameters = new MapSqlParameterSource();
@@ -287,14 +288,20 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
persistentEntity.doWithProperties((PropertyHandler<RelationalPersistentProperty>) 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<T> embeddedEntity = (RelationalPersistentEntity<T>) 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;

View File

@@ -39,6 +39,7 @@ import org.springframework.util.Assert;
* @author Oliver Gierke
* @author Mark Paluch
* @author Maciej Walkowiak
* @author Bastian Wilhelm
*/
public class EntityRowMapper<T> implements RowMapper<T> {
@@ -105,12 +106,15 @@ public class EntityRowMapper<T> implements RowMapper<T> {
}
@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<T> implements RowMapper<T> {
*/
@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<T> implements RowMapper<T> {
}
@Nullable
private <S> S readEntityFrom(ResultSet rs, RelationalPersistentProperty property) {
private <S> S readEmbeddedEntityFrom(ResultSet rs, @Nullable Object id, RelationalPersistentProperty property, String prefix) {
String newPrefix = prefix + property.getEmbeddedPrefix();
String prefix = property.getName() + "_";
@SuppressWarnings("unchecked")
RelationalPersistentEntity<S> entity = (RelationalPersistentEntity<S>) context
.getRequiredPersistentEntity(property.getActualType());
S instance = createInstance(entity, rs, null, newPrefix);
PersistentPropertyAccessor<S> accessor = converter.getPropertyAccessor(entity, instance);
for (RelationalPersistentProperty p : entity) {
accessor.setProperty(p, readOrLoadProperty(rs, id, p, newPrefix));
}
return instance;
}
@Nullable
private <S> S readEntityFrom(ResultSet rs, RelationalPersistentProperty property, String prefix) {
String newPrefix = prefix + property.getName() + "_";
@SuppressWarnings("unchecked")
RelationalPersistentEntity<S> entity = (RelationalPersistentEntity<S>) context
@@ -150,22 +172,22 @@ public class EntityRowMapper<T> implements RowMapper<T> {
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<S> 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<T> implements RowMapper<T> {
}
}
private <S> S createInstance(RelationalPersistentEntity<S> entity, ResultSet rs, @Nullable Object idValue, String prefix) {
private <S> S createInstance(RelationalPersistentEntity<S> entity, ResultSet rs, @Nullable Object idValue,
String prefix) {
return converter.createInstance(entity, parameter -> {

View File

@@ -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<RelationalPersistentProperty>) p -> {
private void initColumnNames(RelationalPersistentEntity<?> entity, String prefix) {
entity.doWithProperties((PropertyHandler<RelationalPersistentProperty>) 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<RelationalPersistentProperty> path) {
PersistentPropertyPath<RelationalPersistentProperty> 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<RelationalPersistentProperty> getSubPath(
PersistentPropertyPath<RelationalPersistentProperty> path) {
@@ -338,11 +396,21 @@ class SqlGenerator {
PersistentPropertyPath<RelationalPersistentProperty> 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<RelationalPersistentProperty> extensionForBaseOf = path
.getExtensionForBaseOf(ancestor);
return extensionForBaseOf;
}
private String cascadeConditions(String innerCondition, PersistentPropertyPath<RelationalPersistentProperty> path) {
@@ -351,8 +419,13 @@ class SqlGenerator {
return innerCondition;
}
PersistentPropertyPath<RelationalPersistentProperty> 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)", //

View File

@@ -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<Trivial> children;
}
static class EmbeddedEntity {
@Id Long id;
String name;
@Embedded("prefix_") Trivial children;
}
private static class DontUseSetter {
String value;

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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");
}

View File

@@ -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<DummyEntity> 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<DummyEntity, Long> {}
@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;
}
}

View File

@@ -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<DummyEntity> 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<DummyEntity, Long> {}
@Data
static class DummyEntity {
@Id Long id;
@Embedded("prefix_") Embeddable prefixedEmbeddable;
}
@Value
@Wither
private static class Embeddable {
Long attr1;
String attr2;
}
}

View File

@@ -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<DummyEntity> 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<DummyEntity, Long> {}
@Data
static class DummyEntity {
@Id Long id;
@Embedded("prefix_") Embeddable prefixedEmbeddable;
@Embedded Embeddable embeddable;
}
@Data
static class Embeddable {
Long attr1;
String attr2;
}
}

View File

@@ -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<DummyEntity> 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<DummyEntity, Long> {}
@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;
}
}

View File

@@ -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<DummyEntity> 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<DummyEntity, Long> {}
@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<DummyEntity2> list = new ArrayList<>();
String test;
}
@Data
private static class DummyEntity2 {
String test;
}
}

View File

@@ -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<DummyEntity> 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<DummyEntity, Long> {}
@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;
}
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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))

View File

@@ -0,0 +1 @@
CREATE TABLE dummy_entity (id BIGINT AUTO_INCREMENT PRIMARY KEY, PREFIX_ATTR1 BIGINT, PREFIX_ATTR2 VARCHAR(100));

View File

@@ -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));

View File

@@ -0,0 +1 @@
CREATE TABLE dummy_entity (id BIGINT AUTO_INCREMENT PRIMARY KEY, PREFIX_ATTR1 BIGINT, PREFIX_ATTR2 VARCHAR(100));

View File

@@ -0,0 +1,2 @@
DROP TABLE dummy_entity;
CREATE TABLE dummy_entity (id SERIAL PRIMARY KEY, PREFIX_ATTR1 BIGINT, PREFIX_ATTR2 VARCHAR(100));

View File

@@ -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))

View File

@@ -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));

View File

@@ -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));

View File

@@ -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));

View File

@@ -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));

View File

@@ -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)

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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))

View File

@@ -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));

View File

@@ -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));

View File

@@ -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));

View File

@@ -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));

View File

@@ -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))

View File

@@ -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));

View File

@@ -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));

View File

@@ -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));

View File

@@ -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));

View File

@@ -31,6 +31,7 @@ import org.springframework.util.Assert;
*
* @author Jens Schauder
* @author Mark Paluch
* @author Bastian Wilhelm
*/
public class RelationalEntityDeleteWriter implements EntityWriter<Object, AggregateChange<?>> {
@@ -66,6 +67,7 @@ public class RelationalEntityDeleteWriter implements EntityWriter<Object, Aggreg
List<DbAction<?>> 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<Object, Aggreg
List<DbAction<?>> 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);

View File

@@ -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<DbAction<?>> actions = new ArrayList<>();
from(path).forEach(node -> {
DbAction.Insert<Object> insert;
if (node.getPath().getRequiredLeafProperty().isQualified()) {
DbAction.Insert<Object> insert;
if (node.getPath().getRequiredLeafProperty().isQualified()) {
Pair<Object, Object> value = (Pair) node.getValue();
insert = new DbAction.Insert<>(value.getSecond(), path, getAction(node.getParent()));
insert.getAdditionalValues().put(node.getPath().getRequiredLeafProperty().getKeyColumn(), value.getFirst());
Pair<Object, Object> 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<PathNode> 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<RelationalPersistentProperty> path){
PersistentPropertyPath<RelationalPersistentProperty> currentPath = path.getParentPath();
while (!currentPath.isEmpty()){
if(!currentPath.getRequiredLeafProperty().isEmbedded()){
return false;
}
currentPath = currentPath.getParentPath();
}
return true;
}
@Nullable
private Object getFromRootValue(PersistentPropertyPath<RelationalPersistentProperty> path){
final Stack<PersistentPropertyPath<RelationalPersistentProperty>> stack = new Stack<>();
PersistentPropertyPath<RelationalPersistentProperty> 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<PathNode> createNodes(
PersistentPropertyPath<RelationalPersistentProperty> path,
@Nullable PathNode parentNode, @Nullable Object value) {
@@ -227,8 +260,9 @@ class WritingContext {
}
List<PathNode> 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)

View File

@@ -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<RelationalPersistentProperty>
implements RelationalPersistentProperty {
@@ -56,6 +57,8 @@ public class BasicRelationalPersistentProperty extends AnnotationBasedPersistent
private final RelationalMappingContext context;
private final Lazy<Optional<String>> columnName;
private final Lazy<Optional<String>> keyColumnName;
private final Lazy<Boolean> isEmbedded;
private final Lazy<String> 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());
}

View File

@@ -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 "";
}

View File

@@ -23,6 +23,7 @@ import org.springframework.lang.Nullable;
*
* @author Jens Schauder
* @author Oliver Gierke
* @author Bastian Wilhelm
*/
public interface RelationalPersistentProperty extends PersistentProperty<RelationalPersistentProperty> {
@@ -68,4 +69,15 @@ public interface RelationalPersistentProperty extends PersistentProperty<Relatio
* Returns whether this property is an ordered property.
*/
boolean isOrdered();
/**
* @return true, if the Property is an embedded value object, otherwise false.
*/
boolean isEmbedded();
/**
* @return Prefix for embedded columns. If the column is not embedded the return value is null.
*/
@Nullable
String getEmbeddedPrefix();
}

View File

@@ -35,12 +35,14 @@ import org.springframework.data.relational.core.conversion.DbAction.Delete;
import org.springframework.data.relational.core.conversion.DbAction.Insert;
import org.springframework.data.relational.core.conversion.DbAction.InsertRoot;
import org.springframework.data.relational.core.conversion.DbAction.UpdateRoot;
import org.springframework.data.relational.core.mapping.Embedded;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
/**
* Unit tests for the {@link RelationalEntityWriter}
*
* @author Jens Schauder
* @author Bastian Wilhelm
*/
@RunWith(MockitoJUnitRunner.class)
public class RelationalEntityWriterUnitTests {
@@ -65,6 +67,25 @@ public class RelationalEntityWriterUnitTests {
);
}
@Test // DATAJDBC-111
public void newEntityGetsConvertedToOneInsertByEmbeddedEntities() {
EmbeddedReferenceEntity entity = new EmbeddedReferenceEntity(null);
entity.other = new Element(2L);
AggregateChange<EmbeddedReferenceEntity> 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 {

View File

@@ -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;
}
}

View File

@@ -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<some entity>` 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