DATAJDBC-454 - Redesigned events.
Ids are only contained if it can not be guaranteed that an entity is contained which applies to the delete events. As a side effect Identifier got simplified into a single simple class. Original pull request: #199.
This commit is contained in:
committed by
Mark Paluch
parent
7a2a3fb2eb
commit
22f9eded60
@@ -17,7 +17,6 @@ package org.springframework.data.jdbc.core;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
@@ -40,7 +39,6 @@ import org.springframework.data.relational.core.conversion.RelationalEntityUpdat
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
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;
|
||||
|
||||
@@ -212,7 +210,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
|
||||
|
||||
T entity = accessStrategy.findById(id, domainType);
|
||||
if (entity != null) {
|
||||
return triggerAfterLoad(id, entity);
|
||||
return triggerAfterLoad(entity);
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
@@ -337,8 +335,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
|
||||
|
||||
AggregateChange<T> change = changeCreator.apply(aggregateRoot);
|
||||
|
||||
aggregateRoot = triggerBeforeSave(aggregateRoot,
|
||||
persistentEntity.getIdentifierAccessor(aggregateRoot).getIdentifier(), change);
|
||||
aggregateRoot = triggerBeforeSave(aggregateRoot, change);
|
||||
|
||||
change.setEntity(aggregateRoot);
|
||||
|
||||
@@ -348,7 +345,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
|
||||
|
||||
Assert.notNull(identifier, "After saving the identifier must not be null!");
|
||||
|
||||
return triggerAfterSave(change.getEntity(), identifier, change);
|
||||
return triggerAfterSave(change.getEntity(), change);
|
||||
}
|
||||
|
||||
private <T> void deleteTree(Object id, @Nullable T entity, Class<T> domainType) {
|
||||
@@ -396,19 +393,15 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
|
||||
List<T> result = new ArrayList<>();
|
||||
|
||||
for (T e : all) {
|
||||
|
||||
RelationalPersistentEntity<?> entity = context.getRequiredPersistentEntity(e.getClass());
|
||||
IdentifierAccessor identifierAccessor = entity.getIdentifierAccessor(e);
|
||||
|
||||
result.add(triggerAfterLoad(identifierAccessor.getRequiredIdentifier(), e));
|
||||
result.add(triggerAfterLoad(e));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private <T> T triggerAfterLoad(Object id, T entity) {
|
||||
private <T> T triggerAfterLoad(T entity) {
|
||||
|
||||
publisher.publishEvent(new AfterLoadEvent(Identifier.of(id), entity));
|
||||
publisher.publishEvent(new AfterLoadEvent(entity));
|
||||
|
||||
return entityCallbacks.callback(AfterLoadCallback.class, entity);
|
||||
}
|
||||
@@ -417,33 +410,27 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
|
||||
return entityCallbacks.callback(BeforeConvertCallback.class, aggregateRoot);
|
||||
}
|
||||
|
||||
private <T> T triggerBeforeSave(T aggregateRoot, @Nullable Object id, AggregateChange<T> change) {
|
||||
private <T> T triggerBeforeSave(T aggregateRoot, AggregateChange<T> change) {
|
||||
|
||||
publisher.publishEvent(new BeforeSaveEvent( //
|
||||
Identifier.ofNullable(id), //
|
||||
//
|
||||
aggregateRoot, //
|
||||
change //
|
||||
));
|
||||
//
|
||||
change));
|
||||
|
||||
return entityCallbacks.callback(BeforeSaveCallback.class, aggregateRoot, change);
|
||||
}
|
||||
|
||||
private <T> T triggerAfterSave(T aggregateRoot, Object id, AggregateChange<T> change) {
|
||||
private <T> T triggerAfterSave(T aggregateRoot, AggregateChange<T> change) {
|
||||
|
||||
Specified identifier = Identifier.of(id);
|
||||
|
||||
publisher.publishEvent(new AfterSaveEvent( //
|
||||
identifier, //
|
||||
aggregateRoot, //
|
||||
change //
|
||||
));
|
||||
publisher.publishEvent(new AfterSaveEvent(aggregateRoot, change));
|
||||
|
||||
return entityCallbacks.callback(AfterSaveCallback.class, aggregateRoot);
|
||||
}
|
||||
|
||||
private <T> void triggerAfterDelete(@Nullable T aggregateRoot, Object id, AggregateChange<?> change) {
|
||||
|
||||
publisher.publishEvent(new AfterDeleteEvent(Identifier.of(id), Optional.ofNullable(aggregateRoot), change));
|
||||
publisher.publishEvent(new AfterDeleteEvent(Identifier.of(id), aggregateRoot, change));
|
||||
|
||||
if (aggregateRoot != null) {
|
||||
entityCallbacks.callback(AfterDeleteCallback.class, aggregateRoot);
|
||||
@@ -453,7 +440,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
|
||||
@Nullable
|
||||
private <T> T triggerBeforeDelete(@Nullable T aggregateRoot, Object id, AggregateChange<?> change) {
|
||||
|
||||
publisher.publishEvent(new BeforeDeleteEvent(Identifier.of(id), Optional.ofNullable(aggregateRoot), change));
|
||||
publisher.publishEvent(new BeforeDeleteEvent(Identifier.of(id), aggregateRoot, change));
|
||||
|
||||
if (aggregateRoot != null) {
|
||||
return entityCallbacks.callback(BeforeDeleteCallback.class, aggregateRoot, change);
|
||||
|
||||
@@ -31,7 +31,6 @@ import org.springframework.data.relational.core.mapping.RelationalMappingContext
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
import org.springframework.data.relational.core.mapping.event.AfterLoadCallback;
|
||||
import org.springframework.data.relational.core.mapping.event.AfterLoadEvent;
|
||||
import org.springframework.data.relational.core.mapping.event.Identifier;
|
||||
import org.springframework.data.repository.query.Parameter;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.jdbc.core.ResultSetExtractor;
|
||||
@@ -213,10 +212,8 @@ class JdbcRepositoryQuery implements RepositoryQuery {
|
||||
|
||||
MapSqlParameterSource parameters = new MapSqlParameterSource();
|
||||
|
||||
queryMethod.getParameters().getBindableParameters().forEach(p -> {
|
||||
|
||||
convertAndAddParameter(parameters, p, objects[p.getIndex()]);
|
||||
});
|
||||
queryMethod.getParameters().getBindableParameters()
|
||||
.forEach(p -> convertAndAddParameter(parameters, p, objects[p.getIndex()]));
|
||||
|
||||
return parameters;
|
||||
}
|
||||
@@ -291,7 +288,7 @@ class JdbcRepositoryQuery implements RepositoryQuery {
|
||||
Object identifier = e.getIdentifierAccessor(entity).getIdentifier();
|
||||
|
||||
if (identifier != null) {
|
||||
publisher.publishEvent(new AfterLoadEvent(Identifier.of(identifier), entity));
|
||||
publisher.publishEvent(new AfterLoadEvent(entity));
|
||||
}
|
||||
|
||||
callbacks.callback(AfterLoadCallback.class, entity);
|
||||
|
||||
@@ -1,232 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017-2020 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.jdbc.repository;
|
||||
|
||||
import static java.util.Arrays.*;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import junit.framework.AssertionFailedError;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.annotation.PersistenceConstructor;
|
||||
import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories;
|
||||
import org.springframework.data.jdbc.testing.TestConfiguration;
|
||||
import org.springframework.data.relational.core.conversion.DbAction;
|
||||
import org.springframework.data.relational.core.mapping.event.BeforeDeleteEvent;
|
||||
import org.springframework.data.relational.core.mapping.event.BeforeSaveEvent;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.rules.SpringClassRule;
|
||||
import org.springframework.test.context.junit4.rules.SpringMethodRule;
|
||||
|
||||
/**
|
||||
* Tests that the event infrastructure of Spring Data JDBC is sufficient to manipulate the {@link DbAction}s to be
|
||||
* executed against the database.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
@ContextConfiguration
|
||||
public class JdbcRepositoryManipulateDbActionsIntegrationTests {
|
||||
|
||||
@ClassRule public static final SpringClassRule classRule = new SpringClassRule();
|
||||
@Rule public SpringMethodRule methodRule = new SpringMethodRule();
|
||||
|
||||
@Autowired DummyEntityRepository repository;
|
||||
@Autowired LogRepository logRepository;
|
||||
|
||||
@Test // DATAJDBC-120
|
||||
public void softDelete() {
|
||||
|
||||
// given a persistent entity
|
||||
DummyEntity entity = new DummyEntity(null, "Hello");
|
||||
repository.save(entity);
|
||||
assertThat(entity.id).isNotNull();
|
||||
|
||||
// when I delete the entity
|
||||
repository.delete(entity);
|
||||
|
||||
// it is still in the repository, but marked as deleted
|
||||
assertThat(repository.findById(entity.id)) //
|
||||
.contains(new DummyEntity( //
|
||||
entity.id, //
|
||||
entity.name, //
|
||||
true) //
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-120
|
||||
public void softDeleteMany() {
|
||||
|
||||
// given persistent entities
|
||||
DummyEntity one = new DummyEntity(null, "One");
|
||||
DummyEntity two = new DummyEntity(null, "Two");
|
||||
repository.saveAll(asList(one, two));
|
||||
|
||||
assertThat(one.id).isNotNull();
|
||||
|
||||
// when I delete the entities
|
||||
repository.deleteAll(asList(one, two));
|
||||
|
||||
// they are still in the repository, but marked as deleted
|
||||
assertThat(repository.findById(one.id)) //
|
||||
.contains(new DummyEntity( //
|
||||
one.id, //
|
||||
one.name, //
|
||||
true) //
|
||||
);
|
||||
|
||||
assertThat(repository.findById(two.id)) //
|
||||
.contains(new DummyEntity( //
|
||||
two.id, //
|
||||
two.name, //
|
||||
true) //
|
||||
);
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-120
|
||||
public void loggingOnSave() {
|
||||
|
||||
// given a new entity
|
||||
DummyEntity one = new DummyEntity(null, "one");
|
||||
|
||||
repository.save(one);
|
||||
assertThat(one.id).isNotNull();
|
||||
|
||||
// they are still in the repository, but marked as deleted
|
||||
assertThat(logRepository.findById(Config.lastLogId)) //
|
||||
.isNotEmpty() //
|
||||
.map(Log::getText) //
|
||||
.contains("one saved");
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-120
|
||||
public void loggingOnSaveMany() {
|
||||
|
||||
// given a new entity
|
||||
DummyEntity one = new DummyEntity(null, "one");
|
||||
DummyEntity two = new DummyEntity(null, "two");
|
||||
|
||||
repository.saveAll(asList(one, two));
|
||||
assertThat(one.id).isNotNull();
|
||||
|
||||
// they are still in the repository, but marked as deleted
|
||||
assertThat(logRepository.findById(Config.lastLogId)) //
|
||||
.isNotEmpty() //
|
||||
.map(Log::getText) //
|
||||
.contains("two saved");
|
||||
}
|
||||
|
||||
@Data
|
||||
private static class DummyEntity {
|
||||
|
||||
@Id Long id;
|
||||
String name;
|
||||
boolean deleted;
|
||||
|
||||
DummyEntity(Long id, String name) {
|
||||
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.deleted = false;
|
||||
}
|
||||
|
||||
@PersistenceConstructor
|
||||
DummyEntity(Long id, String name, boolean deleted) {
|
||||
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.deleted = deleted;
|
||||
}
|
||||
}
|
||||
|
||||
private interface DummyEntityRepository extends CrudRepository<DummyEntity, Long> {}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@RequiredArgsConstructor
|
||||
private static class Log {
|
||||
|
||||
@Id Long id;
|
||||
DummyEntity entity;
|
||||
String text;
|
||||
}
|
||||
|
||||
private interface LogRepository extends CrudRepository<Log, Long> {}
|
||||
|
||||
@Configuration
|
||||
@Import(TestConfiguration.class)
|
||||
@EnableJdbcRepositories(considerNestedRepositories = true)
|
||||
static class Config {
|
||||
|
||||
static long lastLogId;
|
||||
|
||||
@Bean
|
||||
Class<?> testClass() {
|
||||
return JdbcRepositoryManipulateDbActionsIntegrationTests.class;
|
||||
}
|
||||
|
||||
@Bean
|
||||
ApplicationListener<BeforeDeleteEvent> softDeleteListener() {
|
||||
|
||||
return event -> {
|
||||
|
||||
DummyEntity entity = (DummyEntity) event.getOptionalEntity().orElseThrow(AssertionFailedError::new);
|
||||
entity.deleted = true;
|
||||
|
||||
List<DbAction<?>> actions = event.getChange().getActions();
|
||||
actions.clear();
|
||||
actions.add(new DbAction.UpdateRoot<>(entity));
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
ApplicationListener<BeforeSaveEvent> logOnSaveListener() {
|
||||
|
||||
// this would actually be easier to implement with an AfterSaveEvent listener, but we want to test AggregateChange
|
||||
// manipulation.
|
||||
return event -> {
|
||||
|
||||
DummyEntity entity = (DummyEntity) event.getOptionalEntity().orElseThrow(AssertionFailedError::new);
|
||||
lastLogId = new Random().nextLong();
|
||||
Log log = new Log();
|
||||
log.setId(lastLogId);
|
||||
log.entity = entity;
|
||||
log.text = entity.name + " saved";
|
||||
|
||||
List<DbAction<?>> actions = event.getChange().getActions();
|
||||
actions.add(new DbAction.InsertRoot<>(log));
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,6 @@ import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import junit.framework.AssertionFailedError;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Value;
|
||||
import lombok.With;
|
||||
@@ -57,11 +56,13 @@ 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.RelationalEvent;
|
||||
import org.springframework.data.relational.core.mapping.event.WithId;
|
||||
import org.springframework.data.repository.PagingAndSortingRepository;
|
||||
import org.springframework.jdbc.core.JdbcOperations;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
|
||||
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
|
||||
import org.springframework.jdbc.support.KeyHolder;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Unit tests for application events via {@link SimpleJdbcRepository}.
|
||||
@@ -143,14 +144,23 @@ public class SimpleJdbcRepositoryEventsUnitTests {
|
||||
|
||||
assertThat(publisher.events).extracting( //
|
||||
RelationalEvent::getClass, //
|
||||
e -> e.getOptionalEntity().orElseGet(AssertionFailedError::new), //
|
||||
RelationalEvent::getId //
|
||||
this::getEntity, //
|
||||
this::getId //
|
||||
).containsExactly( //
|
||||
Tuple.tuple(BeforeDeleteEvent.class, entity, Identifier.of(23L)), //
|
||||
Tuple.tuple(AfterDeleteEvent.class, entity, Identifier.of(23L)) //
|
||||
);
|
||||
}
|
||||
|
||||
private Identifier getId(RelationalEvent e) {
|
||||
return ((WithId) e).getId();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Object getEntity(RelationalEvent e) {
|
||||
return e.getEntity();
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-99
|
||||
@SuppressWarnings("rawtypes")
|
||||
public void publishesEventsOnDeleteById() {
|
||||
|
||||
@@ -61,8 +61,7 @@ public class JdbcRepositoryFactoryBeanUnitTests {
|
||||
@Mock DataAccessStrategy dataAccessStrategy;
|
||||
@Mock ApplicationEventPublisher publisher;
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS) ListableBeanFactory beanFactory;
|
||||
@Mock
|
||||
Dialect dialect;
|
||||
@Mock Dialect dialect;
|
||||
|
||||
RelationalMappingContext mappingContext;
|
||||
|
||||
@@ -78,10 +77,11 @@ public class JdbcRepositoryFactoryBeanUnitTests {
|
||||
|
||||
ObjectProvider<DataAccessStrategy> provider = mock(ObjectProvider.class);
|
||||
when(beanFactory.getBeanProvider(DataAccessStrategy.class)).thenReturn(provider);
|
||||
when(provider.getIfAvailable(any())).then((Answer) invocation -> ((Supplier) invocation.getArgument(0)).get());
|
||||
when(provider.getIfAvailable(any()))
|
||||
.then((Answer<?>) invocation -> ((Supplier<?>) invocation.getArgument(0)).get());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // DATAJDBC-151
|
||||
public void setsUpBasicInstanceCorrectly() {
|
||||
|
||||
factoryBean.setDataAccessStrategy(dataAccessStrategy);
|
||||
@@ -95,7 +95,7 @@ public class JdbcRepositoryFactoryBeanUnitTests {
|
||||
assertThat(factoryBean.getObject()).isNotNull();
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
@Test(expected = IllegalArgumentException.class) // DATAJDBC-151
|
||||
public void requiresListableBeanFactory() {
|
||||
|
||||
factoryBean.setBeanFactory(mock(BeanFactory.class));
|
||||
|
||||
Reference in New Issue
Block a user