DATAJDBC-393 - EntityCallback support.
An `EntityCallback` works very similar to an `ApplicationEvent` but returns a potentially changed instance. The returned instance will be used in further processing which enables proper event handling for immutable classes. Auditing was changed to use a callback making it work also with immutable objects. Original pull request: #161.
This commit is contained in:
committed by
Mark Paluch
parent
d143d8fe71
commit
1a9612c64f
@@ -16,10 +16,15 @@
|
||||
package org.springframework.data.jdbc.core;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
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;
|
||||
import org.springframework.data.relational.core.conversion.Interpreter;
|
||||
@@ -30,12 +35,7 @@ import org.springframework.data.relational.core.conversion.RelationalEntityUpdat
|
||||
import org.springframework.data.relational.core.conversion.RelationalEntityWriter;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
import org.springframework.data.relational.core.mapping.event.AfterDeleteEvent;
|
||||
import org.springframework.data.relational.core.mapping.event.AfterLoadEvent;
|
||||
import org.springframework.data.relational.core.mapping.event.AfterSaveEvent;
|
||||
import org.springframework.data.relational.core.mapping.event.BeforeDeleteEvent;
|
||||
import org.springframework.data.relational.core.mapping.event.BeforeSaveEvent;
|
||||
import org.springframework.data.relational.core.mapping.event.Identifier;
|
||||
import org.springframework.data.relational.core.mapping.event.*;
|
||||
import org.springframework.data.relational.core.mapping.event.Identifier.Specified;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -62,6 +62,8 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
|
||||
|
||||
private final DataAccessStrategy accessStrategy;
|
||||
|
||||
private EntityCallbacks entityCallbacks = NoopEntityCallback.INSTANCE;
|
||||
|
||||
/**
|
||||
* Creates a new {@link JdbcAggregateTemplate} given {@link ApplicationEventPublisher},
|
||||
* {@link RelationalMappingContext} and {@link DataAccessStrategy}.
|
||||
@@ -90,6 +92,13 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
|
||||
this.interpreter = new DefaultJdbcInterpreter(context, accessStrategy);
|
||||
}
|
||||
|
||||
public void setEntityCallbacks(EntityCallbacks entityCallbacks) {
|
||||
|
||||
Assert.notNull(entityCallbacks, "Callbacks must not be null.");
|
||||
|
||||
this.entityCallbacks = entityCallbacks;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.core.JdbcAggregateOperations#save(java.lang.Object)
|
||||
@@ -100,11 +109,11 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
|
||||
Assert.notNull(instance, "Aggregate instance must not be null!");
|
||||
|
||||
RelationalPersistentEntity<?> persistentEntity = context.getRequiredPersistentEntity(instance.getClass());
|
||||
IdentifierAccessor identifierAccessor = persistentEntity.getIdentifierAccessor(instance);
|
||||
|
||||
AggregateChange<T> change = createChange(instance);
|
||||
Function<T, AggregateChange<T>> changeCreator = persistentEntity.isNew(instance) ? this::createInsertChange
|
||||
: this::createUpdateChange;
|
||||
|
||||
return store(instance, identifierAccessor, change, persistentEntity);
|
||||
return store(instance, changeCreator, persistentEntity);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -120,11 +129,8 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
|
||||
Assert.notNull(instance, "Aggregate instance must not be null!");
|
||||
|
||||
RelationalPersistentEntity<?> persistentEntity = context.getRequiredPersistentEntity(instance.getClass());
|
||||
IdentifierAccessor identifierAccessor = persistentEntity.getIdentifierAccessor(instance);
|
||||
|
||||
AggregateChange<T> change = createInsertChange(instance);
|
||||
|
||||
return store(instance, identifierAccessor, change, persistentEntity);
|
||||
return store(instance, this::createInsertChange, persistentEntity);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -140,11 +146,8 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
|
||||
Assert.notNull(instance, "Aggregate instance must not be null!");
|
||||
|
||||
RelationalPersistentEntity<?> persistentEntity = context.getRequiredPersistentEntity(instance.getClass());
|
||||
IdentifierAccessor identifierAccessor = persistentEntity.getIdentifierAccessor(instance);
|
||||
|
||||
AggregateChange<T> change = createUpdateChange(instance);
|
||||
|
||||
return store(instance, identifierAccessor, change, persistentEntity);
|
||||
return store(instance, this::createUpdateChange, persistentEntity);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -171,7 +174,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
|
||||
|
||||
T entity = accessStrategy.findById(id, domainType);
|
||||
if (entity != null) {
|
||||
publishAfterLoad(id, entity);
|
||||
return triggerAfterLoad(id, entity);
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
@@ -199,8 +202,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
|
||||
Assert.notNull(domainType, "Domain type must not be null!");
|
||||
|
||||
Iterable<T> all = accessStrategy.findAll(domainType);
|
||||
publishAfterLoad(all);
|
||||
return all;
|
||||
return triggerAfterLoad(all);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -214,8 +216,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
|
||||
Assert.notNull(domainType, "Domain type must not be null!");
|
||||
|
||||
Iterable<T> allById = accessStrategy.findAllById(ids, domainType);
|
||||
publishAfterLoad(allById);
|
||||
return allById;
|
||||
return triggerAfterLoad(allById);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -260,16 +261,20 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
|
||||
change.executeWith(interpreter, context, converter);
|
||||
}
|
||||
|
||||
private <T> T store(T instance, IdentifierAccessor identifierAccessor, AggregateChange<T> change,
|
||||
private <T> T store(T aggregateRoot, Function<T, AggregateChange<T>> changeCreator,
|
||||
RelationalPersistentEntity<?> persistentEntity) {
|
||||
|
||||
Assert.notNull(instance, "Aggregate instance must not be null!");
|
||||
Assert.notNull(aggregateRoot, "Aggregate instance must not be null!");
|
||||
|
||||
publisher.publishEvent(new BeforeSaveEvent( //
|
||||
Identifier.ofNullable(identifierAccessor.getIdentifier()), //
|
||||
instance, //
|
||||
change //
|
||||
));
|
||||
aggregateRoot = triggerBeforeConvert(aggregateRoot,
|
||||
persistentEntity.getIdentifierAccessor(aggregateRoot).getIdentifier());
|
||||
|
||||
AggregateChange<T> change = changeCreator.apply(aggregateRoot);
|
||||
|
||||
aggregateRoot = triggerBeforeSave(aggregateRoot,
|
||||
persistentEntity.getIdentifierAccessor(aggregateRoot).getIdentifier(), change);
|
||||
|
||||
change.setEntity(aggregateRoot);
|
||||
|
||||
change.executeWith(interpreter, context, converter);
|
||||
|
||||
@@ -277,31 +282,26 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
|
||||
|
||||
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();
|
||||
return triggerAfterSave(change.getEntity(), identifier, change);
|
||||
}
|
||||
|
||||
private void deleteTree(Object id, @Nullable Object entity, Class<?> domainType) {
|
||||
private <T> void deleteTree(Object id, @Nullable T entity, Class<T> domainType) {
|
||||
|
||||
AggregateChange<?> change = createDeletingChange(id, entity, domainType);
|
||||
AggregateChange<T> change = createDeletingChange(id, entity, domainType);
|
||||
|
||||
Specified specifiedId = Identifier.of(id);
|
||||
Optional<Object> optionalEntity = Optional.ofNullable(entity);
|
||||
publisher.publishEvent(new BeforeDeleteEvent(specifiedId, optionalEntity, change));
|
||||
entity = triggerBeforeDelete(entity, id, change);
|
||||
change.setEntity(entity);
|
||||
|
||||
change.executeWith(interpreter, context, converter);
|
||||
|
||||
publisher.publishEvent(new AfterDeleteEvent(specifiedId, optionalEntity, change));
|
||||
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;
|
||||
@@ -324,9 +324,9 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
private AggregateChange<?> createDeletingChange(Object id, @Nullable Object entity, Class<?> domainType) {
|
||||
private <T> AggregateChange<T> createDeletingChange(Object id, @Nullable T entity, Class<T> domainType) {
|
||||
|
||||
AggregateChange<?> aggregateChange = new AggregateChange(Kind.DELETE, domainType, entity);
|
||||
AggregateChange<T> aggregateChange = new AggregateChange(Kind.DELETE, domainType, entity);
|
||||
jdbcEntityDeleteWriter.write(id, aggregateChange);
|
||||
return aggregateChange;
|
||||
}
|
||||
@@ -338,18 +338,96 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
|
||||
return aggregateChange;
|
||||
}
|
||||
|
||||
private <T> void publishAfterLoad(Iterable<T> all) {
|
||||
private <T> Iterable<T> triggerAfterLoad(Iterable<T> all) {
|
||||
|
||||
for (T e : all) {
|
||||
return StreamSupport.stream(all.spliterator(), false).map(e -> {
|
||||
|
||||
RelationalPersistentEntity<?> entity = context.getRequiredPersistentEntity(e.getClass());
|
||||
IdentifierAccessor identifierAccessor = entity.getIdentifierAccessor(e);
|
||||
|
||||
publishAfterLoad(identifierAccessor.getRequiredIdentifier(), e);
|
||||
return triggerAfterLoad(identifierAccessor.getRequiredIdentifier(), e);
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private <T> T triggerAfterLoad(Object id, T entity) {
|
||||
|
||||
publisher.publishEvent(new AfterLoadEvent(Identifier.of(id), entity));
|
||||
|
||||
return entityCallbacks.callback(AfterLoadCallback.class, entity, Identifier.of(id));
|
||||
}
|
||||
|
||||
private <T> T triggerBeforeConvert(T aggregateRoot, @Nullable Object id) {
|
||||
|
||||
Identifier identifier = Identifier.ofNullable(id);
|
||||
|
||||
return entityCallbacks.callback(BeforeConvertCallback.class, aggregateRoot, identifier);
|
||||
}
|
||||
|
||||
private <T> T triggerBeforeSave(T aggregateRoot, @Nullable Object id, AggregateChange<T> change) {
|
||||
|
||||
Identifier identifier = Identifier.ofNullable(id);
|
||||
|
||||
publisher.publishEvent(new BeforeSaveEvent( //
|
||||
identifier, //
|
||||
aggregateRoot, //
|
||||
change //
|
||||
));
|
||||
|
||||
return entityCallbacks.callback(BeforeSaveCallback.class, aggregateRoot, identifier);
|
||||
}
|
||||
|
||||
private <T> T triggerAfterSave(T aggregateRoot, Object id, AggregateChange<T> change) {
|
||||
|
||||
Specified identifier = Identifier.of(id);
|
||||
|
||||
publisher.publishEvent(new AfterSaveEvent( //
|
||||
identifier, //
|
||||
aggregateRoot, //
|
||||
change //
|
||||
));
|
||||
|
||||
return entityCallbacks.callback(AfterSaveCallback.class, aggregateRoot, identifier);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private <T> void triggerAfterDelete(@Nullable T aggregateRoot, Object id, AggregateChange<?> change) {
|
||||
|
||||
Specified identifier = Identifier.of(id);
|
||||
|
||||
publisher.publishEvent(new AfterDeleteEvent(identifier, Optional.ofNullable(aggregateRoot), change));
|
||||
|
||||
if (aggregateRoot != null) {
|
||||
entityCallbacks.callback(AfterDeleteCallback.class, aggregateRoot, identifier);
|
||||
}
|
||||
}
|
||||
|
||||
private <T> void publishAfterLoad(Object id, T entity) {
|
||||
publisher.publishEvent(new AfterLoadEvent(Identifier.of(id), entity));
|
||||
@Nullable
|
||||
private <T> T triggerBeforeDelete(@Nullable T aggregateRoot, Object id, AggregateChange<?> change) {
|
||||
|
||||
Specified identifier = Identifier.of(id);
|
||||
|
||||
publisher.publishEvent(new BeforeDeleteEvent(identifier, Optional.ofNullable(aggregateRoot), change));
|
||||
|
||||
if (aggregateRoot != null) {
|
||||
return entityCallbacks.callback(BeforeDeleteCallback.class, aggregateRoot, identifier);
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
||||
import org.springframework.data.auditing.IsNewAwareAuditingHandler;
|
||||
import org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport;
|
||||
import org.springframework.data.auditing.config.AuditingConfiguration;
|
||||
import org.springframework.data.relational.domain.support.RelationalAuditingCallback;
|
||||
import org.springframework.data.relational.domain.support.RelationalAuditingEventListener;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -86,7 +87,7 @@ class JdbcAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupport {
|
||||
protected void registerAuditListenerBeanDefinition(BeanDefinition auditingHandlerDefinition,
|
||||
BeanDefinitionRegistry registry) {
|
||||
|
||||
Class<?> listenerClass = RelationalAuditingEventListener.class;
|
||||
Class<?> listenerClass = RelationalAuditingCallback.class;
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(listenerClass) //
|
||||
.addConstructorArgReference(AUDITING_HANDLER_BEAN_NAME);
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcConverter;
|
||||
import org.springframework.data.jdbc.repository.QueryMappingConfiguration;
|
||||
import org.springframework.data.jdbc.repository.RowMapperMap;
|
||||
import org.springframework.data.mapping.callback.EntityCallbacks;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
import org.springframework.data.repository.core.EntityInformation;
|
||||
@@ -53,6 +54,7 @@ public class JdbcRepositoryFactory extends RepositoryFactorySupport {
|
||||
private final NamedParameterJdbcOperations operations;
|
||||
|
||||
private QueryMappingConfiguration queryMappingConfiguration = QueryMappingConfiguration.EMPTY;
|
||||
private EntityCallbacks entityCallbacks;
|
||||
|
||||
/**
|
||||
* Creates a new {@link JdbcRepositoryFactory} for the given {@link DataAccessStrategy},
|
||||
@@ -117,7 +119,13 @@ public class JdbcRepositoryFactory extends RepositoryFactorySupport {
|
||||
|
||||
JdbcAggregateTemplate template = new JdbcAggregateTemplate(publisher, context, converter, accessStrategy);
|
||||
|
||||
return new SimpleJdbcRepository<>(template, context.getPersistentEntity(repositoryInformation.getDomainType()));
|
||||
SimpleJdbcRepository<?, Object> repository = new SimpleJdbcRepository<>(template, context.getPersistentEntity(repositoryInformation.getDomainType()));
|
||||
|
||||
if (entityCallbacks != null) {
|
||||
template.setEntityCallbacks(entityCallbacks);
|
||||
}
|
||||
|
||||
return repository;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -147,4 +155,9 @@ public class JdbcRepositoryFactory extends RepositoryFactorySupport {
|
||||
|
||||
throw new IllegalArgumentException(String.format("Unsupported query lookup strategy %s!", key));
|
||||
}
|
||||
|
||||
public void setEntityCallbacks(EntityCallbacks entityCallbacks) {
|
||||
|
||||
this.entityCallbacks = entityCallbacks;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.springframework.data.jdbc.core.convert.JdbcConverter;
|
||||
import org.springframework.data.jdbc.core.convert.SqlGeneratorSource;
|
||||
import org.springframework.data.jdbc.repository.QueryMappingConfiguration;
|
||||
import org.springframework.data.jdbc.repository.RowMapperMap;
|
||||
import org.springframework.data.mapping.callback.EntityCallbacks;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
|
||||
@@ -54,6 +55,7 @@ public class JdbcRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extend
|
||||
private DataAccessStrategy dataAccessStrategy;
|
||||
private QueryMappingConfiguration queryMappingConfiguration = QueryMappingConfiguration.EMPTY;
|
||||
private NamedParameterJdbcOperations operations;
|
||||
private EntityCallbacks entityCallbacks;
|
||||
|
||||
/**
|
||||
* Creates a new {@link JdbcRepositoryFactoryBean} for the given repository interface.
|
||||
@@ -85,6 +87,7 @@ public class JdbcRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extend
|
||||
JdbcRepositoryFactory jdbcRepositoryFactory = new JdbcRepositoryFactory(dataAccessStrategy, mappingContext,
|
||||
converter, publisher, operations);
|
||||
jdbcRepositoryFactory.setQueryMappingConfiguration(queryMappingConfiguration);
|
||||
jdbcRepositoryFactory.setEntityCallbacks(entityCallbacks);
|
||||
|
||||
return jdbcRepositoryFactory;
|
||||
}
|
||||
@@ -151,10 +154,16 @@ public class JdbcRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extend
|
||||
Assert.state(this.converter != null, "RelationalConverter is required and must not be null!");
|
||||
|
||||
if (this.operations == null) {
|
||||
|
||||
Assert.state(beanFactory != null, "If no JdbcOperations are set a BeanFactory must be available.");
|
||||
|
||||
this.operations = beanFactory.getBean(NamedParameterJdbcOperations.class);
|
||||
}
|
||||
|
||||
if (this.dataAccessStrategy == null) {
|
||||
|
||||
Assert.state(beanFactory != null, "If no DataAccessStrategy is set a BeanFactory must be available.");
|
||||
|
||||
this.dataAccessStrategy = this.beanFactory.getBeanProvider(DataAccessStrategy.class) //
|
||||
.getIfAvailable(() -> {
|
||||
|
||||
@@ -168,6 +177,10 @@ public class JdbcRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extend
|
||||
this.queryMappingConfiguration = QueryMappingConfiguration.EMPTY;
|
||||
}
|
||||
|
||||
if (beanFactory != null) {
|
||||
entityCallbacks = EntityCallbacks.create(beanFactory);
|
||||
}
|
||||
|
||||
super.afterPropertiesSet();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ 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;
|
||||
@@ -71,6 +72,7 @@ public class JdbcAggregateTemplateIntegrationTests {
|
||||
|
||||
@ClassRule public static final SpringClassRule classRule = new SpringClassRule();
|
||||
@Rule public SpringMethodRule methodRule = new SpringMethodRule();
|
||||
|
||||
@Autowired JdbcAggregateOperations template;
|
||||
@Autowired NamedParameterJdbcOperations jdbcTemplate;
|
||||
LegoSet legoSet = createLegoSet();
|
||||
@@ -728,22 +730,6 @@ public class JdbcAggregateTemplateIntegrationTests {
|
||||
private String content;
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Import(TestConfiguration.class)
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
Class<?> testClass() {
|
||||
return JdbcAggregateTemplateIntegrationTests.class;
|
||||
}
|
||||
|
||||
@Bean
|
||||
JdbcAggregateOperations operations(ApplicationEventPublisher publisher, RelationalMappingContext context,
|
||||
DataAccessStrategy dataAccessStrategy, RelationalConverter converter) {
|
||||
return new JdbcAggregateTemplate(publisher, context, converter, dataAccessStrategy);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* One may think of ChainN as a chain with N further elements
|
||||
*/
|
||||
@@ -869,4 +855,21 @@ public class JdbcAggregateTemplateIntegrationTests {
|
||||
String fourValue;
|
||||
Map<String, NoIdMapChain3> chain3 = new HashMap<>();
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
@Import(TestConfiguration.class)
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
Class<?> testClass() {
|
||||
return JdbcAggregateTemplateIntegrationTests.class;
|
||||
}
|
||||
|
||||
@Bean
|
||||
JdbcAggregateOperations operations(ApplicationEventPublisher publisher, RelationalMappingContext context,
|
||||
DataAccessStrategy dataAccessStrategy, RelationalConverter converter) {
|
||||
return new JdbcAggregateTemplate(publisher, context, converter, dataAccessStrategy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,13 +15,14 @@
|
||||
*/
|
||||
package org.springframework.data.jdbc.core;
|
||||
|
||||
import static java.util.Arrays.*;
|
||||
import static java.util.Collections.*;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -31,15 +32,19 @@ import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.jdbc.core.convert.BasicJdbcConverter;
|
||||
import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.convert.DefaultDataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcConverter;
|
||||
import org.springframework.data.jdbc.core.convert.RelationResolver;
|
||||
import org.springframework.data.jdbc.core.convert.SqlGeneratorSource;
|
||||
import org.springframework.data.mapping.callback.EntityCallbacks;
|
||||
import org.springframework.data.relational.core.mapping.Column;
|
||||
import org.springframework.data.relational.core.mapping.NamingStrategy;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||
import org.springframework.data.relational.core.mapping.event.AfterDeleteCallback;
|
||||
import org.springframework.data.relational.core.mapping.event.AfterLoadCallback;
|
||||
import org.springframework.data.relational.core.mapping.event.AfterSaveCallback;
|
||||
import org.springframework.data.relational.core.mapping.event.BeforeConvertCallback;
|
||||
import org.springframework.data.relational.core.mapping.event.BeforeDeleteCallback;
|
||||
import org.springframework.data.relational.core.mapping.event.BeforeSaveCallback;
|
||||
import org.springframework.data.relational.core.mapping.event.Identifier;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
@@ -49,19 +54,19 @@ public class JdbcAggregateTemplateUnitTests {
|
||||
|
||||
JdbcAggregateOperations template;
|
||||
|
||||
@Mock DataAccessStrategy dataAccessStrategy;
|
||||
@Mock ApplicationEventPublisher eventPublisher;
|
||||
@Mock RelationResolver relationResolver;
|
||||
@Mock DataSource dataSource;
|
||||
@Mock EntityCallbacks callbacks;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
RelationalMappingContext mappingContext = new RelationalMappingContext(NamingStrategy.INSTANCE);
|
||||
JdbcConverter converter = new BasicJdbcConverter(mappingContext, relationResolver);
|
||||
NamedParameterJdbcOperations namedParameterJdbcOperations = new NamedParameterJdbcTemplate(dataSource);
|
||||
DataAccessStrategy dataAccessStrategy = new DefaultDataAccessStrategy(new SqlGeneratorSource(mappingContext),
|
||||
mappingContext, converter, namedParameterJdbcOperations);
|
||||
|
||||
template = new JdbcAggregateTemplate(eventPublisher, mappingContext, converter, dataAccessStrategy);
|
||||
((JdbcAggregateTemplate) template).setEntityCallbacks(callbacks);
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-378
|
||||
@@ -81,7 +86,61 @@ public class JdbcAggregateTemplateUnitTests {
|
||||
assertThat(template.findAllById(emptyList(), SampleEntity.class)).isEmpty();
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-393
|
||||
public void callbackOnSave() {
|
||||
|
||||
SampleEntity first = new SampleEntity(null, "Alfred");
|
||||
SampleEntity second = new SampleEntity(23L, "Alfred E.");
|
||||
SampleEntity third = new SampleEntity(23L, "Neumann");
|
||||
|
||||
when(callbacks.callback(any(Class.class), any(), any())).thenReturn(second, third);
|
||||
|
||||
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(AfterSaveCallback.class, third, Identifier.of(23L));
|
||||
assertThat(last).isEqualTo(third);
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-393
|
||||
public void callbackOnDelete() {
|
||||
|
||||
SampleEntity first = new SampleEntity(23L, "Alfred");
|
||||
SampleEntity second = new SampleEntity(23L, "Alfred E.");
|
||||
|
||||
when(callbacks.callback(any(Class.class), any(), any())).thenReturn(second);
|
||||
|
||||
template.delete(first, SampleEntity.class);
|
||||
|
||||
verify(callbacks).callback(BeforeDeleteCallback.class, first, Identifier.of(23L));
|
||||
verify(callbacks).callback(AfterDeleteCallback.class, second, Identifier.of(23L));
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-393
|
||||
public void callbackOnLoad() {
|
||||
|
||||
SampleEntity alfred1 = new SampleEntity(23L, "Alfred");
|
||||
SampleEntity alfred2 = new SampleEntity(23L, "Alfred E.");
|
||||
|
||||
SampleEntity neumann1 = new SampleEntity(42L, "Neumann");
|
||||
SampleEntity neumann2 = new SampleEntity(42L, "Alfred E. Neumann");
|
||||
|
||||
when(dataAccessStrategy.findAll(SampleEntity.class)).thenReturn(asList(alfred1, neumann1));
|
||||
|
||||
when(callbacks.callback(any(Class.class), eq(alfred1), any())).thenReturn(alfred2);
|
||||
when(callbacks.callback(any(Class.class), eq(neumann1), any())).thenReturn(neumann2);
|
||||
|
||||
Iterable<SampleEntity> all = template.findAll(SampleEntity.class);
|
||||
|
||||
verify(callbacks).callback(AfterLoadCallback.class, alfred1, Identifier.of(23L));
|
||||
verify(callbacks).callback(AfterLoadCallback.class, neumann1, Identifier.of(42L));
|
||||
|
||||
assertThat(all).containsExactly(alfred2, neumann2);
|
||||
}
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
private static class SampleEntity {
|
||||
|
||||
@Column("id1") @Id private Long id;
|
||||
|
||||
@@ -20,6 +20,9 @@ import static org.assertj.core.api.Assertions.*;
|
||||
import lombok.Data;
|
||||
import lombok.Value;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
import lombok.experimental.Wither;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
@@ -34,6 +37,7 @@ import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories;
|
||||
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
|
||||
import org.springframework.data.jdbc.repository.support.SimpleJdbcRepository;
|
||||
import org.springframework.data.relational.core.mapping.NamingStrategy;
|
||||
import org.springframework.data.relational.core.mapping.event.BeforeConvertCallback;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
@@ -67,6 +71,7 @@ public class JdbcRepositoryIdGenerationIntegrationTests {
|
||||
@Autowired NamedParameterJdbcTemplate template;
|
||||
@Autowired ReadOnlyIdEntityRepository readOnlyIdrepository;
|
||||
@Autowired PrimitiveIdEntityRepository primitiveIdRepository;
|
||||
@Autowired ImmutableWithManualIdEntityRepository immutableWithManualIdEntityRepository;
|
||||
|
||||
@Test // DATAJDBC-98
|
||||
public void idWithoutSetterGetsSet() {
|
||||
@@ -99,10 +104,23 @@ public class JdbcRepositoryIdGenerationIntegrationTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-393
|
||||
public void manuallyGeneratedId() {
|
||||
|
||||
ImmutableWithManualIdEntity entity = new ImmutableWithManualIdEntity(null, "immutable");
|
||||
ImmutableWithManualIdEntity saved = immutableWithManualIdEntityRepository.save(entity);
|
||||
|
||||
assertThat(saved.getId()).isNotNull();
|
||||
|
||||
assertThat(immutableWithManualIdEntityRepository.findAll()).hasSize(1);
|
||||
}
|
||||
|
||||
private interface PrimitiveIdEntityRepository extends CrudRepository<PrimitiveIdEntity, Long> {}
|
||||
|
||||
public interface ReadOnlyIdEntityRepository extends CrudRepository<ReadOnlyIdEntity, Long> {}
|
||||
|
||||
private interface ImmutableWithManualIdEntityRepository extends CrudRepository<ImmutableWithManualIdEntity, Long> {}
|
||||
|
||||
@Value
|
||||
@FieldDefaults(makeFinal = false)
|
||||
static class ReadOnlyIdEntity {
|
||||
@@ -118,11 +136,20 @@ public class JdbcRepositoryIdGenerationIntegrationTests {
|
||||
String name;
|
||||
}
|
||||
|
||||
@Value
|
||||
@Wither
|
||||
static class ImmutableWithManualIdEntity {
|
||||
@Id Long id;
|
||||
String name;
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ComponentScan("org.springframework.data.jdbc.testing")
|
||||
@EnableJdbcRepositories(considerNestedRepositories = true)
|
||||
static class TestConfiguration {
|
||||
|
||||
AtomicLong lastId = new AtomicLong(0);
|
||||
|
||||
@Bean
|
||||
Class<?> testClass() {
|
||||
return JdbcRepositoryIdGenerationIntegrationTests.class;
|
||||
@@ -134,12 +161,19 @@ public class JdbcRepositoryIdGenerationIntegrationTests {
|
||||
*/
|
||||
@Bean
|
||||
NamingStrategy namingStrategy() {
|
||||
|
||||
return new NamingStrategy() {
|
||||
|
||||
@Override
|
||||
public String getTableName(Class<?> type) {
|
||||
return type.getSimpleName().toUpperCase();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
BeforeConvertCallback<ImmutableWithManualIdEntity> idGenerator() {
|
||||
return (e, __) -> e.withId(lastId.incrementAndGet());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,9 @@ import org.springframework.data.annotation.LastModifiedDate;
|
||||
import org.springframework.data.auditing.DateTimeProvider;
|
||||
import org.springframework.data.domain.AuditorAware;
|
||||
import org.springframework.data.relational.core.mapping.NamingStrategy;
|
||||
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;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
@@ -186,7 +188,9 @@ public class EnableJdbcAuditingHsqlIntegrationTests {
|
||||
AuditingAnnotatedDummyEntityRepository.class, //
|
||||
TestConfiguration.class, //
|
||||
AuditingConfiguration.class, //
|
||||
OrderAssertingEventListener.class) //
|
||||
OrderAssertingEventListener.class, //
|
||||
OrderAssertingCallback.class //
|
||||
) //
|
||||
.accept(repository -> {
|
||||
|
||||
AuditingAnnotatedDummyEntity entity = repository.save(new AuditingAnnotatedDummyEntity());
|
||||
@@ -331,4 +335,21 @@ public class EnableJdbcAuditingHsqlIntegrationTests {
|
||||
assertThat(((AuditingAnnotatedDummyEntity) entity).createdDate).isNotNull();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An event listener asserting that it is running after {@link AuditingConfiguration#auditorAware()} was invoked and
|
||||
* set the auditing data.
|
||||
*/
|
||||
@Component
|
||||
static class OrderAssertingCallback implements BeforeConvertCallback {
|
||||
|
||||
@Override
|
||||
public Object onBeforeConvert(Object entity, Identifier id) {
|
||||
|
||||
assertThat(entity).isInstanceOf(AuditingAnnotatedDummyEntity.class);
|
||||
assertThat(((AuditingAnnotatedDummyEntity) entity).createdDate).isNotNull();
|
||||
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,10 +23,10 @@ import java.util.function.Supplier;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
@@ -35,7 +35,6 @@ import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.jdbc.core.convert.BasicJdbcConverter;
|
||||
import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.convert.DefaultDataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcTypeFactory;
|
||||
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
|
||||
import org.springframework.data.jdbc.repository.QueryMappingConfiguration;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
@@ -60,7 +59,7 @@ public class JdbcRepositoryFactoryBeanUnitTests {
|
||||
|
||||
@Mock DataAccessStrategy dataAccessStrategy;
|
||||
@Mock ApplicationEventPublisher publisher;
|
||||
@Mock ListableBeanFactory beanFactory;
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS) ListableBeanFactory beanFactory;
|
||||
|
||||
RelationalMappingContext mappingContext;
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ import java.util.Optional;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.apache.ibatis.session.SqlSessionFactory;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
@@ -38,6 +40,7 @@ import org.springframework.data.jdbc.core.convert.RelationResolver;
|
||||
import org.springframework.data.jdbc.core.convert.SqlGeneratorSource;
|
||||
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
|
||||
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
|
||||
import org.springframework.data.mapping.callback.EntityCallbacks;
|
||||
import org.springframework.data.relational.core.mapping.NamingStrategy;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
</appender>
|
||||
|
||||
<!--<logger name="org.springframework.data" level="info" />-->
|
||||
<!--<logger name="org.springframework.jdbc.core" level="trace" />-->
|
||||
<logger name="org.springframework.jdbc.core" level="trace" />
|
||||
<!--<logger name="org.springframework.data.jdbc.mybatis.DummyEntityMapper" level="trace" />-->
|
||||
|
||||
<root level="warn">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
|
||||
CREATE TABLE ReadOnlyIdEntity (ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, NAME VARCHAR(100))
|
||||
CREATE TABLE PrimitiveIdEntity (ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, NAME VARCHAR(100))
|
||||
CREATE TABLE ReadOnlyIdEntity (ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, NAME VARCHAR(100));
|
||||
CREATE TABLE PrimitiveIdEntity (ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, NAME VARCHAR(100));
|
||||
CREATE TABLE ImmutableWithManualIdentity (ID BIGINT PRIMARY KEY, NAME VARCHAR(100));
|
||||
|
||||
@@ -99,6 +99,11 @@ public class AggregateChange<T> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void setEntity(@Nullable T aggregateRoot) {
|
||||
entity = aggregateRoot;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T> void setIdInElementOfSet(RelationalConverter converter, DbAction.WithDependingOn<?> action,
|
||||
Object generatedId, Set<T> set) {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
* https://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.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
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface AfterDeleteCallback<T> extends EntityCallback<T> {
|
||||
|
||||
T onAfterDelete(@Nullable T aggregate, Identifier id);
|
||||
}
|
||||
@@ -36,7 +36,7 @@ public class AfterDeleteEvent extends RelationalEventWithId {
|
||||
* @param change the {@link AggregateChange} encoding the actions that were performed on the database as part of the
|
||||
* delete operation.
|
||||
*/
|
||||
public AfterDeleteEvent(Specified id, Optional<Object> instance, AggregateChange change) {
|
||||
public AfterDeleteEvent(Specified id, Optional<?> instance, AggregateChange change) {
|
||||
super(id, instance, change);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
* https://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.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
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface AfterLoadCallback<T> extends EntityCallback<T> {
|
||||
T onAfterLoad(T aggregate, Identifier.Specified id);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
* https://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.mapping.event;
|
||||
|
||||
import org.springframework.data.mapping.callback.EntityCallback;
|
||||
|
||||
/**
|
||||
* An {@link EntityCallback} that gets invoked after an aggregate was saved.
|
||||
*
|
||||
* @since 1.1
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface AfterSaveCallback<T> extends EntityCallback<T> {
|
||||
T onAfterSave(T aggregate, Identifier.Specified id);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
* https://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.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
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface BeforeConvertCallback<T> extends EntityCallback<T> {
|
||||
T onBeforeConvert(T aggregate, Identifier id);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
* https://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.mapping.event;
|
||||
|
||||
import org.springframework.data.relational.core.conversion.AggregateChange;
|
||||
import org.springframework.data.relational.core.mapping.event.Identifier.Specified;
|
||||
|
||||
/**
|
||||
* Gets published before an aggregate gets converted into a database change.
|
||||
*
|
||||
* @since 1.1
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
public class BeforeConvertEvent extends RelationalEventWithIdAndEntity {
|
||||
|
||||
private static final long serialVersionUID = 3980149746683849019L;
|
||||
|
||||
/**
|
||||
* @param id identifier of the saved entity.
|
||||
* @param instance the saved entity.
|
||||
* @param change the {@link AggregateChange} encoding the actions performed on the database as part of the delete.
|
||||
*/
|
||||
public BeforeConvertEvent(Specified id, Object instance, AggregateChange change) {
|
||||
super(id, instance, change);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
* https://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.mapping.event;
|
||||
|
||||
import org.springframework.data.mapping.callback.EntityCallback;
|
||||
|
||||
/**
|
||||
* An {@link EntityCallback} that gets invoked before an entity gets 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
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface BeforeDeleteCallback<T> extends EntityCallback<T> {
|
||||
|
||||
T onBeforeDelete(T aggregate, Identifier id);
|
||||
}
|
||||
@@ -35,7 +35,7 @@ public class BeforeDeleteEvent extends RelationalEventWithId {
|
||||
* @param entity the entity about to get deleted. Might be empty.
|
||||
* @param change the {@link AggregateChange} encoding the planned actions to be performed on the database.
|
||||
*/
|
||||
public <T> BeforeDeleteEvent(Specified id, Optional<Object> entity, AggregateChange change) {
|
||||
public <T> BeforeDeleteEvent(Specified id, Optional<?> entity, AggregateChange change) {
|
||||
super(id, entity, change);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
* https://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.mapping.event;
|
||||
|
||||
import org.springframework.data.mapping.callback.EntityCallback;
|
||||
|
||||
/**
|
||||
* An {@link EntityCallback} that gets invoked before changes get applied to the database but after the aggregate got actually converted to a database change.
|
||||
*
|
||||
* @since 1.1
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface BeforeSaveCallback<T> extends EntityCallback<T> {
|
||||
|
||||
T onBeforeSave(T aggregate, Identifier id);
|
||||
}
|
||||
@@ -32,7 +32,7 @@ public class RelationalEventWithId extends SimpleRelationalEvent implements With
|
||||
|
||||
private final Specified id;
|
||||
|
||||
public RelationalEventWithId(Specified id, Optional<Object> entity, @Nullable AggregateChange change) {
|
||||
public RelationalEventWithId(Specified id, Optional<?> entity, @Nullable AggregateChange change) {
|
||||
|
||||
super(id, entity, change);
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ class SimpleRelationalEvent extends ApplicationEvent implements RelationalEvent
|
||||
private final Object entity;
|
||||
private final AggregateChange change;
|
||||
|
||||
SimpleRelationalEvent(Identifier id, Optional<Object> entity, @Nullable AggregateChange change) {
|
||||
SimpleRelationalEvent(Identifier id, Optional<?> entity, @Nullable AggregateChange change) {
|
||||
|
||||
super(id);
|
||||
|
||||
@@ -61,6 +61,15 @@ class SimpleRelationalEvent extends ApplicationEvent implements RelationalEvent
|
||||
return Optional.ofNullable(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the an {@link AggregateChange} instance representing the SQL statements performed by the action that
|
||||
* triggered this event.
|
||||
*
|
||||
* @return Guaranteed to be not {@literal null}.
|
||||
* @deprecated There is currently no replacement for this. If something like this is required please create an issue
|
||||
* outlining your use case.
|
||||
*/
|
||||
@Deprecated
|
||||
public AggregateChange getChange() {
|
||||
return change;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2018-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
|
||||
*
|
||||
* https://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.domain.support;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* {@link BeforeConvertCallback} to capture auditing information on persisting and updating entities.
|
||||
* <p>
|
||||
* An instance of this class gets registered when you enable auditing for Spring Data JDBC.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class RelationalAuditingCallback implements BeforeConvertCallback, Ordered {
|
||||
|
||||
/**
|
||||
* The order used for this {@link org.springframework.context.event.EventListener}. Ordering ensures that this
|
||||
* {@link ApplicationListener} will run before other listeners without a specified priority.
|
||||
*
|
||||
* @see org.springframework.core.annotation.Order
|
||||
* @see Ordered
|
||||
*/
|
||||
public static final int AUDITING_ORDER = 100;
|
||||
|
||||
private final IsNewAwareAuditingHandler handler;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.Ordered#getOrder()
|
||||
*/
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return AUDITING_ORDER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object onBeforeConvert(Object entity, Identifier id) {
|
||||
return handler.markAudited(entity);
|
||||
}
|
||||
}
|
||||
@@ -24,13 +24,13 @@ import org.springframework.data.relational.core.mapping.event.BeforeSaveEvent;
|
||||
|
||||
/**
|
||||
* Spring JDBC event listener to capture auditing information on persisting and updating entities.
|
||||
* <p>
|
||||
* An instance of this class gets registered when you enable auditing for Spring Data JDBC.
|
||||
*
|
||||
* @author Kazuki Shimizu
|
||||
* @author Jens Schauder
|
||||
* @author Oliver Gierke
|
||||
* @deprecated Use {@link RelationalAuditingCallback} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@RequiredArgsConstructor
|
||||
public class RelationalAuditingEventListener implements ApplicationListener<BeforeSaveEvent>, Ordered {
|
||||
|
||||
|
||||
@@ -563,9 +563,9 @@ Note that the type used for prefixing the statement name is the name of the aggr
|
||||
|===
|
||||
|
||||
[[jdbc.events]]
|
||||
== Events
|
||||
== Events and Callback
|
||||
|
||||
Spring Data JDBC triggers events that get published to any matching `ApplicationListener` in the application context.
|
||||
Spring Data JDBC triggers events that get published to any matching `ApplicationListener` and `EntityCallback` beans in the application context.
|
||||
For example, the following listener gets invoked before an aggregate gets saved:
|
||||
|
||||
====
|
||||
@@ -586,27 +586,34 @@ public ApplicationListener<BeforeSave> timeStampingSaveTime() {
|
||||
----
|
||||
====
|
||||
|
||||
The following table describes the available events:
|
||||
The following table describes the available events and callbacks:
|
||||
|
||||
.Available events
|
||||
|===
|
||||
| Event | When It Is Published
|
||||
| Event | `EntityCallback` | 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/BeforeSaveEvent.html[`BeforeSaveEvent`]
|
||||
| {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).
|
||||
The event has a reference to an {javadoc-base}/org/springframework/data/relational/core/conversion/AggregateChange.html[`AggregateChange`] instance.
|
||||
The instance can be modified by adding or removing {javadoc-base}/org/springframework/data/relational/core/conversion/DbAction.html[`DbAction`] instances.
|
||||
|
||||
| {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`]
|
||||
| {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.
|
||||
|===
|
||||
|
||||
|
||||
Reference in New Issue
Block a user