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));
|
||||
|
||||
Reference in New Issue
Block a user