Browse Source

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.
pull/163/head
Jens Schauder 7 years ago committed by Mark Paluch
parent
commit
1a9612c64f
  1. 176
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateTemplate.java
  2. 3
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/JdbcAuditingRegistrar.java
  3. 15
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java
  4. 13
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java
  5. 35
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateIntegrationTests.java
  6. 79
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateUnitTests.java
  7. 34
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java
  8. 23
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcAuditingHsqlIntegrationTests.java
  9. 5
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBeanUnitTests.java
  10. 3
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java
  11. 2
      spring-data-jdbc/src/test/resources/logback.xml
  12. 5
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIdGenerationIntegrationTests-hsql.sql
  13. 5
      spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/AggregateChange.java
  14. 33
      spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AfterDeleteCallback.java
  15. 2
      spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AfterDeleteEvent.java
  16. 29
      spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AfterLoadCallback.java
  17. 29
      spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AfterSaveCallback.java
  18. 30
      spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/BeforeConvertCallback.java
  19. 39
      spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/BeforeConvertEvent.java
  20. 32
      spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/BeforeDeleteCallback.java
  21. 2
      spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/BeforeDeleteEvent.java
  22. 30
      spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/BeforeSaveCallback.java
  23. 2
      spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/RelationalEventWithId.java
  24. 11
      spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/SimpleRelationalEvent.java
  25. 61
      spring-data-relational/src/main/java/org/springframework/data/relational/domain/support/RelationalAuditingCallback.java
  26. 4
      spring-data-relational/src/main/java/org/springframework/data/relational/domain/support/RelationalAuditingEventListener.java
  27. 19
      src/main/asciidoc/jdbc.adoc

176
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateTemplate.java

@ -16,10 +16,15 @@ @@ -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 @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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> void publishAfterLoad(Object id, T entity) {
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);
}
}
@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;
}
}
}
}

3
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/JdbcAuditingRegistrar.java

@ -24,6 +24,7 @@ import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; @@ -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 { @@ -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);

15
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java

@ -23,6 +23,7 @@ import org.springframework.data.jdbc.core.convert.DataAccessStrategy; @@ -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 { @@ -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 { @@ -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 { @@ -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;
}
}

13
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java

@ -27,6 +27,7 @@ import org.springframework.data.jdbc.core.convert.JdbcConverter; @@ -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 @@ -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 @@ -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 @@ -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 @@ -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();
}
}

35
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateIntegrationTests.java

@ -44,6 +44,7 @@ import org.springframework.data.annotation.Id; @@ -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 { @@ -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 { @@ -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 { @@ -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);
}
}
}

79
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateUnitTests.java

@ -15,13 +15,14 @@ @@ -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; @@ -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 { @@ -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 { @@ -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;

34
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java

@ -20,6 +20,9 @@ import static org.assertj.core.api.Assertions.*; @@ -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; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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());
}
}
}

23
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcAuditingHsqlIntegrationTests.java

@ -42,7 +42,9 @@ import org.springframework.data.annotation.LastModifiedDate; @@ -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 { @@ -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 { @@ -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;
}
}
}

5
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBeanUnitTests.java

@ -23,10 +23,10 @@ import java.util.function.Supplier; @@ -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; @@ -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 { @@ -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;

3
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java

@ -20,6 +20,8 @@ import java.util.Optional; @@ -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; @@ -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;

2
spring-data-jdbc/src/test/resources/logback.xml

@ -8,7 +8,7 @@ @@ -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">

5
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIdGenerationIntegrationTests-hsql.sql

@ -1,4 +1,5 @@ @@ -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));

5
spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/AggregateChange.java

@ -99,6 +99,11 @@ public class AggregateChange<T> { @@ -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) {

33
spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AfterDeleteCallback.java

@ -0,0 +1,33 @@ @@ -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);
}

2
spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AfterDeleteEvent.java

@ -36,7 +36,7 @@ public class AfterDeleteEvent extends RelationalEventWithId { @@ -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);
}
}

29
spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AfterLoadCallback.java

@ -0,0 +1,29 @@ @@ -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);
}

29
spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AfterSaveCallback.java

@ -0,0 +1,29 @@ @@ -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);
}

30
spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/BeforeConvertCallback.java

@ -0,0 +1,30 @@ @@ -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);
}

39
spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/BeforeConvertEvent.java

@ -0,0 +1,39 @@ @@ -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);
}
}

32
spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/BeforeDeleteCallback.java

@ -0,0 +1,32 @@ @@ -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);
}

2
spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/BeforeDeleteEvent.java

@ -35,7 +35,7 @@ public class BeforeDeleteEvent extends RelationalEventWithId { @@ -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);
}
}

30
spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/BeforeSaveCallback.java

@ -0,0 +1,30 @@ @@ -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);
}

2
spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/RelationalEventWithId.java

@ -32,7 +32,7 @@ public class RelationalEventWithId extends SimpleRelationalEvent implements With @@ -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);

11
spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/SimpleRelationalEvent.java

@ -35,7 +35,7 @@ class SimpleRelationalEvent extends ApplicationEvent implements RelationalEvent @@ -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 @@ -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;
}

61
spring-data-relational/src/main/java/org/springframework/data/relational/domain/support/RelationalAuditingCallback.java

@ -0,0 +1,61 @@ @@ -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);
}
}

4
spring-data-relational/src/main/java/org/springframework/data/relational/domain/support/RelationalAuditingEventListener.java

@ -24,13 +24,13 @@ import org.springframework.data.relational.core.mapping.event.BeforeSaveEvent; @@ -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 {

19
src/main/asciidoc/jdbc.adoc

@ -563,9 +563,9 @@ Note that the type used for prefixing the statement name is the name of the aggr @@ -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() { @@ -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/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.
|===

Loading…
Cancel
Save