From 257dd7d5d49890c6e418cce74e85c4d0f212845d Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Tue, 16 Jul 2019 11:13:25 +0200 Subject: [PATCH] DATAJDBC-393 - Polishing. Accept AggregateChange in BeforeSave and BeforeDelete callbacks. Add Javadoc. Fix generics usage in RelationalAuditingCallback. Align documentation for consistent entity callback documentation. Add overloaded JdbcAggregateTemplate constructor to directly configure EntityCallbacks. Original pull request: #161. --- .../data/jdbc/core/JdbcAggregateTemplate.java | 78 +++++++++++-------- .../config/AbstractJdbcConfiguration.java | 17 ++-- .../config/EnableJdbcRepositories.java | 10 +-- .../support/JdbcRepositoryFactory.java | 8 +- ...JdbcAggregateTemplateIntegrationTests.java | 4 +- .../core/JdbcAggregateTemplateUnitTests.java | 12 ++- .../mapping/event/AfterDeleteCallback.java | 22 ++++-- .../core/mapping/event/AfterLoadCallback.java | 16 +++- .../core/mapping/event/AfterSaveCallback.java | 14 +++- .../mapping/event/BeforeConvertCallback.java | 18 ++++- .../mapping/event/BeforeDeleteCallback.java | 20 ++++- .../mapping/event/BeforeSaveCallback.java | 21 ++++- .../support/RelationalAuditingCallback.java | 9 ++- .../RelationalAuditingEventListener.java | 2 +- src/main/asciidoc/index.adoc | 2 +- src/main/asciidoc/jdbc.adoc | 50 +++++++++--- 16 files changed, 208 insertions(+), 95 deletions(-) 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 5246a4cb6..8e0f8e418 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 @@ -20,10 +20,10 @@ import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.StreamSupport; +import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.jdbc.core.convert.DataAccessStrategy; import org.springframework.data.mapping.IdentifierAccessor; -import org.springframework.data.mapping.callback.EntityCallback; import org.springframework.data.mapping.callback.EntityCallbacks; import org.springframework.data.relational.core.conversion.AggregateChange; import org.springframework.data.relational.core.conversion.AggregateChange.Kind; @@ -62,7 +62,38 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations { private final DataAccessStrategy accessStrategy; - private EntityCallbacks entityCallbacks = NoopEntityCallback.INSTANCE; + private EntityCallbacks entityCallbacks = EntityCallbacks.create(); + + /** + * Creates a new {@link JdbcAggregateTemplate} given {@link ApplicationContext}, {@link RelationalMappingContext} and + * {@link DataAccessStrategy}. + * + * @param publisher must not be {@literal null}. + * @param context must not be {@literal null}. + * @param dataAccessStrategy must not be {@literal null}. + * @since 1.1 + */ + public JdbcAggregateTemplate(ApplicationContext publisher, RelationalMappingContext context, + RelationalConverter converter, DataAccessStrategy dataAccessStrategy) { + + Assert.notNull(publisher, "ApplicationContext must not be null!"); + Assert.notNull(context, "RelationalMappingContext must not be null!"); + Assert.notNull(converter, "RelationalConverter must not be null!"); + Assert.notNull(dataAccessStrategy, "DataAccessStrategy must not be null!"); + + this.publisher = publisher; + this.context = context; + this.converter = converter; + this.accessStrategy = dataAccessStrategy; + + this.jdbcEntityWriter = new RelationalEntityWriter(context); + this.jdbcEntityInsertWriter = new RelationalEntityInsertWriter(context); + this.jdbcEntityUpdateWriter = new RelationalEntityUpdateWriter(context); + this.jdbcEntityDeleteWriter = new RelationalEntityDeleteWriter(context); + this.interpreter = new DefaultJdbcInterpreter(context, accessStrategy); + + setEntityCallbacks(EntityCallbacks.create(publisher)); + } /** * Creates a new {@link JdbcAggregateTemplate} given {@link ApplicationEventPublisher}, @@ -92,6 +123,10 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations { this.interpreter = new DefaultJdbcInterpreter(context, accessStrategy); } + /** + * @param entityCallbacks + * @since 1.1 + */ public void setEntityCallbacks(EntityCallbacks entityCallbacks) { Assert.notNull(entityCallbacks, "Callbacks must not be null."); @@ -297,16 +332,6 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations { triggerAfterDelete(entity, id, change); } - @SuppressWarnings({ "unchecked", "rawtypes" }) - private AggregateChange createChange(T instance) { - - // context.getRequiredPersistentEntity(o.getClass()).isNew(o) - - AggregateChange aggregateChange = new AggregateChange(Kind.SAVE, instance.getClass(), instance); - jdbcEntityWriter.write(instance, aggregateChange); - return aggregateChange; - } - @SuppressWarnings({ "unchecked", "rawtypes" }) private AggregateChange createInsertChange(T instance) { @@ -351,9 +376,11 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations { private T triggerAfterLoad(Object id, T entity) { - publisher.publishEvent(new AfterLoadEvent(Identifier.of(id), entity)); + Specified identifier = Identifier.of(id); + + publisher.publishEvent(new AfterLoadEvent(identifier, entity)); - return entityCallbacks.callback(AfterLoadCallback.class, entity, Identifier.of(id)); + return entityCallbacks.callback(AfterLoadCallback.class, entity, identifier); } private T triggerBeforeConvert(T aggregateRoot, @Nullable Object id) { @@ -373,7 +400,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations { change // )); - return entityCallbacks.callback(BeforeSaveCallback.class, aggregateRoot, identifier); + return entityCallbacks.callback(BeforeSaveCallback.class, aggregateRoot, identifier, change); } private T triggerAfterSave(T aggregateRoot, Object id, AggregateChange change) { @@ -389,7 +416,6 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations { return entityCallbacks.callback(AfterSaveCallback.class, aggregateRoot, identifier); } - @Nullable private void triggerAfterDelete(@Nullable T aggregateRoot, Object id, AggregateChange change) { Specified identifier = Identifier.of(id); @@ -409,25 +435,9 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations { publisher.publishEvent(new BeforeDeleteEvent(identifier, Optional.ofNullable(aggregateRoot), change)); if (aggregateRoot != null) { - return entityCallbacks.callback(BeforeDeleteCallback.class, aggregateRoot, identifier); + return entityCallbacks.callback(BeforeDeleteCallback.class, aggregateRoot, identifier, change); } - return aggregateRoot; - } - /** - * An {@link EntityCallbacks} implementation doing nothing. - */ - private enum NoopEntityCallback implements EntityCallbacks { - - INSTANCE { - - @Override - public void addEntityCallback(EntityCallback callback) {} - - @Override - public T callback(Class callbackType, T entity, Object... args) { - return entity; - } - } + return null; } } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfiguration.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfiguration.java index 65dd4b594..dfa189be3 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfiguration.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfiguration.java @@ -17,8 +17,7 @@ package org.springframework.data.jdbc.repository.config; import java.util.Optional; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Lazy; @@ -75,7 +74,7 @@ public class AbstractJdbcConfiguration { * Creates a {@link RelationalConverter} using the configured {@link #jdbcMappingContext(Optional)}. Will get * {@link #jdbcCustomConversions()} applied. * - * @see #jdbcMappingContext(Optional) + * @see #jdbcMappingContext(Optional, JdbcCustomConversions) * @see #jdbcCustomConversions() * @return must not be {@literal null}. */ @@ -91,7 +90,7 @@ public class AbstractJdbcConfiguration { /** * Register custom {@link Converter}s in a {@link JdbcCustomConversions} object if required. These * {@link JdbcCustomConversions} will be registered with the - * {@link #jdbcConverter(RelationalMappingContext, NamedParameterJdbcOperations, ObjectProvider, Optional, JdbcConverter)}. + * {@link #jdbcConverter(RelationalMappingContext, NamedParameterJdbcOperations, RelationResolver, JdbcCustomConversions)}. * Returns an empty {@link JdbcCustomConversions} instance by default. * * @return will never be {@literal null}. @@ -105,16 +104,16 @@ public class AbstractJdbcConfiguration { * Register a {@link JdbcAggregateTemplate} as a bean for easy use in applications that need a lower level of * abstraction than the normal repository abstraction. * - * @param publisher for publishing events. Must not be {@literal null}. - * @param context the mapping context to be used. Must not be {@literal null}. + * @param applicationContext for publishing events. Must not be {@literal null}. + * @param mappingContext the mapping context to be used. Must not be {@literal null}. * @param converter the conversions used when reading and writing from/to the database. Must not be {@literal null}. * @return a {@link JdbcAggregateTemplate}. Will never be {@literal null}. */ @Bean - public JdbcAggregateTemplate jdbcAggregateTemplate(ApplicationEventPublisher publisher, - RelationalMappingContext context, JdbcConverter converter, DataAccessStrategy dataAccessStrategy) { + public JdbcAggregateTemplate jdbcAggregateTemplate(ApplicationContext applicationContext, + RelationalMappingContext mappingContext, JdbcConverter converter, DataAccessStrategy dataAccessStrategy) { - return new JdbcAggregateTemplate(publisher, context, converter, dataAccessStrategy); + return new JdbcAggregateTemplate(applicationContext, mappingContext, converter, dataAccessStrategy); } /** diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositories.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositories.java index 4682f9b92..322721833 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositories.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositories.java @@ -101,15 +101,15 @@ public @interface EnableJdbcRepositories { String repositoryImplementationPostfix() default "Impl"; /** - * Configures the name of the {@link NamedParameterJdbcOperations} bean definition to be used to create repositories - * discovered through this annotation. Defaults to {@code namedParameterJdbcTemplate}. + * Configures the name of the {@link org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations} bean + * definition to be used to create repositories discovered through this annotation. Defaults to + * {@code namedParameterJdbcTemplate}. */ String jdbcOperationsRef() default ""; - /** - * Configures the name of the {@link DataAccessStrategy} bean definition to be used to create repositories - * discovered through this annotation. Defaults to {@code defaultDataAccessStrategy} if existed. + * Configures the name of the {@link org.springframework.data.jdbc.core.convert.DataAccessStrategy} bean definition to + * be used to create repositories discovered through this annotation. Defaults to {@code defaultDataAccessStrategy}. */ String dataAccessStrategyRef() default ""; } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java index c2a5e852c..dde646460 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java @@ -119,7 +119,8 @@ public class JdbcRepositoryFactory extends RepositoryFactorySupport { JdbcAggregateTemplate template = new JdbcAggregateTemplate(publisher, context, converter, accessStrategy); - SimpleJdbcRepository repository = new SimpleJdbcRepository<>(template, context.getPersistentEntity(repositoryInformation.getDomainType())); + SimpleJdbcRepository repository = new SimpleJdbcRepository<>(template, + context.getPersistentEntity(repositoryInformation.getDomainType())); if (entityCallbacks != null) { template.setEntityCallbacks(entityCallbacks); @@ -156,8 +157,11 @@ public class JdbcRepositoryFactory extends RepositoryFactorySupport { throw new IllegalArgumentException(String.format("Unsupported query lookup strategy %s!", key)); } + /** + * @param entityCallbacks + * @since 1.1 + */ public void setEntityCallbacks(EntityCallbacks entityCallbacks) { - this.entityCallbacks = entityCallbacks; } } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateIntegrationTests.java index 3786469a2..c388b4623 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateIntegrationTests.java @@ -44,7 +44,6 @@ import org.springframework.data.annotation.Id; import org.springframework.data.jdbc.core.convert.DataAccessStrategy; import org.springframework.data.jdbc.testing.DatabaseProfileValueSource; import org.springframework.data.jdbc.testing.TestConfiguration; -import org.springframework.data.mapping.callback.EntityCallbacks; import org.springframework.data.relational.core.conversion.RelationalConverter; import org.springframework.data.relational.core.mapping.Column; import org.springframework.data.relational.core.mapping.RelationalMappingContext; @@ -856,7 +855,6 @@ public class JdbcAggregateTemplateIntegrationTests { Map chain3 = new HashMap<>(); } - @Configuration @Import(TestConfiguration.class) static class Config { @@ -868,7 +866,7 @@ public class JdbcAggregateTemplateIntegrationTests { @Bean JdbcAggregateOperations operations(ApplicationEventPublisher publisher, RelationalMappingContext context, - DataAccessStrategy dataAccessStrategy, RelationalConverter converter) { + DataAccessStrategy dataAccessStrategy, RelationalConverter converter) { return new JdbcAggregateTemplate(publisher, context, converter, dataAccessStrategy); } } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateUnitTests.java index c50fd0e5e..2f6e63d10 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateUnitTests.java @@ -35,6 +35,7 @@ import org.springframework.data.jdbc.core.convert.DataAccessStrategy; import org.springframework.data.jdbc.core.convert.JdbcConverter; import org.springframework.data.jdbc.core.convert.RelationResolver; import org.springframework.data.mapping.callback.EntityCallbacks; +import org.springframework.data.relational.core.conversion.AggregateChange; import org.springframework.data.relational.core.mapping.Column; import org.springframework.data.relational.core.mapping.NamingStrategy; import org.springframework.data.relational.core.mapping.RelationalMappingContext; @@ -47,7 +48,10 @@ import org.springframework.data.relational.core.mapping.event.BeforeSaveCallback import org.springframework.data.relational.core.mapping.event.Identifier; /** + * Unit tests for {@link JdbcAggregateTemplate}. + * * @author Christoph Strobl + * @author Mark Paluch */ @RunWith(MockitoJUnitRunner.class) public class JdbcAggregateTemplateUnitTests { @@ -98,7 +102,8 @@ public class JdbcAggregateTemplateUnitTests { SampleEntity last = template.save(first); verify(callbacks).callback(BeforeConvertCallback.class, first, Identifier.ofNullable(null)); - verify(callbacks).callback(BeforeSaveCallback.class, second, Identifier.ofNullable(23L)); + verify(callbacks).callback(eq(BeforeSaveCallback.class), eq(second), eq(Identifier.ofNullable(23L)), + any(AggregateChange.class)); verify(callbacks).callback(AfterSaveCallback.class, third, Identifier.of(23L)); assertThat(last).isEqualTo(third); } @@ -109,11 +114,12 @@ public class JdbcAggregateTemplateUnitTests { SampleEntity first = new SampleEntity(23L, "Alfred"); SampleEntity second = new SampleEntity(23L, "Alfred E."); - when(callbacks.callback(any(Class.class), any(), any())).thenReturn(second); + when(callbacks.callback(any(Class.class), any(), any(), any())).thenReturn(second); template.delete(first, SampleEntity.class); - verify(callbacks).callback(BeforeDeleteCallback.class, first, Identifier.of(23L)); + verify(callbacks).callback(eq(BeforeDeleteCallback.class), eq(first), eq(Identifier.of(23L)), + any(AggregateChange.class)); verify(callbacks).callback(AfterDeleteCallback.class, second, Identifier.of(23L)); } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AfterDeleteCallback.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AfterDeleteCallback.java index 6ac2b17be..c4cde7bfb 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AfterDeleteCallback.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AfterDeleteCallback.java @@ -16,18 +16,26 @@ package org.springframework.data.relational.core.mapping.event; import org.springframework.data.mapping.callback.EntityCallback; -import org.springframework.lang.Nullable; /** - * An {@link EntityCallback} that gets called after an aggregate got deleted. This callback gets only invoked if - * the method deleting the aggregate received an instance of that aggregate as an argument. Methods deleting entities by id or - * without any parameter don't invoke this callback. - * - * @since 1.1 + * An {@link EntityCallback} that gets called after an aggregate got deleted. This callback gets only invoked if the + * method deleting the aggregate received an instance of that aggregate as an argument. Methods deleting entities by id + * or without any parameter don't invoke this callback. + * * @author Jens Schauder + * @author Mark Paluch + * @since 1.1 */ @FunctionalInterface public interface AfterDeleteCallback extends EntityCallback { - T onAfterDelete(@Nullable T aggregate, Identifier id); + /** + * Entity callback method invoked after an aggregate root was deleted. Can return either the same or a modified + * instance of the aggregate object. + * + * @param aggregate the aggregate that was deleted. + * @param id identifier. + * @return the aggregate that was deleted. + */ + T onAfterDelete(T aggregate, Identifier id); } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AfterLoadCallback.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AfterLoadCallback.java index 2bad180eb..21d865503 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AfterLoadCallback.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AfterLoadCallback.java @@ -18,12 +18,22 @@ package org.springframework.data.relational.core.mapping.event; import org.springframework.data.mapping.callback.EntityCallback; /** - * An {@link EntityCallback} that gets invoked after an aggregate gets loaded from the database. - * - * @since 1.1 + * An {@link EntityCallback} that gets invoked after an aggregate was loaded from the database. + * * @author Jens Schauder + * @author Mark Paluch + * @since 1.1 */ @FunctionalInterface public interface AfterLoadCallback extends EntityCallback { + + /** + * Entity callback method invoked after an aggregate root was loaded. Can return either the same or a modified + * instance of the domain object. + * + * @param aggregate the loaded aggregate. + * @param id identifier. + * @return the loaded aggregate. + */ T onAfterLoad(T aggregate, Identifier.Specified id); } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AfterSaveCallback.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AfterSaveCallback.java index e5f65756c..2927cd378 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AfterSaveCallback.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AfterSaveCallback.java @@ -19,11 +19,21 @@ import org.springframework.data.mapping.callback.EntityCallback; /** * An {@link EntityCallback} that gets invoked after an aggregate was saved. - * - * @since 1.1 + * * @author Jens Schauder + * @author Mark Paluch + * @since 1.1 */ @FunctionalInterface public interface AfterSaveCallback extends EntityCallback { + + /** + * Entity callback method invoked after an aggregate root was persisted. Can return either the same or a modified + * instance of the aggregate. + * + * @param aggregate the saved aggregate. + * @param id identifier. + * @return the saved aggregate. + */ T onAfterSave(T aggregate, Identifier.Specified id); } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/BeforeConvertCallback.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/BeforeConvertCallback.java index 82ea8d45c..637fb00c8 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/BeforeConvertCallback.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/BeforeConvertCallback.java @@ -18,13 +18,23 @@ package org.springframework.data.relational.core.mapping.event; import org.springframework.data.mapping.callback.EntityCallback; /** - * An {@link EntityCallback} that gets invoked before the aggregate gets converted into a database change. The decision - * if the change will be an insert or update will be made before this callback gets called. - * - * @since 1.1 + * An {@link EntityCallback} that gets invoked before the aggregate is converted into a database change. The decision if + * the change will be an insert or update is made before this callback gets called. + * * @author Jens Schauder + * @author Mark Paluch + * @since 1.1 */ @FunctionalInterface public interface BeforeConvertCallback extends EntityCallback { + + /** + * Entity callback method invoked before an aggregate root is converted to be persisted. Can return either the same or + * a modified instance of the aggregate. + * + * @param aggregate the saved aggregate. + * @param id identifier. + * @return the aggregate to be persisted. + */ T onBeforeConvert(T aggregate, Identifier id); } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/BeforeDeleteCallback.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/BeforeDeleteCallback.java index 40d9cd528..9de2dbe64 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/BeforeDeleteCallback.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/BeforeDeleteCallback.java @@ -16,17 +16,29 @@ package org.springframework.data.relational.core.mapping.event; import org.springframework.data.mapping.callback.EntityCallback; +import org.springframework.data.relational.core.conversion.AggregateChange; /** - * An {@link EntityCallback} that gets invoked before an entity gets deleted.This callback gets only invoked if * the + * An {@link EntityCallback} that gets invoked before an entity is deleted. This callback gets only invoked if the * method deleting the aggregate received an instance of that aggregate as an argument. Methods deleting entities by id - * or * without any parameter don't invoke this callback. + * or without any parameter don't invoke this callback. * - * @since 1.1 * @author Jens Schauder + * @since 1.1 */ @FunctionalInterface public interface BeforeDeleteCallback extends EntityCallback { - T onBeforeDelete(T aggregate, Identifier id); + /** + * Entity callback method invoked before an aggregate root is deleted. Can return either the same or a modified + * instance of the aggregate and can modify {@link AggregateChange} contents. This method is called after converting + * the {@code aggregate} to {@link AggregateChange}. Changes to the aggregate are not taken into account for deleting. + * Only transient fields of the entity should be changed in this callback. + * + * @param aggregate the aggregate. + * @param id identifier. + * @param aggregateChange the associated {@link AggregateChange}. + * @return the aggregate to be deleted. + */ + T onBeforeDelete(T aggregate, Identifier id, AggregateChange aggregateChange); } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/BeforeSaveCallback.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/BeforeSaveCallback.java index 479d3f124..e1d8351f8 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/BeforeSaveCallback.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/BeforeSaveCallback.java @@ -16,15 +16,30 @@ package org.springframework.data.relational.core.mapping.event; import org.springframework.data.mapping.callback.EntityCallback; +import org.springframework.data.relational.core.conversion.AggregateChange; /** - * An {@link EntityCallback} that gets invoked before changes get applied to the database but after the aggregate got actually converted to a database change. + * An {@link EntityCallback} that gets invoked before changes are applied to the database, after the aggregate was + * converted to a database change. * - * @since 1.1 * @author Jens Schauder + * @author Mark Paluch + * @since 1.1 */ @FunctionalInterface public interface BeforeSaveCallback extends EntityCallback { - T onBeforeSave(T aggregate, Identifier id); + /** + * Entity callback method invoked before an aggregate root is saved. Can return either the same or a modified instance + * of the aggregate and can modify {@link AggregateChange} contents. This method is called after converting the + * {@code aggregate} to {@link AggregateChange}. Changes to the aggregate are not taken into account for saving. Only + * transient fields of the entity should be changed in this callback. To change persistent the entity before being + * converted, use the {@link BeforeConvertCallback}. + * + * @param aggregate the aggregate. + * @param id identifier. + * @param aggregateChange the associated {@link AggregateChange}. + * @return the aggregate object to be persisted. + */ + T onBeforeSave(T aggregate, Identifier id, AggregateChange aggregateChange); } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/domain/support/RelationalAuditingCallback.java b/spring-data-relational/src/main/java/org/springframework/data/relational/domain/support/RelationalAuditingCallback.java index 620db90e5..ddc73fad8 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/domain/support/RelationalAuditingCallback.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/domain/support/RelationalAuditingCallback.java @@ -21,7 +21,6 @@ import org.springframework.context.ApplicationListener; import org.springframework.core.Ordered; import org.springframework.data.auditing.IsNewAwareAuditingHandler; import org.springframework.data.relational.core.mapping.event.BeforeConvertCallback; -import org.springframework.data.relational.core.mapping.event.BeforeSaveEvent; import org.springframework.data.relational.core.mapping.event.Identifier; /** @@ -30,9 +29,11 @@ import org.springframework.data.relational.core.mapping.event.Identifier; * An instance of this class gets registered when you enable auditing for Spring Data JDBC. * * @author Jens Schauder + * @author Mark Paluch + * @since 1.1 */ @RequiredArgsConstructor -public class RelationalAuditingCallback implements BeforeConvertCallback, Ordered { +public class RelationalAuditingCallback implements BeforeConvertCallback, Ordered { /** * The order used for this {@link org.springframework.context.event.EventListener}. Ordering ensures that this @@ -54,6 +55,10 @@ public class RelationalAuditingCallback implements BeforeConvertCallback, Ordere return AUDITING_ORDER; } + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.mapping.event.BeforeConvertCallback#onBeforeConvert(java.lang.Object, org.springframework.data.relational.core.mapping.event.Identifier) + */ @Override public Object onBeforeConvert(Object entity, Identifier id) { return handler.markAudited(entity); diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/domain/support/RelationalAuditingEventListener.java b/spring-data-relational/src/main/java/org/springframework/data/relational/domain/support/RelationalAuditingEventListener.java index a6c29c1c4..fb014d72f 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/domain/support/RelationalAuditingEventListener.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/domain/support/RelationalAuditingEventListener.java @@ -28,7 +28,7 @@ import org.springframework.data.relational.core.mapping.event.BeforeSaveEvent; * @author Kazuki Shimizu * @author Jens Schauder * @author Oliver Gierke - * @deprecated Use {@link RelationalAuditingCallback} instead. + * @deprecated since 1.1, use {@link RelationalAuditingCallback} instead. */ @Deprecated @RequiredArgsConstructor diff --git a/src/main/asciidoc/index.adoc b/src/main/asciidoc/index.adoc index 931409708..cff36596a 100644 --- a/src/main/asciidoc/index.adoc +++ b/src/main/asciidoc/index.adoc @@ -4,7 +4,7 @@ Jens Schauder, Jay Bryant, Mark Paluch, Bastian Wilhelm :revdate: {localdate} :javadoc-base: https://docs.spring.io/spring-data/jdbc/docs/{revnumber}/api/ ifdef::backend-epub3[:front-cover-image: image:epub-cover.png[Front Cover,1050,1600]] -:spring-data-commons-docs: https://raw.githubusercontent.com/spring-projects/spring-data-commons/master/src/main/asciidoc +:spring-data-commons-docs: ../../../../spring-data-commons/src/main/asciidoc :spring-framework-docs: https://docs.spring.io/spring-framework/docs/{springVersion}/ (C) 2018-2019 The original authors. diff --git a/src/main/asciidoc/jdbc.adoc b/src/main/asciidoc/jdbc.adoc index 09c61e956..a8e62202e 100644 --- a/src/main/asciidoc/jdbc.adoc +++ b/src/main/asciidoc/jdbc.adoc @@ -5,7 +5,7 @@ This chapter points out the specialties for repository support for JDBC. This bu You should have a sound understanding of the basic concepts explained there. [[jdbc.why]] -=== Why Spring Data JDBC? +== Why Spring Data JDBC? The main persistence API for relational databases in the Java world is certainly JPA, which has its own Spring Data module. Why is there another one? @@ -36,7 +36,7 @@ If you do not like that, you should code your own strategy. Spring Data JDBC offers only very limited support for customizing the strategy with annotations. [[jdbc.domain-driven-design]] -=== Domain Driven Design and Relational Databases. +== Domain Driven Design and Relational Databases. All Spring Data modules are inspired by the concepts of "`repository`", "`aggregate`", and "`aggregate root`" from Domain Driven Design. These are possibly even more important for Spring Data JDBC, because they are, to some extent, contrary to normal practice when working with relational databases. @@ -62,7 +62,7 @@ WARNING: In the current implementation, entities referenced from an aggregate ro You can overwrite the repository methods with implementations that match your style of working and designing your database. [[jdbc.java-config]] -=== Annotation-based Configuration +== Annotation-based Configuration The Spring Data JDBC repositories support can be activated by an annotation through Java configuration, as the following example shows: .Spring Data JDBC repositories using Java configuration @@ -563,9 +563,9 @@ Note that the type used for prefixing the statement name is the name of the aggr |=== [[jdbc.events]] -== Events and Callback +== Lifecycle Events -Spring Data JDBC triggers events that get published to any matching `ApplicationListener` and `EntityCallback` beans in the application context. +Spring Data JDBC triggers events that get published to any matching `ApplicationListener` beans in the application context. For example, the following listener gets invoked before an aggregate gets saved: ==== @@ -586,33 +586,59 @@ public ApplicationListener timeStampingSaveTime() { ---- ==== -The following table describes the available events and callbacks: +The following table describes the available events: .Available events |=== -| Event | `EntityCallback` | When It Is Published +| Event | When It Is Published | {javadoc-base}org/springframework/data/relational/core/mapping/event/BeforeDeleteEvent.html[`BeforeDeleteEvent`] -| {javadoc-base}org/springframework/data/relational/core/mapping/event/BeforeDeleteCallback.html[`BeforeDeleteCallback`] | Before an aggregate root gets deleted. | {javadoc-base}org/springframework/data/relational/core/mapping/event/AfterDeleteEvent.html[`AfterDeleteEvent`] -| {javadoc-base}org/springframework/data/relational/core/mapping/event/AfterDeleteCallback.html[`AfterDeleteCallback`] | After an aggregate root gets deleted. | {javadoc-base}/org/springframework/data/relational/core/mapping/event/BeforeConvertEvent.html[`BeforeConvertEvent`] -| {javadoc-base}/org/springframework/data/relational/core/mapping/event/BeforeConvertCallback.html[`BeforeConvertCallback`] | Before an aggregate root gets saved (that is, inserted or updated but after the decision about whether if it gets updated or deleted was made). | {javadoc-base}/org/springframework/data/relational/core/mapping/event/BeforeSaveEvent.html[`BeforeSaveEvent`] -| {javadoc-base}/org/springframework/data/relational/core/mapping/event/BeforeSaveCallback.html[`BeforeSaveCallback`] | Before an aggregate root gets saved (that is, inserted or updated but after the decision about whether if it gets updated or deleted was made). | {javadoc-base}org/springframework/data/relational/core/mapping/event/AfterSaveEvent.html[`AfterSaveEvent`] -| {javadoc-base}org/springframework/data/relational/core/mapping/event/AfterSaveCallback.html[`AfterSaveCallback`] | After an aggregate root gets saved (that is, inserted or updated). | {javadoc-base}org/springframework/data/relational/core/mapping/event/AfterLoadEvent.html[`AfterLoadEvent`] +| After an aggregate root gets created from a database `ResultSet` and all its property get set. +|=== + +WARNING: Lifecycle events depend on an `ApplicationEventMulticaster`, which in case of the `SimpleApplicationEventMulticaster` can be configured with a `TaskExecutor`, and therefore gives no guarantees when an Event is processed. + +include::../{spring-data-commons-docs}/entity-callbacks.adoc[leveloffset=+1] + +[[jdbc.entity-callbacks]] +=== Store-specific EntityCallbacks + +Spring Data JDBC uses the `EntityCallback` API for its auditing support and reacts on the following callbacks: + +.Available Callbacks +|=== +| `EntityCallback` | When It Is Published + +| {javadoc-base}org/springframework/data/relational/core/mapping/event/BeforeDeleteCallback.html[`BeforeDeleteCallback`] +| Before an aggregate root gets deleted. + +| {javadoc-base}org/springframework/data/relational/core/mapping/event/AfterDeleteCallback.html[`AfterDeleteCallback`] +| After an aggregate root gets deleted. + +| {javadoc-base}/org/springframework/data/relational/core/mapping/event/BeforeConvertCallback.html[`BeforeConvertCallback`] +| Before an aggregate root gets saved (that is, inserted or updated but after the decision about whether if it gets updated or deleted was made). + +| {javadoc-base}/org/springframework/data/relational/core/mapping/event/BeforeSaveCallback.html[`BeforeSaveCallback`] +| Before an aggregate root gets saved (that is, inserted or updated but after the decision about whether if it gets updated or deleted was made). + +| {javadoc-base}org/springframework/data/relational/core/mapping/event/AfterSaveCallback.html[`AfterSaveCallback`] +| After an aggregate root gets saved (that is, inserted or updated). + | {javadoc-base}org/springframework/data/relational/core/mapping/event/AfterLoadCallback.html[`AfterLoadCallback`] | After an aggregate root gets created from a database `ResultSet` and all its property get set. |===