From ff8a71fe62efac310da5e52796ac4af7b1e33da5 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Fri, 11 Jan 2019 11:09:01 +0100 Subject: [PATCH] DATAJDBC-282 - Polishing. Formatting. Removed AbstractRelationalEntityWriter. Fixed integration test for MS-SQL. Extracted some common test infrastructure. Original pull request: #107. --- .../jdbc/core/JdbcAggregateOperations.java | 183 +++++----- .../data/jdbc/core/JdbcAggregateTemplate.java | 105 +++--- .../data/jdbc/core/JdbcRepository.java | 32 +- .../support/SimpleJdbcRepository.java | 4 +- ...ositoryInsertExistingIntegrationTests.java | 102 ++++++ .../JdbcRepositoryIntegrationTests.java | 328 ++++++++---------- ...oryInsertExistingIntegrationTests-hsql.sql | 1 + ...InsertExistingIntegrationTests-mariadb.sql | 1 + ...ryInsertExistingIntegrationTests-mssql.sql | 2 + ...ryInsertExistingIntegrationTests-mysql.sql | 1 + ...nsertExistingIntegrationTests-postgres.sql | 2 + .../AbstractRelationalEntityWriter.java | 274 --------------- .../relational/core/conversion/PathNode.java | 47 +++ .../RelationalEntityInsertWriter.java | 24 +- .../RelationalEntityUpdateWriter.java | 24 +- .../conversion/RelationalEntityWriter.java | 17 +- .../core/conversion/WritingContext.java | 252 ++++++++++++++ .../core/conversion/DbActionTestSupport.java | 47 +++ ...RelationalEntityDeleteWriterUnitTests.java | 18 +- ...RelationalEntityInsertWriterUnitTests.java | 47 +-- ...RelationalEntityUpdateWriterUnitTests.java | 83 ++--- .../RelationalEntityWriterUnitTests.java | 97 ++---- 22 files changed, 917 insertions(+), 774 deletions(-) create mode 100644 spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryInsertExistingIntegrationTests.java create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryInsertExistingIntegrationTests-hsql.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryInsertExistingIntegrationTests-mariadb.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryInsertExistingIntegrationTests-mssql.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryInsertExistingIntegrationTests-mysql.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryInsertExistingIntegrationTests-postgres.sql delete mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/AbstractRelationalEntityWriter.java create mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/PathNode.java create mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/WritingContext.java create mode 100644 spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/DbActionTestSupport.java diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateOperations.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateOperations.java index fe119dde2..55a02a387 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateOperations.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateOperations.java @@ -21,106 +21,111 @@ import org.springframework.lang.Nullable; * Specifies a operations one can perform on a database, based on an Domain Type. * * @author Jens Schauder + * @author Thomas Lang */ public interface JdbcAggregateOperations { - /** - * Saves an instance of an aggregate, including all the members of the aggregate. - * - * @param instance the aggregate root of the aggregate to be saved. Must not be {@code null}. - * @param the type of the aggregate root. - * @return the saved instance. - */ - T save(T instance); + /** + * Saves an instance of an aggregate, including all the members of the aggregate. + * + * @param instance the aggregate root of the aggregate to be saved. Must not be {@code null}. + * @param the type of the aggregate root. + * @return the saved instance. + */ + T save(T instance); - /** - * Dedicated insert function to do just the insert of an instance of an aggregate, including all the members of the aggregate. - * - * @param instance the aggregate root of the aggregate to be inserted. Must not be {@code null}. - * @param the type of the aggregate root. - * @return the saved instance. - */ - T insert(T instance); + /** + * Dedicated insert function. This skips the test if the aggregate root is new and makes an insert. + *

+ * This is useful if the client provides an id for new aggregate roots. + *

+ * + * @param instance the aggregate root of the aggregate to be inserted. Must not be {@code null}. + * @param the type of the aggregate root. + * @return the saved instance. + */ + T insert(T instance); - /** - * Dedicated update function to do just an update of an instance of an aggregate, including all the members of the aggregate. - * - * @param instance the aggregate root of the aggregate to be inserted. Must not be {@code null}. - * @param the type of the aggregate root. - * @return the saved instance. - */ - T update(T instance); + /** + * Dedicated update function. This skips the test if the aggregate root is new or not and always performs an update + * operation. + * + * @param instance the aggregate root of the aggregate to be inserted. Must not be {@code null}. + * @param the type of the aggregate root. + * @return the saved instance. + */ + T update(T instance); - /** - * Deletes a single Aggregate including all entities contained in that aggregate. - * - * @param id the id of the aggregate root of the aggregate to be deleted. Must not be {@code null}. - * @param domainType the type of the aggregate root. - * @param the type of the aggregate root. - */ - void deleteById(Object id, Class domainType); + /** + * Deletes a single Aggregate including all entities contained in that aggregate. + * + * @param id the id of the aggregate root of the aggregate to be deleted. Must not be {@code null}. + * @param domainType the type of the aggregate root. + * @param the type of the aggregate root. + */ + void deleteById(Object id, Class domainType); - /** - * Delete an aggregate identified by it's aggregate root. - * - * @param aggregateRoot to delete. Must not be {@code null}. - * @param domainType the type of the aggregate root. Must not be {@code null}. - * @param the type of the aggregate root. - */ - void delete(T aggregateRoot, Class domainType); + /** + * Delete an aggregate identified by it's aggregate root. + * + * @param aggregateRoot to delete. Must not be {@code null}. + * @param domainType the type of the aggregate root. Must not be {@code null}. + * @param the type of the aggregate root. + */ + void delete(T aggregateRoot, Class domainType); - /** - * Delete all aggregates of a given type. - * - * @param domainType type of the aggregate roots to be deleted. Must not be {@code null}. - */ - void deleteAll(Class domainType); + /** + * Delete all aggregates of a given type. + * + * @param domainType type of the aggregate roots to be deleted. Must not be {@code null}. + */ + void deleteAll(Class domainType); - /** - * Counts the number of aggregates of a given type. - * - * @param domainType the type of the aggregates to be counted. - * @return the number of instances stored in the database. Guaranteed to be not {@code null}. - */ - long count(Class domainType); + /** + * Counts the number of aggregates of a given type. + * + * @param domainType the type of the aggregates to be counted. + * @return the number of instances stored in the database. Guaranteed to be not {@code null}. + */ + long count(Class domainType); - /** - * Load an aggregate from the database. - * - * @param id the id of the aggregate to load. Must not be {@code null}. - * @param domainType the type of the aggregate root. Must not be {@code null}. - * @param the type of the aggregate root. - * @return the loaded aggregate. Might return {@code null}. - */ - @Nullable - T findById(Object id, Class domainType); + /** + * Load an aggregate from the database. + * + * @param id the id of the aggregate to load. Must not be {@code null}. + * @param domainType the type of the aggregate root. Must not be {@code null}. + * @param the type of the aggregate root. + * @return the loaded aggregate. Might return {@code null}. + */ + @Nullable + T findById(Object id, Class domainType); - /** - * Load all aggregates of a given type that are identified by the given ids. - * - * @param ids of the aggregate roots identifying the aggregates to load. Must not be {@code null}. - * @param domainType the type of the aggregate roots. Must not be {@code null}. - * @param the type of the aggregate roots. Must not be {@code null}. - * @return Guaranteed to be not {@code null}. - */ - Iterable findAllById(Iterable ids, Class domainType); + /** + * Load all aggregates of a given type that are identified by the given ids. + * + * @param ids of the aggregate roots identifying the aggregates to load. Must not be {@code null}. + * @param domainType the type of the aggregate roots. Must not be {@code null}. + * @param the type of the aggregate roots. Must not be {@code null}. + * @return Guaranteed to be not {@code null}. + */ + Iterable findAllById(Iterable ids, Class domainType); - /** - * Load all aggregates of a given type. - * - * @param domainType the type of the aggregate roots. Must not be {@code null}. - * @param the type of the aggregate roots. Must not be {@code null}. - * @return Guaranteed to be not {@code null}. - */ - Iterable findAll(Class domainType); + /** + * Load all aggregates of a given type. + * + * @param domainType the type of the aggregate roots. Must not be {@code null}. + * @param the type of the aggregate roots. Must not be {@code null}. + * @return Guaranteed to be not {@code null}. + */ + Iterable findAll(Class domainType); - /** - * Checks if an aggregate identified by type and id exists in the database. - * - * @param id the id of the aggregate root. - * @param domainType the type of the aggregate root. - * @param the type of the aggregate root. - * @return whether the aggregate exists. - */ - boolean existsById(Object id, Class domainType); + /** + * Checks if an aggregate identified by type and id exists in the database. + * + * @param id the id of the aggregate root. + * @param domainType the type of the aggregate root. + * @param the type of the aggregate root. + * @return whether the aggregate exists. + */ + boolean existsById(Object id, Class domainType); } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateTemplate.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateTemplate.java index b60d3a7ee..1036a36df 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateTemplate.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateTemplate.java @@ -44,6 +44,7 @@ import org.springframework.util.Assert; * * @author Jens Schauder * @author Mark Paluch + * @author Thomas Lang */ public class JdbcAggregateTemplate implements JdbcAggregateOperations { @@ -59,36 +60,12 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations { private final DataAccessStrategy accessStrategy; - private T store(T instance, IdentifierAccessor identifierAccessor, AggregateChange change, - RelationalPersistentEntity persistentEntity) { - Assert.notNull(instance, "Aggregate instance must not be null!"); - publisher.publishEvent(new BeforeSaveEvent( // - Identifier.ofNullable(identifierAccessor.getIdentifier()), // - instance, // - change // - )); - - change.executeWith(interpreter, context, converter); - - Object identifier = persistentEntity.getIdentifierAccessor(change.getEntity()).getIdentifier(); - - Assert.notNull(identifier, "After saving the identifier must not be null"); - - publisher.publishEvent(new AfterSaveEvent( // - Identifier.of(identifier), // - change.getEntity(), // - change // - )); - - return (T) change.getEntity(); - } - /** * Creates a new {@link JdbcAggregateTemplate} given {@link ApplicationEventPublisher}, * {@link RelationalMappingContext} and {@link DataAccessStrategy}. * - * @param publisher must not be {@literal null}. - * @param context must not be {@literal null}. + * @param publisher must not be {@literal null}. + * @param context must not be {@literal null}. * @param dataAccessStrategy must not be {@literal null}. */ public JdbcAggregateTemplate(ApplicationEventPublisher publisher, RelationalMappingContext context, @@ -115,7 +92,8 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations { * (non-Javadoc) * @see org.springframework.data.jdbc.core.JdbcAggregateOperations#save(java.lang.Object) */ - @Override public T save(T instance) { + @Override + public T save(T instance) { Assert.notNull(instance, "Aggregate instance must not be null!"); @@ -128,12 +106,15 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations { } /** - * Dedicated insert function to do just the insert of an instance of an aggregate, including all the members of the aggregate. + * Dedicated insert function to do just the insert of an instance of an aggregate, including all the members of the + * aggregate. * * @param instance the aggregate root of the aggregate to be inserted. Must not be {@code null}. * @return the saved instance. */ - @Override public T insert(T instance) { + @Override + public T insert(T instance) { + Assert.notNull(instance, "Aggregate instance must not be null!"); RelationalPersistentEntity persistentEntity = context.getRequiredPersistentEntity(instance.getClass()); @@ -145,12 +126,15 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations { } /** - * Dedicated update function to do just an update of an instance of an aggregate, including all the members of the aggregate. + * Dedicated update function to do just an update of an instance of an aggregate, including all the members of the + * aggregate. * * @param instance the aggregate root of the aggregate to be inserted. Must not be {@code null}. * @return the saved instance. */ - @Override public T update(T instance) { + @Override + public T update(T instance) { + Assert.notNull(instance, "Aggregate instance must not be null!"); RelationalPersistentEntity persistentEntity = context.getRequiredPersistentEntity(instance.getClass()); @@ -165,7 +149,8 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations { * (non-Javadoc) * @see org.springframework.data.jdbc.core.JdbcAggregateOperations#count(java.lang.Class) */ - @Override public long count(Class domainType) { + @Override + public long count(Class domainType) { return accessStrategy.count(domainType); } @@ -173,7 +158,8 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations { * (non-Javadoc) * @see org.springframework.data.jdbc.core.JdbcAggregateOperations#findById(java.lang.Object, java.lang.Class) */ - @Override public T findById(Object id, Class domainType) { + @Override + public T findById(Object id, Class domainType) { T entity = accessStrategy.findById(id, domainType); if (entity != null) { @@ -186,7 +172,8 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations { * (non-Javadoc) * @see org.springframework.data.jdbc.core.JdbcAggregateOperations#existsById(java.lang.Object, java.lang.Class) */ - @Override public boolean existsById(Object id, Class domainType) { + @Override + public boolean existsById(Object id, Class domainType) { return accessStrategy.existsById(id, domainType); } @@ -194,7 +181,8 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations { * (non-Javadoc) * @see org.springframework.data.jdbc.core.JdbcAggregateOperations#findAll(java.lang.Class) */ - @Override public Iterable findAll(Class domainType) { + @Override + public Iterable findAll(Class domainType) { Iterable all = accessStrategy.findAll(domainType); publishAfterLoad(all); @@ -205,7 +193,8 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations { * (non-Javadoc) * @see org.springframework.data.jdbc.core.JdbcAggregateOperations#findAllById(java.lang.Iterable, java.lang.Class) */ - @Override public Iterable findAllById(Iterable ids, Class domainType) { + @Override + public Iterable findAllById(Iterable ids, Class domainType) { Iterable allById = accessStrategy.findAllById(ids, domainType); publishAfterLoad(allById); @@ -216,7 +205,8 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations { * (non-Javadoc) * @see org.springframework.data.jdbc.core.JdbcAggregateOperations#delete(java.lang.Object, java.lang.Class) */ - @Override public void delete(S aggregateRoot, Class domainType) { + @Override + public void delete(S aggregateRoot, Class domainType) { IdentifierAccessor identifierAccessor = context.getRequiredPersistentEntity(domainType) .getIdentifierAccessor(aggregateRoot); @@ -228,7 +218,8 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations { * (non-Javadoc) * @see org.springframework.data.jdbc.core.JdbcAggregateOperations#deleteById(java.lang.Object, java.lang.Class) */ - @Override public void deleteById(Object id, Class domainType) { + @Override + public void deleteById(Object id, Class domainType) { deleteTree(id, null, domainType); } @@ -236,12 +227,39 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations { * (non-Javadoc) * @see org.springframework.data.jdbc.core.JdbcAggregateOperations#deleteAll(java.lang.Class) */ - @Override public void deleteAll(Class domainType) { + @Override + public void deleteAll(Class domainType) { AggregateChange change = createDeletingChange(domainType); change.executeWith(interpreter, context, converter); } + private T store(T instance, IdentifierAccessor identifierAccessor, AggregateChange change, + RelationalPersistentEntity persistentEntity) { + + Assert.notNull(instance, "Aggregate instance must not be null!"); + + publisher.publishEvent(new BeforeSaveEvent( // + Identifier.ofNullable(identifierAccessor.getIdentifier()), // + instance, // + change // + )); + + change.executeWith(interpreter, context, converter); + + Object identifier = persistentEntity.getIdentifierAccessor(change.getEntity()).getIdentifier(); + + Assert.notNull(identifier, "After saving the identifier must not be null"); + + publisher.publishEvent(new AfterSaveEvent( // + Identifier.of(identifier), // + change.getEntity(), // + change // + )); + + return (T) change.getEntity(); + } + private void deleteTree(Object id, @Nullable Object entity, Class domainType) { AggregateChange change = createDeletingChange(id, entity, domainType); @@ -255,21 +273,24 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations { publisher.publishEvent(new AfterDeleteEvent(specifiedId, optionalEntity, change)); } - @SuppressWarnings({ "unchecked", "rawtypes" }) private AggregateChange createChange(T instance) { + @SuppressWarnings({ "unchecked", "rawtypes" }) + private AggregateChange createChange(T instance) { AggregateChange aggregateChange = new AggregateChange(Kind.SAVE, instance.getClass(), instance); jdbcEntityWriter.write(instance, aggregateChange); return aggregateChange; } - @SuppressWarnings({ "unchecked", "rawtypes" }) private AggregateChange createInsertChange(T instance) { + @SuppressWarnings({ "unchecked", "rawtypes" }) + private AggregateChange createInsertChange(T instance) { AggregateChange aggregateChange = new AggregateChange(Kind.SAVE, instance.getClass(), instance); jdbcEntityInsertWriter.write(instance, aggregateChange); return aggregateChange; } - @SuppressWarnings({ "unchecked", "rawtypes" }) private AggregateChange createUpdateChange(T instance) { + @SuppressWarnings({ "unchecked", "rawtypes" }) + private AggregateChange createUpdateChange(T instance) { AggregateChange aggregateChange = new AggregateChange(Kind.SAVE, instance.getClass(), instance); jdbcEntityUpdateWriter.write(instance, aggregateChange); diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcRepository.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcRepository.java index beaae03c3..2c2a7bc61 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcRepository.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcRepository.java @@ -3,16 +3,36 @@ package org.springframework.data.jdbc.core; import org.springframework.data.repository.Repository; /** - * Jdbc repository for dedicated insert(), update() and upsert() sql functions. - * Other than {@link org.springframework.data.jdbc.repository.support.SimpleJdbcRepository} - * there should be bypassing of the isNew check. + * Jdbc repository for dedicated insert(), update() and upsert() sql functions. Other than + * {@link org.springframework.data.jdbc.repository.support.SimpleJdbcRepository} there should be bypassing of the isNew + * check. * * @author Thomas Lang - * @see DATAJDBC-282 + * @since 1.1 */ public interface JdbcRepository extends Repository { - S insert(S var1); + /** + * Dedicated insert function. This skips the test if the aggregate root is new and makes an insert. + *

+ * This is useful if the client provides an id for new aggregate roots. + *

+ * + * @param aggregateRoot the aggregate root to be saved in the database. Must not be {@code null}. + * @param Type of the aggregate root. + * @return the saved aggregate root. If the provided aggregate root was immutable and a value needed changing, e.g. + * the id this will be a new instance. + */ + S insert(S aggregateRoot); - S update(S var1); + /** + * Dedicated update function. This skips the test if the aggregate root is new or not and always performs an update + * operation. + * + * @param aggregateRoot the aggregate root to be saved in the database. Must not be {@code null}. + * @param Type of the aggregate root. + * @return the saved aggregate root. If the provided aggregate root was immutable and a value needed changing, e.g. + * the id this will be a new instance. + */ + S update(S aggregateRoot); } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/SimpleJdbcRepository.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/SimpleJdbcRepository.java index b6b846d22..836de3992 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/SimpleJdbcRepository.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/SimpleJdbcRepository.java @@ -152,7 +152,7 @@ public class SimpleJdbcRepository implements CrudRepository, JdbcR * @see org.springframework.data.repository.JdbcRepository#update(T t) */ @Override - public S update(S var1) { - return entityOperations.update(var1); + public S update(S aggregateRoot) { + return entityOperations.update(aggregateRoot); } } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryInsertExistingIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryInsertExistingIntegrationTests.java new file mode 100644 index 000000000..6b88949ca --- /dev/null +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryInsertExistingIntegrationTests.java @@ -0,0 +1,102 @@ +/* + * 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 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.core.JdbcRepository; +import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; +import org.springframework.data.jdbc.testing.TestConfiguration; +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. + * + * @author Jens Schauder + */ +@ContextConfiguration +@Transactional +public class JdbcRepositoryInsertExistingIntegrationTests { + + @Configuration + @Import(TestConfiguration.class) + static class Config { + + @Autowired JdbcRepositoryFactory factory; + + @Bean + Class testClass() { + return JdbcRepositoryInsertExistingIntegrationTests.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-282 + public void insertAnExistingEntity() { + + DummyEntity existingDummyEntity = createDummyEntity(); + existingDummyEntity.idProp = 123L; + + DummyEntity entity = repository.insert(existingDummyEntity); + + assertThat(JdbcTestUtils.countRowsInTableWhere((JdbcTemplate) template.getJdbcOperations(), "dummy_entity", + "id_Prop = " + existingDummyEntity.getIdProp())).isEqualTo(1); + } + + private static DummyEntity createDummyEntity() { + + DummyEntity entity = new DummyEntity(); + entity.setName("Entity Name"); + return entity; + } + + interface DummyEntityRepository extends CrudRepository, JdbcRepository {} + + @Data + static class DummyEntity { + + String name; + @Id private Long idProp; + } +} diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java index d30692637..daf0846ee 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java @@ -40,6 +40,8 @@ import org.springframework.test.context.junit4.rules.SpringMethodRule; import org.springframework.test.jdbc.JdbcTestUtils; import org.springframework.transaction.annotation.Transactional; +import java.util.HashMap; + /** * Very simple use cases for creation and usage of JdbcRepositories. * @@ -49,246 +51,222 @@ import org.springframework.transaction.annotation.Transactional; @Transactional public class JdbcRepositoryIntegrationTests { - @Configuration - @Import(TestConfiguration.class) - static class Config { - - @Autowired - JdbcRepositoryFactory factory; - - @Bean - Class testClass() { - return JdbcRepositoryIntegrationTests.class; - } - - @Bean - DummyEntityRepository dummyEntityRepository() { - return factory.getRepository(DummyEntityRepository.class); - } - - } + @Configuration + @Import(TestConfiguration.class) + static class Config { - @ClassRule - public static final SpringClassRule classRule = new SpringClassRule(); - @Rule - public SpringMethodRule methodRule = new SpringMethodRule(); + @Autowired JdbcRepositoryFactory factory; - @Autowired - NamedParameterJdbcTemplate template; - @Autowired - DummyEntityRepository repository; + @Bean + Class testClass() { + return JdbcRepositoryIntegrationTests.class; + } - @Test // DATAJDBC-95 - public void savesAnEntity() { + @Bean + DummyEntityRepository dummyEntityRepository() { + return factory.getRepository(DummyEntityRepository.class); + } - DummyEntity entity = repository.save(createDummyEntity()); + } - assertThat(JdbcTestUtils.countRowsInTableWhere((JdbcTemplate) template.getJdbcOperations(), "dummy_entity", - "id_Prop = " + entity.getIdProp())).isEqualTo(1); - } + @ClassRule public static final SpringClassRule classRule = new SpringClassRule(); + @Rule public SpringMethodRule methodRule = new SpringMethodRule(); - @Test // DATAJDBC-282 - public void insertAnEntity() { + @Autowired NamedParameterJdbcTemplate template; + @Autowired DummyEntityRepository repository; - DummyEntity entity = repository.insert(createDummyEntity()); + @Test // DATAJDBC-95 + public void savesAnEntity() { - assertThat(JdbcTestUtils.countRowsInTableWhere((JdbcTemplate) template.getJdbcOperations(), "dummy_entity", - "id_Prop = " + entity.getIdProp())).isEqualTo(1); - } + DummyEntity entity = repository.save(createDummyEntity()); - @Test // DATAJDBC-282 - public void insertAnExistingEntity() { + assertThat(JdbcTestUtils.countRowsInTableWhere((JdbcTemplate) template.getJdbcOperations(), "dummy_entity", + "id_Prop = " + entity.getIdProp())).isEqualTo(1); + } - DummyEntity existingDummyEntity = createExistingDummyEntity(); - DummyEntity entity = repository.insert(existingDummyEntity); - assertThat(JdbcTestUtils.countRowsInTableWhere((JdbcTemplate) template.getJdbcOperations(), "dummy_entity", - "id_Prop = " + existingDummyEntity.getIdProp())).isEqualTo(1); - } + @Test // DATAJDBC-282 + public void insertAnEntity() { - @Test // DATAJDBC-95 - public void saveAndLoadAnEntity() { + DummyEntity entity = repository.insert(createDummyEntity()); - DummyEntity entity = repository.save(createDummyEntity()); + assertThat(JdbcTestUtils.countRowsInTableWhere((JdbcTemplate) template.getJdbcOperations(), "dummy_entity", + "id_Prop = " + entity.getIdProp())).isEqualTo(1); + } - assertThat(repository.findById(entity.getIdProp())).hasValueSatisfying(it -> { + @Test // DATAJDBC-95 + public void saveAndLoadAnEntity() { - assertThat(it.getIdProp()).isEqualTo(entity.getIdProp()); - assertThat(it.getName()).isEqualTo(entity.getName()); - }); - } + DummyEntity entity = repository.save(createDummyEntity()); - @Test // DATAJDBC-97 - public void savesManyEntities() { + assertThat(repository.findById(entity.getIdProp())).hasValueSatisfying(it -> { - DummyEntity entity = createDummyEntity(); - DummyEntity other = createDummyEntity(); + assertThat(it.getIdProp()).isEqualTo(entity.getIdProp()); + assertThat(it.getName()).isEqualTo(entity.getName()); + }); + } - repository.saveAll(asList(entity, other)); + @Test // DATAJDBC-97 + public void savesManyEntities() { - assertThat(repository.findAll()) // - .extracting(DummyEntity::getIdProp) // - .containsExactlyInAnyOrder(entity.getIdProp(), other.getIdProp()); - } + DummyEntity entity = createDummyEntity(); + DummyEntity other = createDummyEntity(); - @Test // DATAJDBC-97 - public void existsReturnsTrueIffEntityExists() { + repository.saveAll(asList(entity, other)); - DummyEntity entity = repository.save(createDummyEntity()); + assertThat(repository.findAll()) // + .extracting(DummyEntity::getIdProp) // + .containsExactlyInAnyOrder(entity.getIdProp(), other.getIdProp()); + } - assertThat(repository.existsById(entity.getIdProp())).isTrue(); - assertThat(repository.existsById(entity.getIdProp() + 1)).isFalse(); - } + @Test // DATAJDBC-97 + public void existsReturnsTrueIffEntityExists() { - @Test // DATAJDBC-97 - public void findAllFindsAllEntities() { + DummyEntity entity = repository.save(createDummyEntity()); - DummyEntity entity = repository.save(createDummyEntity()); - DummyEntity other = repository.save(createDummyEntity()); + assertThat(repository.existsById(entity.getIdProp())).isTrue(); + assertThat(repository.existsById(entity.getIdProp() + 1)).isFalse(); + } - Iterable all = repository.findAll(); + @Test // DATAJDBC-97 + public void findAllFindsAllEntities() { - assertThat(all)// - .extracting(DummyEntity::getIdProp)// - .containsExactlyInAnyOrder(entity.getIdProp(), other.getIdProp()); - } + DummyEntity entity = repository.save(createDummyEntity()); + DummyEntity other = repository.save(createDummyEntity()); - @Test // DATAJDBC-97 - public void findAllFindsAllSpecifiedEntities() { + Iterable all = repository.findAll(); - DummyEntity entity = repository.save(createDummyEntity()); - DummyEntity other = repository.save(createDummyEntity()); + assertThat(all)// + .extracting(DummyEntity::getIdProp)// + .containsExactlyInAnyOrder(entity.getIdProp(), other.getIdProp()); + } - assertThat(repository.findAllById(asList(entity.getIdProp(), other.getIdProp())))// - .extracting(DummyEntity::getIdProp)// - .containsExactlyInAnyOrder(entity.getIdProp(), other.getIdProp()); - } + @Test // DATAJDBC-97 + public void findAllFindsAllSpecifiedEntities() { - @Test // DATAJDBC-97 - public void countsEntities() { + DummyEntity entity = repository.save(createDummyEntity()); + DummyEntity other = repository.save(createDummyEntity()); - repository.save(createDummyEntity()); - repository.save(createDummyEntity()); - repository.save(createDummyEntity()); + assertThat(repository.findAllById(asList(entity.getIdProp(), other.getIdProp())))// + .extracting(DummyEntity::getIdProp)// + .containsExactlyInAnyOrder(entity.getIdProp(), other.getIdProp()); + } - assertThat(repository.count()).isEqualTo(3L); - } + @Test // DATAJDBC-97 + public void countsEntities() { - @Test // DATAJDBC-97 - public void deleteById() { + repository.save(createDummyEntity()); + repository.save(createDummyEntity()); + repository.save(createDummyEntity()); - DummyEntity one = repository.save(createDummyEntity()); - DummyEntity two = repository.save(createDummyEntity()); - DummyEntity three = repository.save(createDummyEntity()); + assertThat(repository.count()).isEqualTo(3L); + } - repository.deleteById(two.getIdProp()); + @Test // DATAJDBC-97 + public void deleteById() { - assertThat(repository.findAll()) // - .extracting(DummyEntity::getIdProp) // - .containsExactlyInAnyOrder(one.getIdProp(), three.getIdProp()); - } + DummyEntity one = repository.save(createDummyEntity()); + DummyEntity two = repository.save(createDummyEntity()); + DummyEntity three = repository.save(createDummyEntity()); - @Test // DATAJDBC-97 - public void deleteByEntity() { + repository.deleteById(two.getIdProp()); - DummyEntity one = repository.save(createDummyEntity()); - DummyEntity two = repository.save(createDummyEntity()); - DummyEntity three = repository.save(createDummyEntity()); + assertThat(repository.findAll()) // + .extracting(DummyEntity::getIdProp) // + .containsExactlyInAnyOrder(one.getIdProp(), three.getIdProp()); + } - repository.delete(one); + @Test // DATAJDBC-97 + public void deleteByEntity() { - assertThat(repository.findAll()) // - .extracting(DummyEntity::getIdProp) // - .containsExactlyInAnyOrder(two.getIdProp(), three.getIdProp()); - } + DummyEntity one = repository.save(createDummyEntity()); + DummyEntity two = repository.save(createDummyEntity()); + DummyEntity three = repository.save(createDummyEntity()); - @Test // DATAJDBC-97 - public void deleteByList() { + repository.delete(one); - DummyEntity one = repository.save(createDummyEntity()); - DummyEntity two = repository.save(createDummyEntity()); - DummyEntity three = repository.save(createDummyEntity()); + assertThat(repository.findAll()) // + .extracting(DummyEntity::getIdProp) // + .containsExactlyInAnyOrder(two.getIdProp(), three.getIdProp()); + } - repository.deleteAll(asList(one, three)); + @Test // DATAJDBC-97 + public void deleteByList() { - assertThat(repository.findAll()) // - .extracting(DummyEntity::getIdProp) // - .containsExactlyInAnyOrder(two.getIdProp()); - } + DummyEntity one = repository.save(createDummyEntity()); + DummyEntity two = repository.save(createDummyEntity()); + DummyEntity three = repository.save(createDummyEntity()); - @Test // DATAJDBC-97 - public void deleteAll() { + repository.deleteAll(asList(one, three)); - repository.save(createDummyEntity()); - repository.save(createDummyEntity()); - repository.save(createDummyEntity()); + assertThat(repository.findAll()) // + .extracting(DummyEntity::getIdProp) // + .containsExactlyInAnyOrder(two.getIdProp()); + } - assertThat(repository.findAll()).isNotEmpty(); + @Test // DATAJDBC-97 + public void deleteAll() { - repository.deleteAll(); + repository.save(createDummyEntity()); + repository.save(createDummyEntity()); + repository.save(createDummyEntity()); - assertThat(repository.findAll()).isEmpty(); - } + assertThat(repository.findAll()).isNotEmpty(); - @Test // DATAJDBC-98 - public void update() { + repository.deleteAll(); - DummyEntity entity = repository.save(createDummyEntity()); + assertThat(repository.findAll()).isEmpty(); + } - entity.setName("something else"); - DummyEntity saved = repository.save(entity); + @Test // DATAJDBC-98 + public void update() { - assertThat(repository.findById(entity.getIdProp())).hasValueSatisfying(it -> { - assertThat(it.getName()).isEqualTo(saved.getName()); - }); - } + DummyEntity entity = repository.save(createDummyEntity()); - @Test // DATAJDBC-98 - public void updateMany() { + entity.setName("something else"); + DummyEntity saved = repository.save(entity); - DummyEntity entity = repository.save(createDummyEntity()); - DummyEntity other = repository.save(createDummyEntity()); + assertThat(repository.findById(entity.getIdProp())).hasValueSatisfying(it -> { + assertThat(it.getName()).isEqualTo(saved.getName()); + }); + } - entity.setName("something else"); - other.setName("others Name"); + @Test // DATAJDBC-98 + public void updateMany() { - repository.saveAll(asList(entity, other)); + DummyEntity entity = repository.save(createDummyEntity()); + DummyEntity other = repository.save(createDummyEntity()); - assertThat(repository.findAll()) // - .extracting(DummyEntity::getName) // - .containsExactlyInAnyOrder(entity.getName(), other.getName()); - } + entity.setName("something else"); + other.setName("others Name"); - @Test // DATAJDBC-112 - public void findByIdReturnsEmptyWhenNoneFound() { + repository.saveAll(asList(entity, other)); - // NOT saving anything, so DB is empty + assertThat(repository.findAll()) // + .extracting(DummyEntity::getName) // + .containsExactlyInAnyOrder(entity.getName(), other.getName()); + } - assertThat(repository.findById(-1L)).isEmpty(); - } + @Test // DATAJDBC-112 + public void findByIdReturnsEmptyWhenNoneFound() { - private static DummyEntity createDummyEntity() { + // NOT saving anything, so DB is empty - DummyEntity entity = new DummyEntity(); - entity.setName("Entity Name"); - return entity; - } + assertThat(repository.findById(-1L)).isEmpty(); + } - private static DummyEntity createExistingDummyEntity() { + private static DummyEntity createDummyEntity() { - DummyEntity entity = new DummyEntity(); - entity.setIdProp(Long.parseLong("123")); - entity.setName("Entity Name"); - return entity; - } + DummyEntity entity = new DummyEntity(); + entity.setName("Entity Name"); + return entity; + } - interface DummyEntityRepository extends CrudRepository, JdbcRepository { - } + interface DummyEntityRepository extends CrudRepository, JdbcRepository {} - @Data - static class DummyEntity { + @Data + static class DummyEntity { - String name; - @Id - private Long idProp; - } + String name; + @Id private Long idProp; + } } diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryInsertExistingIntegrationTests-hsql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryInsertExistingIntegrationTests-hsql.sql new file mode 100644 index 000000000..25a09e431 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryInsertExistingIntegrationTests-hsql.sql @@ -0,0 +1 @@ +CREATE TABLE dummy_entity ( id_Prop BIGINT PRIMARY KEY, NAME VARCHAR(100)) diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryInsertExistingIntegrationTests-mariadb.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryInsertExistingIntegrationTests-mariadb.sql new file mode 100644 index 000000000..fdb22e2d4 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryInsertExistingIntegrationTests-mariadb.sql @@ -0,0 +1 @@ +CREATE TABLE dummy_entity (id_Prop BIGINT PRIMARY KEY, NAME VARCHAR(100)); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryInsertExistingIntegrationTests-mssql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryInsertExistingIntegrationTests-mssql.sql new file mode 100644 index 000000000..f12da5cb0 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryInsertExistingIntegrationTests-mssql.sql @@ -0,0 +1,2 @@ +DROP TABLE IF EXISTS dummy_entity; +CREATE TABLE dummy_entity (id_Prop BIGINT PRIMARY KEY, NAME VARCHAR(100)); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryInsertExistingIntegrationTests-mysql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryInsertExistingIntegrationTests-mysql.sql new file mode 100644 index 000000000..fdb22e2d4 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryInsertExistingIntegrationTests-mysql.sql @@ -0,0 +1 @@ +CREATE TABLE dummy_entity (id_Prop BIGINT PRIMARY KEY, NAME VARCHAR(100)); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryInsertExistingIntegrationTests-postgres.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryInsertExistingIntegrationTests-postgres.sql new file mode 100644 index 000000000..65f67a5bf --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryInsertExistingIntegrationTests-postgres.sql @@ -0,0 +1,2 @@ +DROP TABLE dummy_entity; +CREATE TABLE dummy_entity (id_Prop INTEGER PRIMARY KEY, NAME VARCHAR(100)); diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/AbstractRelationalEntityWriter.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/AbstractRelationalEntityWriter.java deleted file mode 100644 index f6b23bffd..000000000 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/AbstractRelationalEntityWriter.java +++ /dev/null @@ -1,274 +0,0 @@ -package org.springframework.data.relational.core.conversion; - -import lombok.Value; -import org.springframework.data.convert.EntityWriter; -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; -import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; -import org.springframework.data.util.Pair; -import org.springframework.lang.Nullable; -import org.springframework.util.Assert; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Used as an abstract class to derive relational writer actions (save, insert, update). - * Implementations see {@link RelationalEntityWriter} and {@link RelationalEntityInsertWriter} - * - * @author Thomas Lang - */ -abstract class AbstractRelationalEntityWriter implements EntityWriter> { - - protected final RelationalMappingContext context; - - AbstractRelationalEntityWriter(RelationalMappingContext context) { - this.context = context; - } - - /** - * Holds context information for the current save operation. - */ - class WritingContext { - - private final Object root; - private final Object entity; - private final Class entityType; - private final PersistentPropertyPaths paths; - private final Map previousActions = new HashMap<>(); - private Map, List> nodesCache = new HashMap<>(); - - WritingContext(Object root, AggregateChange aggregateChange) { - - this.root = root; - this.entity = aggregateChange.getEntity(); - this.entityType = aggregateChange.getEntityType(); - this.paths = context.findPersistentPropertyPaths(entityType, PersistentProperty::isEntity); - } - - /** - * Leaves out the isNew check as defined in #DATAJDBC-282 - * - * @return List of {@link DbAction}s - * @see DAJDBC-282 - */ - List> insert() { - - List> actions = new ArrayList<>(); - actions.add(setRootAction(new DbAction.InsertRoot<>(entity))); - actions.addAll(insertReferenced()); - return actions; - } - - /** - * Leaves out the isNew check as defined in #DATAJDBC-282 - * - * @return List of {@link DbAction}s - * @see DAJDBC-282 - */ - List> update() { - - List> actions = new ArrayList<>(deleteReferenced()); - actions.add(setRootAction(new DbAction.UpdateRoot<>(entity))); - actions.addAll(insertReferenced()); - return actions; - } - - List> save() { - - List> actions = new ArrayList<>(); - if (isNew(root)) { - - actions.add(setRootAction(new DbAction.InsertRoot<>(entity))); - actions.addAll(insertReferenced()); - } else { - - actions.addAll(deleteReferenced()); - actions.add(setRootAction(new DbAction.UpdateRoot<>(entity))); - actions.addAll(insertReferenced()); - } - - return actions; - } - - private boolean isNew(Object o) { - return context.getRequiredPersistentEntity(o.getClass()).isNew(o); - } - - //// Operations on all paths - - private List> insertReferenced() { - - List> actions = new ArrayList<>(); - - paths.forEach(path -> actions.addAll(insertAll(path))); - - return actions; - } - - private List> insertAll(PersistentPropertyPath path) { - - List> actions = new ArrayList<>(); - - from(path).forEach(node -> { - - DbAction.Insert insert; - if (node.path.getRequiredLeafProperty().isQualified()) { - - Pair value = (Pair) node.getValue(); - insert = new DbAction.Insert<>(value.getSecond(), path, getAction(node.parent)); - insert.getAdditionalValues().put(node.path.getRequiredLeafProperty().getKeyColumn(), value.getFirst()); - - } else { - insert = new DbAction.Insert<>(node.getValue(), path, getAction(node.parent)); - } - - previousActions.put(node, insert); - actions.add(insert); - }); - - return actions; - } - - private List> deleteReferenced() { - - List> deletes = new ArrayList<>(); - paths.forEach(path -> deletes.add(0, deleteReferenced(path))); - - return deletes; - } - - /// Operations on a single path - - private DbAction.Delete deleteReferenced(PersistentPropertyPath path) { - - Object id = context.getRequiredPersistentEntity(entityType).getIdentifierAccessor(entity).getIdentifier(); - - return new DbAction.Delete<>(id, path); - } - - //// methods not directly related to the creation of DbActions - - private DbAction setRootAction(DbAction dbAction) { - - previousActions.put(null, dbAction); - return dbAction; - } - - @Nullable - private DbAction.WithEntity getAction(@Nullable RelationalEntityInsertWriter.PathNode parent) { - - DbAction action = previousActions.get(parent); - - if (action != null) { - - Assert.isInstanceOf( // - DbAction.WithEntity.class, // - action, // - "dependsOn action is not a WithEntity, but " + action.getClass().getSimpleName() // - ); - - return (DbAction.WithEntity) action; - } - - return null; - } - // commented as of #DATAJDBC-282 - // private boolean isNew(Object o) { - // return context.getRequiredPersistentEntity(o.getClass()).isNew(o); - // } - - private List from( - PersistentPropertyPath path) { - - List nodes = new ArrayList<>(); - - if (path.getLength() == 1) { - - Object value = context // - .getRequiredPersistentEntity(entityType) // - .getPropertyAccessor(entity) // - .getProperty(path.getRequiredLeafProperty()); - - nodes.addAll(createNodes(path, null, value)); - - } else { - - List pathNodes = nodesCache.get(path.getParentPath()); - pathNodes.forEach(parentNode -> { - - Object value = path.getRequiredLeafProperty().getOwner().getPropertyAccessor(parentNode.getValue()) - .getProperty(path.getRequiredLeafProperty()); - - nodes.addAll(createNodes(path, parentNode, value)); - }); - } - - nodesCache.put(path, nodes); - - return nodes; - } - - private List createNodes( - PersistentPropertyPath path, - @Nullable RelationalEntityInsertWriter.PathNode parentNode, @Nullable Object value) { - - if (value == null) { - return Collections.emptyList(); - } - - List nodes = new ArrayList<>(); - - if (path.getRequiredLeafProperty().isQualified()) { - - if (path.getRequiredLeafProperty().isMap()) { - ((Map) value) - .forEach((k, v) -> nodes.add(new RelationalEntityInsertWriter.PathNode(path, parentNode, Pair.of(k, v)))); - } else { - - List listValue = (List) value; - for (int k = 0; k < listValue.size(); k++) { - nodes.add(new RelationalEntityInsertWriter.PathNode(path, parentNode, Pair.of(k, listValue.get(k)))); - } - } - } else if (path.getRequiredLeafProperty().isCollectionLike()) { // collection value - ((Collection) value).forEach(v -> nodes.add(new RelationalEntityInsertWriter.PathNode(path, parentNode, v))); - } else { // single entity value - nodes.add(new RelationalEntityInsertWriter.PathNode(path, parentNode, value)); - } - - return nodes; - } - - } - - /** - * Represents a single entity in an aggregate along with its property path from the root entity and the chain of - * objects to traverse a long this path. - */ - @Value - static class PathNode { - - /** - * The path to this entity - */ - PersistentPropertyPath path; - - /** - * The parent {@link PathNode}. This is {@code null} if this is the root entity. - */ - @Nullable - PathNode parent; - - /** - * The value of the entity. - */ - Object value; - } -} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/PathNode.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/PathNode.java new file mode 100644 index 000000000..93d8d8972 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/PathNode.java @@ -0,0 +1,47 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.relational.core.conversion; + +import lombok.Value; +import org.springframework.data.mapping.PersistentPropertyPath; +import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; +import org.springframework.lang.Nullable; + +/** + * Represents a single entity in an aggregate along with its property path from the root entity and the chain of + * objects to traverse a long this path. + * + * @author Jens Schauder + */ +@Value +class PathNode { + + /** + * The path to this entity + */ + PersistentPropertyPath path; + + /** + * The parent {@link PathNode}. This is {@code null} if this is the root entity. + */ + @Nullable + PathNode parent; + + /** + * The value of the entity. + */ + Object value; +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityInsertWriter.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityInsertWriter.java index a283db8a9..0efa2a116 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityInsertWriter.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityInsertWriter.java @@ -15,30 +15,34 @@ */ package org.springframework.data.relational.core.conversion; -import org.springframework.data.relational.core.mapping.RelationalMappingContext; - import java.util.List; +import org.springframework.data.convert.EntityWriter; +import org.springframework.data.relational.core.mapping.RelationalMappingContext; + /** - * Converts an aggregate represented by its root into an {@link AggregateChange}. - * Does not perform any isNew check. + * Converts an aggregate represented by its root into an {@link AggregateChange}. Does not perform any isNew check. * - * @author Jens Schauder - * @author Mark Paluch * @author Thomas Lang + * @author Jens Schauder + * @since 1.1 */ -public class RelationalEntityInsertWriter extends AbstractRelationalEntityWriter { +public class RelationalEntityInsertWriter implements EntityWriter> { + + private final RelationalMappingContext context; public RelationalEntityInsertWriter(RelationalMappingContext context) { - super(context); + this.context = context; } /* * (non-Javadoc) * @see org.springframework.data.convert.EntityWriter#save(java.lang.Object, java.lang.Object) */ - @Override public void write(Object root, AggregateChange aggregateChange) { - List> actions = new WritingContext(root, aggregateChange).insert(); + @Override + public void write(Object root, AggregateChange aggregateChange) { + + List> actions = new WritingContext(context, root, aggregateChange).insert(); actions.forEach(aggregateChange::addAction); } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityUpdateWriter.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityUpdateWriter.java index 51043a1a9..7e31d76ba 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityUpdateWriter.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityUpdateWriter.java @@ -15,30 +15,34 @@ */ package org.springframework.data.relational.core.conversion; -import org.springframework.data.relational.core.mapping.RelationalMappingContext; - import java.util.List; +import org.springframework.data.convert.EntityWriter; +import org.springframework.data.relational.core.mapping.RelationalMappingContext; + /** - * Converts an aggregate represented by its root into an {@link AggregateChange}. - * Does not perform any isNew check. + * Converts an aggregate represented by its root into an {@link AggregateChange}. Does not perform any isNew check. * - * @author Jens Schauder - * @author Mark Paluch * @author Thomas Lang + * @author Jens Schauder + * @since 1.1 */ -public class RelationalEntityUpdateWriter extends AbstractRelationalEntityWriter { +public class RelationalEntityUpdateWriter implements EntityWriter> { + + private final RelationalMappingContext context; public RelationalEntityUpdateWriter(RelationalMappingContext context) { - super(context); + this.context = context; } /* * (non-Javadoc) * @see org.springframework.data.convert.EntityWriter#save(java.lang.Object, java.lang.Object) */ - @Override public void write(Object root, AggregateChange aggregateChange) { - List> actions = new WritingContext(root, aggregateChange).update(); + @Override + public void write(Object root, AggregateChange aggregateChange) { + + List> actions = new WritingContext(context, root, aggregateChange).update(); actions.forEach(aggregateChange::addAction); } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityWriter.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityWriter.java index 0250295b9..6f5efe555 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityWriter.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityWriter.java @@ -15,28 +15,33 @@ */ package org.springframework.data.relational.core.conversion; -import org.springframework.data.relational.core.mapping.RelationalMappingContext; - import java.util.List; +import org.springframework.data.convert.EntityWriter; +import org.springframework.data.relational.core.mapping.RelationalMappingContext; + /** * Converts an aggregate represented by its root into an {@link AggregateChange}. * * @author Jens Schauder * @author Mark Paluch */ -public class RelationalEntityWriter extends AbstractRelationalEntityWriter { +public class RelationalEntityWriter implements EntityWriter> { + + private final RelationalMappingContext context; public RelationalEntityWriter(RelationalMappingContext context) { - super(context); + this.context = context; } /* * (non-Javadoc) * @see org.springframework.data.convert.EntityWriter#save(java.lang.Object, java.lang.Object) */ - @Override public void write(Object root, AggregateChange aggregateChange) { - List> actions = new WritingContext(root, aggregateChange).save(); + @Override + public void write(Object root, AggregateChange aggregateChange) { + + List> actions = new WritingContext(context, root, aggregateChange).save(); actions.forEach(aggregateChange::addAction); } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/WritingContext.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/WritingContext.java new file mode 100644 index 000000000..f57db61ba --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/WritingContext.java @@ -0,0 +1,252 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.relational.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; +import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; +import org.springframework.data.util.Pair; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Holds context information for the current save operation. + * + * @author Jens Schauder + */ +class WritingContext { + + private final RelationalMappingContext context; + private final Object root; + private final Object entity; + private final Class entityType; + private final PersistentPropertyPaths paths; + private final Map previousActions = new HashMap<>(); + private Map, List> nodesCache = new HashMap<>(); + + WritingContext(RelationalMappingContext context, Object root, AggregateChange aggregateChange) { + + this.context = context; + this.root = root; + this.entity = aggregateChange.getEntity(); + this.entityType = aggregateChange.getEntityType(); + this.paths = context.findPersistentPropertyPaths(entityType, PersistentProperty::isEntity); + } + + /** + * Leaves out the isNew check as defined in #DATAJDBC-282 + * + * @return List of {@link DbAction}s + * @see DAJDBC-282 + */ + List> insert() { + + List> actions = new ArrayList<>(); + actions.add(setRootAction(new DbAction.InsertRoot<>(entity))); + actions.addAll(insertReferenced()); + return actions; + } + + /** + * Leaves out the isNew check as defined in #DATAJDBC-282 + * + * @return List of {@link DbAction}s + * @see DAJDBC-282 + */ + List> update() { + + List> actions = new ArrayList<>(deleteReferenced()); + actions.add(setRootAction(new DbAction.UpdateRoot<>(entity))); + actions.addAll(insertReferenced()); + return actions; + } + + List> save() { + + List> actions = new ArrayList<>(); + if (isNew(root)) { + + actions.add(setRootAction(new DbAction.InsertRoot<>(entity))); + actions.addAll(insertReferenced()); + } else { + + actions.addAll(deleteReferenced()); + actions.add(setRootAction(new DbAction.UpdateRoot<>(entity))); + actions.addAll(insertReferenced()); + } + + return actions; + } + + private boolean isNew(Object o) { + return context.getRequiredPersistentEntity(o.getClass()).isNew(o); + } + + //// Operations on all paths + + private List> insertReferenced() { + + List> actions = new ArrayList<>(); + + paths.forEach(path -> actions.addAll(insertAll(path))); + + return actions; + } + + private List> insertAll(PersistentPropertyPath path) { + + List> actions = new ArrayList<>(); + + from(path).forEach(node -> { + + DbAction.Insert insert; + if (node.getPath().getRequiredLeafProperty().isQualified()) { + + Pair value = (Pair) node.getValue(); + insert = new DbAction.Insert<>(value.getSecond(), path, getAction(node.getParent())); + insert.getAdditionalValues().put(node.getPath().getRequiredLeafProperty().getKeyColumn(), value.getFirst()); + + } else { + insert = new DbAction.Insert<>(node.getValue(), path, getAction(node.getParent())); + } + + previousActions.put(node, insert); + actions.add(insert); + }); + + return actions; + } + + private List> deleteReferenced() { + + List> deletes = new ArrayList<>(); + paths.forEach(path -> deletes.add(0, deleteReferenced(path))); + + return deletes; + } + + /// Operations on a single path + + private DbAction.Delete deleteReferenced(PersistentPropertyPath path) { + + Object id = context.getRequiredPersistentEntity(entityType).getIdentifierAccessor(entity).getIdentifier(); + + return new DbAction.Delete<>(id, path); + } + + //// methods not directly related to the creation of DbActions + + private DbAction setRootAction(DbAction dbAction) { + + previousActions.put(null, dbAction); + return dbAction; + } + + @Nullable + private DbAction.WithEntity getAction(@Nullable PathNode parent) { + + DbAction action = previousActions.get(parent); + + if (action != null) { + + Assert.isInstanceOf( // + DbAction.WithEntity.class, // + action, // + "dependsOn action is not a WithEntity, but " + action.getClass().getSimpleName() // + ); + + return (DbAction.WithEntity) action; + } + + return null; + } + // commented as of #DATAJDBC-282 + // private boolean isNew(Object o) { + // return context.getRequiredPersistentEntity(o.getClass()).isNew(o); + // } + + private List from( + PersistentPropertyPath path) { + + List nodes = new ArrayList<>(); + + if (path.getLength() == 1) { + + Object value = context // + .getRequiredPersistentEntity(entityType) // + .getPropertyAccessor(entity) // + .getProperty(path.getRequiredLeafProperty()); + + nodes.addAll(createNodes(path, null, value)); + + } else { + + List pathNodes = nodesCache.get(path.getParentPath()); + pathNodes.forEach(parentNode -> { + + Object value = path.getRequiredLeafProperty().getOwner().getPropertyAccessor(parentNode.getValue()) + .getProperty(path.getRequiredLeafProperty()); + + nodes.addAll(createNodes(path, parentNode, value)); + }); + } + + nodesCache.put(path, nodes); + + return nodes; + } + + private List createNodes( + PersistentPropertyPath path, + @Nullable PathNode parentNode, @Nullable Object value) { + + if (value == null) { + return Collections.emptyList(); + } + + List nodes = new ArrayList<>(); + + if (path.getRequiredLeafProperty().isQualified()) { + + if (path.getRequiredLeafProperty().isMap()) { + ((Map) value) + .forEach((k, v) -> nodes.add(new PathNode(path, parentNode, Pair.of(k, v)))); + } else { + + List listValue = (List) value; + for (int k = 0; k < listValue.size(); k++) { + nodes.add(new PathNode(path, parentNode, Pair.of(k, listValue.get(k)))); + } + } + } else if (path.getRequiredLeafProperty().isCollectionLike()) { // collection value + ((Collection) value).forEach(v -> nodes.add(new PathNode(path, parentNode, v))); + } else { // single entity value + nodes.add(new PathNode(path, parentNode, value)); + } + + return nodes; + } + +} diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/DbActionTestSupport.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/DbActionTestSupport.java new file mode 100644 index 000000000..4a3f466d7 --- /dev/null +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/DbActionTestSupport.java @@ -0,0 +1,47 @@ +/* + * Copyright 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.relational.core.conversion; + +import lombok.experimental.UtilityClass; + +/** + * Utility class for analyzing DbActions in tests. + * @author Jens Schauder + */ +@UtilityClass +class DbActionTestSupport { + + static String extractPath(DbAction action) { + + if (action instanceof DbAction.WithPropertyPath) { + return ((DbAction.WithPropertyPath) action).getPropertyPath().toDotPath(); + } + + return ""; + } + + static boolean isWithDependsOn(DbAction dbAction) { + return dbAction instanceof DbAction.WithDependingOn; + } + + static Class actualEntityType(DbAction a) { + + if (a instanceof DbAction.WithEntity) { + return ((DbAction.WithEntity) a).getEntity().getClass(); + } + return null; + } +} diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityDeleteWriterUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityDeleteWriterUnitTests.java index 5a52ea3a9..354d867f8 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityDeleteWriterUnitTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityDeleteWriterUnitTests.java @@ -23,7 +23,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; import org.springframework.data.annotation.Id; -import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.relational.core.conversion.AggregateChange.Kind; import org.springframework.data.relational.core.conversion.DbAction.Delete; import org.springframework.data.relational.core.conversion.DbAction.DeleteAll; @@ -41,15 +40,6 @@ public class RelationalEntityDeleteWriterUnitTests { RelationalEntityDeleteWriter converter = new RelationalEntityDeleteWriter(new RelationalMappingContext()); - private static Object dotPath(DbAction dba) { - if (dba instanceof DbAction.WithPropertyPath) { - PersistentPropertyPath propertyPath = ((DbAction.WithPropertyPath) dba).getPropertyPath(); - return propertyPath == null ? null : propertyPath.toDotPath(); - } else { - return null; - } - } - @Test // DATAJDBC-112 public void deleteDeletesTheEntityAndReferencedEntities() { @@ -60,11 +50,11 @@ public class RelationalEntityDeleteWriterUnitTests { converter.write(entity.id, aggregateChange); Assertions.assertThat(aggregateChange.getActions()) - .extracting(DbAction::getClass, DbAction::getEntityType, RelationalEntityDeleteWriterUnitTests::dotPath) // + .extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath) // .containsExactly( // Tuple.tuple(Delete.class, YetAnother.class, "other.yetAnother"), // Tuple.tuple(Delete.class, OtherEntity.class, "other"), // - Tuple.tuple(DeleteRoot.class, SomeEntity.class, null) // + Tuple.tuple(DeleteRoot.class, SomeEntity.class, "") // ); } @@ -76,11 +66,11 @@ public class RelationalEntityDeleteWriterUnitTests { converter.write(null, aggregateChange); Assertions.assertThat(aggregateChange.getActions()) - .extracting(DbAction::getClass, DbAction::getEntityType, RelationalEntityDeleteWriterUnitTests::dotPath) // + .extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath) // .containsExactly( // Tuple.tuple(DeleteAll.class, YetAnother.class, "other.yetAnother"), // Tuple.tuple(DeleteAll.class, OtherEntity.class, "other"), // - Tuple.tuple(DeleteAllRoot.class, SomeEntity.class, null) // + Tuple.tuple(DeleteAllRoot.class, SomeEntity.class, "") // ); } diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityInsertWriterUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityInsertWriterUnitTests.java index b78481727..71f2f4a80 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityInsertWriterUnitTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityInsertWriterUnitTests.java @@ -15,7 +15,10 @@ */ package org.springframework.data.relational.core.conversion; +import static org.assertj.core.api.Assertions.*; + import lombok.RequiredArgsConstructor; + import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; @@ -24,15 +27,13 @@ import org.springframework.data.relational.core.conversion.AggregateChange.Kind; import org.springframework.data.relational.core.conversion.DbAction.InsertRoot; import org.springframework.data.relational.core.mapping.RelationalMappingContext; -import static org.assertj.core.api.Assertions.*; - /** * Unit tests for the {@link RelationalEntityInsertWriter} * - * @author Jens Schauder * @author Thomas Lang */ -@RunWith(MockitoJUnitRunner.class) public class RelationalEntityInsertWriterUnitTests { +@RunWith(MockitoJUnitRunner.class) +public class RelationalEntityInsertWriterUnitTests { public static final long SOME_ENTITY_ID = 23L; RelationalEntityInsertWriter converter = new RelationalEntityInsertWriter(new RelationalMappingContext()); @@ -47,8 +48,8 @@ import static org.assertj.core.api.Assertions.*; converter.write(entity, aggregateChange); assertThat(aggregateChange.getActions()) // - .extracting(DbAction::getClass, DbAction::getEntityType, this::extractPath, this::actualEntityType, - this::isWithDependsOn) // + .extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath, + DbActionTestSupport::actualEntityType, DbActionTestSupport::isWithDependsOn) // .containsExactly( // tuple(InsertRoot.class, SingleReferenceEntity.class, "", SingleReferenceEntity.class, false) // ); @@ -65,39 +66,16 @@ import static org.assertj.core.api.Assertions.*; converter.write(entity, aggregateChange); assertThat(aggregateChange.getActions()) // - .extracting(DbAction::getClass, DbAction::getEntityType, this::extractPath, this::actualEntityType, - this::isWithDependsOn) // + .extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath, + DbActionTestSupport::actualEntityType, DbActionTestSupport::isWithDependsOn) // .containsExactly( // tuple(InsertRoot.class, SingleReferenceEntity.class, "", SingleReferenceEntity.class, false) // ); - assertThat(aggregateChange.getEntity()).isNotNull(); - // the new id should not be the same as the origin one - should do insert, not update - // assertThat(aggregateChange.getEntity().id).isNotEqualTo(SOME_ENTITY_ID); - } - - private String extractPath(DbAction action) { - - if (action instanceof DbAction.WithPropertyPath) { - return ((DbAction.WithPropertyPath) action).getPropertyPath().toDotPath(); - } - - return ""; - } - - private boolean isWithDependsOn(DbAction dbAction) { - return dbAction instanceof DbAction.WithDependingOn; - } - - private Class actualEntityType(DbAction a) { - - if (a instanceof DbAction.WithEntity) { - return ((DbAction.WithEntity) a).getEntity().getClass(); - } - return null; } - @RequiredArgsConstructor static class SingleReferenceEntity { + @RequiredArgsConstructor + static class SingleReferenceEntity { @Id final Long id; Element other; @@ -105,7 +83,8 @@ import static org.assertj.core.api.Assertions.*; String name; } - @RequiredArgsConstructor private static class Element { + @RequiredArgsConstructor + private static class Element { @Id final Long id; } } diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityUpdateWriterUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityUpdateWriterUnitTests.java index 6d9a711cb..d33bb1f89 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityUpdateWriterUnitTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityUpdateWriterUnitTests.java @@ -15,7 +15,10 @@ */ package org.springframework.data.relational.core.conversion; +import static org.assertj.core.api.Assertions.*; + import lombok.RequiredArgsConstructor; + import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; @@ -23,74 +26,48 @@ import org.springframework.data.annotation.Id; import org.springframework.data.relational.core.conversion.AggregateChange.Kind; import org.springframework.data.relational.core.mapping.RelationalMappingContext; -import static org.assertj.core.api.Assertions.*; - /** * Unit tests for the {@link RelationalEntityUpdateWriter} * - * @author Jens Schauder * @author Thomas Lang */ @RunWith(MockitoJUnitRunner.class) public class RelationalEntityUpdateWriterUnitTests { - public static final long SOME_ENTITY_ID = 23L; - RelationalEntityUpdateWriter converter = new RelationalEntityUpdateWriter(new RelationalMappingContext()); - - @Test // DATAJDBC-112 - public void existingEntityGetsConvertedToDeletePlusUpdate() { - - SingleReferenceEntity entity = new SingleReferenceEntity(SOME_ENTITY_ID); - - AggregateChange aggregateChange = // - new AggregateChange(Kind.SAVE, SingleReferenceEntity.class, entity); - - converter.write(entity, aggregateChange); - - assertThat(aggregateChange.getActions()) // - .extracting(DbAction::getClass, DbAction::getEntityType, this::extractPath, this::actualEntityType, - this::isWithDependsOn) // - .containsExactly( // - tuple(DbAction.Delete.class, Element.class, "other", null, false), // - tuple(DbAction.UpdateRoot.class, SingleReferenceEntity.class, "", SingleReferenceEntity.class, false) // - ); - } - - private String extractPath(DbAction action) { + public static final long SOME_ENTITY_ID = 23L; + RelationalEntityUpdateWriter converter = new RelationalEntityUpdateWriter(new RelationalMappingContext()); - if (action instanceof DbAction.WithPropertyPath) { - return ((DbAction.WithPropertyPath) action).getPropertyPath().toDotPath(); - } + @Test // DATAJDBC-112 + public void existingEntityGetsConvertedToDeletePlusUpdate() { - return ""; - } + SingleReferenceEntity entity = new SingleReferenceEntity(SOME_ENTITY_ID); - private boolean isWithDependsOn(DbAction dbAction) { - return dbAction instanceof DbAction.WithDependingOn; - } + AggregateChange aggregateChange = // + new AggregateChange(Kind.SAVE, SingleReferenceEntity.class, entity); - private Class actualEntityType(DbAction a) { + converter.write(entity, aggregateChange); - if (a instanceof DbAction.WithEntity) { - return ((DbAction.WithEntity) a).getEntity().getClass(); - } - return null; - } + assertThat(aggregateChange.getActions()) // + .extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath, DbActionTestSupport::actualEntityType, + DbActionTestSupport::isWithDependsOn) // + .containsExactly( // + tuple(DbAction.Delete.class, Element.class, "other", null, false), // + tuple(DbAction.UpdateRoot.class, SingleReferenceEntity.class, "", SingleReferenceEntity.class, false) // + ); + } - @RequiredArgsConstructor - static class SingleReferenceEntity { + @RequiredArgsConstructor + static class SingleReferenceEntity { - @Id - final Long id; - Element other; - // should not trigger own Dbaction - String name; - } + @Id final Long id; + Element other; + // should not trigger own Dbaction + String name; + } - @RequiredArgsConstructor - private static class Element { - @Id - final Long id; - } + @RequiredArgsConstructor + private static class Element { + @Id final Long id; + } } diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityWriterUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityWriterUnitTests.java index 58dee47bc..0373619dd 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityWriterUnitTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityWriterUnitTests.java @@ -53,13 +53,13 @@ public class RelationalEntityWriterUnitTests { SingleReferenceEntity entity = new SingleReferenceEntity(null); AggregateChange aggregateChange = // - new AggregateChange(Kind.SAVE, SingleReferenceEntity.class, entity); + new AggregateChange<>(Kind.SAVE, SingleReferenceEntity.class, entity); converter.write(entity, aggregateChange); assertThat(aggregateChange.getActions()) // - .extracting(DbAction::getClass, DbAction::getEntityType, this::extractPath, this::actualEntityType, - this::isWithDependsOn) // + .extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath, DbActionTestSupport::actualEntityType, + DbActionTestSupport::isWithDependsOn) // .containsExactly( // tuple(InsertRoot.class, SingleReferenceEntity.class, "", SingleReferenceEntity.class, false) // ); @@ -72,13 +72,13 @@ public class RelationalEntityWriterUnitTests { entity.other = new Element(null); AggregateChange aggregateChange = // - new AggregateChange(Kind.SAVE, SingleReferenceEntity.class, entity); + new AggregateChange<>(Kind.SAVE, SingleReferenceEntity.class, entity); converter.write(entity, aggregateChange); assertThat(aggregateChange.getActions()) // - .extracting(DbAction::getClass, DbAction::getEntityType, this::extractPath, this::actualEntityType, - this::isWithDependsOn) // + .extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath, DbActionTestSupport::actualEntityType, + DbActionTestSupport::isWithDependsOn) // .containsExactly( // tuple(InsertRoot.class, SingleReferenceEntity.class, "", SingleReferenceEntity.class, false), // tuple(Insert.class, Element.class, "other", Element.class, true) // @@ -91,13 +91,13 @@ public class RelationalEntityWriterUnitTests { SingleReferenceEntity entity = new SingleReferenceEntity(SOME_ENTITY_ID); AggregateChange aggregateChange = // - new AggregateChange(Kind.SAVE, SingleReferenceEntity.class, entity); + new AggregateChange<>(Kind.SAVE, SingleReferenceEntity.class, entity); converter.write(entity, aggregateChange); assertThat(aggregateChange.getActions()) // - .extracting(DbAction::getClass, DbAction::getEntityType, this::extractPath, this::actualEntityType, - this::isWithDependsOn) // + .extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath, DbActionTestSupport::actualEntityType, + DbActionTestSupport::isWithDependsOn) // .containsExactly( // tuple(Delete.class, Element.class, "other", null, false), // tuple(UpdateRoot.class, SingleReferenceEntity.class, "", SingleReferenceEntity.class, false) // @@ -110,14 +110,14 @@ public class RelationalEntityWriterUnitTests { SingleReferenceEntity entity = new SingleReferenceEntity(SOME_ENTITY_ID); entity.other = new Element(null); - AggregateChange aggregateChange = new AggregateChange(Kind.SAVE, SingleReferenceEntity.class, + AggregateChange aggregateChange = new AggregateChange<>(Kind.SAVE, SingleReferenceEntity.class, entity); converter.write(entity, aggregateChange); assertThat(aggregateChange.getActions()) // - .extracting(DbAction::getClass, DbAction::getEntityType, this::extractPath, this::actualEntityType, - this::isWithDependsOn) // + .extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath, DbActionTestSupport::actualEntityType, + DbActionTestSupport::isWithDependsOn) // .containsExactly( // tuple(Delete.class, Element.class, "other", null, false), // tuple(UpdateRoot.class, SingleReferenceEntity.class, "", SingleReferenceEntity.class, false), // @@ -129,14 +129,14 @@ public class RelationalEntityWriterUnitTests { public void newEntityWithEmptySetResultsInSingleInsert() { SetContainer entity = new SetContainer(null); - AggregateChange aggregateChange = new AggregateChange( + AggregateChange aggregateChange = new AggregateChange<>( Kind.SAVE, SetContainer.class, entity); converter.write(entity, aggregateChange); assertThat(aggregateChange.getActions()) // - .extracting(DbAction::getClass, DbAction::getEntityType, this::extractPath, this::actualEntityType, - this::isWithDependsOn) // + .extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath, DbActionTestSupport::actualEntityType, + DbActionTestSupport::isWithDependsOn) // .containsExactly( // tuple(InsertRoot.class, SetContainer.class, "", SetContainer.class, false)); } @@ -148,12 +148,12 @@ public class RelationalEntityWriterUnitTests { entity.elements.add(new Element(null)); entity.elements.add(new Element(null)); - AggregateChange aggregateChange = new AggregateChange(Kind.SAVE, SetContainer.class, entity); + AggregateChange aggregateChange = new AggregateChange<>(Kind.SAVE, SetContainer.class, entity); converter.write(entity, aggregateChange); assertThat(aggregateChange.getActions()) - .extracting(DbAction::getClass, DbAction::getEntityType, this::extractPath, this::actualEntityType, - this::isWithDependsOn) // + .extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath, DbActionTestSupport::actualEntityType, + DbActionTestSupport::isWithDependsOn) // .containsExactly( // tuple(InsertRoot.class, SetContainer.class, "", SetContainer.class, false), // tuple(Insert.class, Element.class, "elements", Element.class, true), // @@ -176,14 +176,14 @@ public class RelationalEntityWriterUnitTests { new Element(null)) // ); - AggregateChange aggregateChange = new AggregateChange(Kind.SAVE, + AggregateChange aggregateChange = new AggregateChange<>(Kind.SAVE, CascadingReferenceEntity.class, entity); converter.write(entity, aggregateChange); assertThat(aggregateChange.getActions()) - .extracting(DbAction::getClass, DbAction::getEntityType, this::extractPath, this::actualEntityType, - this::isWithDependsOn) // + .extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath, DbActionTestSupport::actualEntityType, + DbActionTestSupport::isWithDependsOn) // .containsExactly( // tuple(InsertRoot.class, CascadingReferenceEntity.class, "", CascadingReferenceEntity.class, false), // tuple(Insert.class, CascadingReferenceMiddleElement.class, "other", CascadingReferenceMiddleElement.class, @@ -212,14 +212,14 @@ public class RelationalEntityWriterUnitTests { new Element(null)) // ); - AggregateChange aggregateChange = new AggregateChange(Kind.SAVE, + AggregateChange aggregateChange = new AggregateChange<>(Kind.SAVE, CascadingReferenceEntity.class, entity); converter.write(entity, aggregateChange); assertThat(aggregateChange.getActions()) - .extracting(DbAction::getClass, DbAction::getEntityType, this::extractPath, this::actualEntityType, - this::isWithDependsOn) // + .extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath, DbActionTestSupport::actualEntityType, + DbActionTestSupport::isWithDependsOn) // .containsExactly( // tuple(Delete.class, Element.class, "other.element", null, false), tuple(Delete.class, CascadingReferenceMiddleElement.class, "other", null, false), @@ -239,11 +239,11 @@ public class RelationalEntityWriterUnitTests { public void newEntityWithEmptyMapResultsInSingleInsert() { MapContainer entity = new MapContainer(null); - AggregateChange aggregateChange = new AggregateChange(Kind.SAVE, MapContainer.class, entity); + AggregateChange aggregateChange = new AggregateChange<>(Kind.SAVE, MapContainer.class, entity); converter.write(entity, aggregateChange); - assertThat(aggregateChange.getActions()).extracting(DbAction::getClass, DbAction::getEntityType, this::extractPath) // + assertThat(aggregateChange.getActions()).extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath) // .containsExactly( // tuple(InsertRoot.class, MapContainer.class, "")); } @@ -255,11 +255,11 @@ public class RelationalEntityWriterUnitTests { entity.elements.put("one", new Element(null)); entity.elements.put("two", new Element(null)); - AggregateChange aggregateChange = new AggregateChange(Kind.SAVE, MapContainer.class, entity); + AggregateChange aggregateChange = new AggregateChange<>(Kind.SAVE, MapContainer.class, entity); converter.write(entity, aggregateChange); assertThat(aggregateChange.getActions()) - .extracting(DbAction::getClass, DbAction::getEntityType, this::getMapKey, this::extractPath) // + .extracting(DbAction::getClass, DbAction::getEntityType, this::getMapKey, DbActionTestSupport::extractPath) // .containsExactlyInAnyOrder( // tuple(InsertRoot.class, MapContainer.class, null, ""), // tuple(Insert.class, Element.class, "one", "elements"), // @@ -290,11 +290,11 @@ public class RelationalEntityWriterUnitTests { entity.elements.put("a", new Element(null)); entity.elements.put("b", new Element(null)); - AggregateChange aggregateChange = new AggregateChange(Kind.SAVE, MapContainer.class, entity); + AggregateChange aggregateChange = new AggregateChange<>(Kind.SAVE, MapContainer.class, entity); converter.write(entity, aggregateChange); assertThat(aggregateChange.getActions()) - .extracting(DbAction::getClass, DbAction::getEntityType, this::getMapKey, this::extractPath) // + .extracting(DbAction::getClass, DbAction::getEntityType, this::getMapKey, DbActionTestSupport::extractPath) // .containsExactlyInAnyOrder( // tuple(InsertRoot.class, MapContainer.class, null, ""), // tuple(Insert.class, Element.class, "1", "elements"), // @@ -316,11 +316,11 @@ public class RelationalEntityWriterUnitTests { public void newEntityWithEmptyListResultsInSingleInsert() { ListContainer entity = new ListContainer(null); - AggregateChange aggregateChange = new AggregateChange(Kind.SAVE, ListContainer.class, entity); + AggregateChange aggregateChange = new AggregateChange<>(Kind.SAVE, ListContainer.class, entity); converter.write(entity, aggregateChange); - assertThat(aggregateChange.getActions()).extracting(DbAction::getClass, DbAction::getEntityType, this::extractPath) // + assertThat(aggregateChange.getActions()).extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::extractPath) // .containsExactly( // tuple(InsertRoot.class, ListContainer.class, "")); } @@ -332,11 +332,11 @@ public class RelationalEntityWriterUnitTests { entity.elements.add(new Element(null)); entity.elements.add(new Element(null)); - AggregateChange aggregateChange = new AggregateChange(Kind.SAVE, ListContainer.class, entity); + AggregateChange aggregateChange = new AggregateChange<>(Kind.SAVE, ListContainer.class, entity); converter.write(entity, aggregateChange); assertThat(aggregateChange.getActions()) - .extracting(DbAction::getClass, DbAction::getEntityType, this::getListKey, this::extractPath) // + .extracting(DbAction::getClass, DbAction::getEntityType, this::getListKey, DbActionTestSupport::extractPath) // .containsExactlyInAnyOrder( // tuple(InsertRoot.class, ListContainer.class, null, ""), // tuple(Insert.class, Element.class, 0, "elements"), // @@ -356,12 +356,12 @@ public class RelationalEntityWriterUnitTests { MapContainer entity = new MapContainer(SOME_ENTITY_ID); entity.elements.put("one", new Element(null)); - AggregateChange aggregateChange = new AggregateChange(Kind.SAVE, MapContainer.class, entity); + AggregateChange aggregateChange = new AggregateChange<>(Kind.SAVE, MapContainer.class, entity); converter.write(entity, aggregateChange); assertThat(aggregateChange.getActions()) // - .extracting(DbAction::getClass, DbAction::getEntityType, this::getMapKey, this::extractPath) // + .extracting(DbAction::getClass, DbAction::getEntityType, this::getMapKey, DbActionTestSupport::extractPath) // .containsExactly( // tuple(Delete.class, Element.class, null, "elements"), // tuple(UpdateRoot.class, MapContainer.class, null, ""), // @@ -375,12 +375,12 @@ public class RelationalEntityWriterUnitTests { ListContainer entity = new ListContainer(SOME_ENTITY_ID); entity.elements.add(new Element(null)); - AggregateChange aggregateChange = new AggregateChange(Kind.SAVE, ListContainer.class, entity); + AggregateChange aggregateChange = new AggregateChange<>(Kind.SAVE, ListContainer.class, entity); converter.write(entity, aggregateChange); assertThat(aggregateChange.getActions()) // - .extracting(DbAction::getClass, DbAction::getEntityType, this::getListKey, this::extractPath) // + .extracting(DbAction::getClass, DbAction::getEntityType, this::getListKey, DbActionTestSupport::extractPath) // .containsExactly( // tuple(Delete.class, Element.class, null, "elements"), // tuple(UpdateRoot.class, ListContainer.class, null, ""), // @@ -408,27 +408,6 @@ public class RelationalEntityWriterUnitTests { : null; } - private String extractPath(DbAction action) { - - if (action instanceof DbAction.WithPropertyPath) { - return ((DbAction.WithPropertyPath) action).getPropertyPath().toDotPath(); - } - - return ""; - } - - private boolean isWithDependsOn(DbAction dbAction) { - return dbAction instanceof DbAction.WithDependingOn; - } - - private Class actualEntityType(DbAction a) { - - if (a instanceof DbAction.WithEntity) { - return ((DbAction.WithEntity) a).getEntity().getClass(); - } - return null; - } - @RequiredArgsConstructor static class SingleReferenceEntity {