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. |===