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.
This commit is contained in:
Mark Paluch
2019-07-16 11:13:25 +02:00
parent 1a9612c64f
commit 257dd7d5d4
16 changed files with 208 additions and 95 deletions

View File

@@ -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 <T> AggregateChange<T> createChange(T instance) {
// context.getRequiredPersistentEntity(o.getClass()).isNew(o)
AggregateChange<T> aggregateChange = new AggregateChange(Kind.SAVE, instance.getClass(), instance);
jdbcEntityWriter.write(instance, aggregateChange);
return aggregateChange;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private <T> AggregateChange<T> createInsertChange(T instance) {
@@ -351,9 +376,11 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
private <T> T triggerAfterLoad(Object id, T entity) {
publisher.publishEvent(new AfterLoadEvent(Identifier.of(id), entity));
Specified identifier = Identifier.of(id);
return entityCallbacks.callback(AfterLoadCallback.class, entity, Identifier.of(id));
publisher.publishEvent(new AfterLoadEvent(identifier, entity));
return entityCallbacks.callback(AfterLoadCallback.class, entity, identifier);
}
private <T> 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> T triggerAfterSave(T aggregateRoot, Object id, AggregateChange<T> change) {
@@ -389,7 +416,6 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
return entityCallbacks.callback(AfterSaveCallback.class, aggregateRoot, identifier);
}
@Nullable
private <T> 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> T callback(Class<? extends EntityCallback> callbackType, T entity, Object... args) {
return entity;
}
}
return null;
}
}

View File

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

View File

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

View File

@@ -119,7 +119,8 @@ public class JdbcRepositoryFactory extends RepositoryFactorySupport {
JdbcAggregateTemplate template = new JdbcAggregateTemplate(publisher, context, converter, accessStrategy);
SimpleJdbcRepository<?, Object> repository = new SimpleJdbcRepository<>(template, context.getPersistentEntity(repositoryInformation.getDomainType()));
SimpleJdbcRepository<?, Object> 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;
}
}

View File

@@ -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<String, NoIdMapChain3> 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);
}
}

View File

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