Browse Source

DATAJDBC-120 - Changed events to support manipulation of DbChange.

This change enables the manipulation of the DbChange instance before it gets interpreted and turned into SQL statements.

Only for Aggregate Roots events get fired, since these are the abstraction the repositories work on.
Insert and Update events got removed, since this distinction doesn't exist on the Aggregate Root level.
It only exists on the level of entities and/or tables which is represented by DbActions.

Improved some tests to properly check all the events triggered.
pull/13/merge
Jens Schauder 8 years ago committed by Greg Turnquist
parent
commit
1f2dc16d05
No known key found for this signature in database
GPG Key ID: CB2FA4D512B5C413
  1. 3
      src/main/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreter.java
  2. 5
      src/main/java/org/springframework/data/jdbc/core/EntityRowMapper.java
  3. 2
      src/main/java/org/springframework/data/jdbc/core/EventPublishingEntityRowMapper.java
  4. 66
      src/main/java/org/springframework/data/jdbc/core/JdbcEntityTemplate.java
  5. 6
      src/main/java/org/springframework/data/jdbc/mapping/event/AfterCreation.java
  6. 6
      src/main/java/org/springframework/data/jdbc/mapping/event/AfterDelete.java
  7. 37
      src/main/java/org/springframework/data/jdbc/mapping/event/AfterInsert.java
  8. 6
      src/main/java/org/springframework/data/jdbc/mapping/event/AfterSave.java
  9. 37
      src/main/java/org/springframework/data/jdbc/mapping/event/AfterUpdate.java
  10. 6
      src/main/java/org/springframework/data/jdbc/mapping/event/BeforeDelete.java
  11. 36
      src/main/java/org/springframework/data/jdbc/mapping/event/BeforeInsert.java
  12. 7
      src/main/java/org/springframework/data/jdbc/mapping/event/BeforeSave.java
  13. 51
      src/main/java/org/springframework/data/jdbc/mapping/event/BeforeUpdate.java
  14. 4
      src/main/java/org/springframework/data/jdbc/mapping/event/Identifier.java
  15. 6
      src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithEntity.java
  16. 5
      src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithId.java
  17. 5
      src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithIdAndEntity.java
  18. 9
      src/main/java/org/springframework/data/jdbc/mapping/event/SimpleJdbcEvent.java
  19. 3
      src/test/java/org/springframework/data/jdbc/core/EventPublishingEntityRowMapperUnitTests.java
  20. 232
      src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryManipulateDbActionsIntegrationTests.java
  21. 4
      src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryPropertyConversionIntegrationTests.java
  22. 55
      src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java
  23. 2
      src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryManipulateDbActionsIntegrationTests-hsql.sql
  24. 2
      src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryManipulateDbActionsIntegrationTests-mysql.sql
  25. 4
      src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryManipulateDbActionsIntegrationTests-postgres.sql

3
src/main/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreter.java

@ -25,7 +25,6 @@ import org.springframework.data.jdbc.core.conversion.DbAction.DeleteAll; @@ -25,7 +25,6 @@ import org.springframework.data.jdbc.core.conversion.DbAction.DeleteAll;
import org.springframework.data.jdbc.core.conversion.DbAction.Insert;
import org.springframework.data.jdbc.core.conversion.DbAction.Update;
import org.springframework.data.jdbc.core.conversion.Interpreter;
import org.springframework.data.jdbc.mapping.event.Identifier;
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity;
@ -74,7 +73,7 @@ class DefaultJdbcInterpreter implements Interpreter { @@ -74,7 +73,7 @@ class DefaultJdbcInterpreter implements Interpreter {
public <T> void interpret(Delete<T> delete) {
if (delete.getPropertyPath() == null) {
template.doDelete(Identifier.of(delete.getRootId()), Optional.ofNullable(delete.getEntity()),
template.doDelete(delete.getRootId(), Optional.ofNullable(delete.getEntity()),
delete.getEntityType());
} else {
template.doDelete(delete.getRootId(), delete.getPropertyPath());

5
src/main/java/org/springframework/data/jdbc/core/EntityRowMapper.java

@ -128,10 +128,11 @@ class EntityRowMapper<T> implements RowMapper<T> { @@ -128,10 +128,11 @@ class EntityRowMapper<T> implements RowMapper<T> {
return null;
}
String column = prefix + name;
try {
return conversionService.convert(resultSet.getObject(prefix + name), parameter.getType().getType());
return conversionService.convert(resultSet.getObject(column), parameter.getType().getType());
} catch (SQLException o_O) {
throw new MappingException(String.format("Couldn't read column %s from ResultSet.", name), o_O);
throw new MappingException(String.format("Couldn't read column %s from ResultSet.", column), o_O);
}
}
}

2
src/main/java/org/springframework/data/jdbc/core/EventPublishingEntityRowMapper.java

@ -50,7 +50,7 @@ public class EventPublishingEntityRowMapper<T> implements RowMapper<T> { @@ -50,7 +50,7 @@ public class EventPublishingEntityRowMapper<T> implements RowMapper<T> {
T instance = delegate.mapRow(resultSet, i);
publisher.publishEvent(new AfterCreation(Identifier.of(entityInformation.getRequiredId(instance)), instance));
publisher.publishEvent(new AfterCreation(Identifier.of(entityInformation.getRequiredId(instance)), instance, null));
return instance;
}

66
src/main/java/org/springframework/data/jdbc/core/JdbcEntityTemplate.java

@ -36,11 +36,9 @@ import org.springframework.data.jdbc.core.conversion.Interpreter; @@ -36,11 +36,9 @@ import org.springframework.data.jdbc.core.conversion.Interpreter;
import org.springframework.data.jdbc.core.conversion.JdbcEntityDeleteWriter;
import org.springframework.data.jdbc.core.conversion.JdbcEntityWriter;
import org.springframework.data.jdbc.mapping.event.AfterDelete;
import org.springframework.data.jdbc.mapping.event.AfterInsert;
import org.springframework.data.jdbc.mapping.event.AfterUpdate;
import org.springframework.data.jdbc.mapping.event.AfterSave;
import org.springframework.data.jdbc.mapping.event.BeforeDelete;
import org.springframework.data.jdbc.mapping.event.BeforeInsert;
import org.springframework.data.jdbc.mapping.event.BeforeUpdate;
import org.springframework.data.jdbc.mapping.event.BeforeSave;
import org.springframework.data.jdbc.mapping.event.Identifier;
import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
import org.springframework.data.jdbc.mapping.model.BasicJdbcPersistentEntityInformation;
@ -102,14 +100,29 @@ public class JdbcEntityTemplate implements JdbcEntityOperations { @@ -102,14 +100,29 @@ public class JdbcEntityTemplate implements JdbcEntityOperations {
@Override
public <T> void save(T instance, Class<T> domainType) {
createChange(instance).executeWith(interpreter);
JdbcPersistentEntityInformation<T, ?> entityInformation = context.getRequiredPersistentEntityInformation(domainType);
AggregateChange change = createChange(instance);
publisher.publishEvent(new BeforeSave( //
Identifier.ofNullable(entityInformation.getId(instance)), //
instance, //
change //
));
change.executeWith(interpreter);
publisher.publishEvent(new AfterSave( //
Identifier.of(entityInformation.getId(instance)), //
instance, //
change //
));
}
@Override
public <T> void insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) {
publisher.publishEvent(new BeforeInsert(instance));
KeyHolder holder = new GeneratedKeyHolder();
JdbcPersistentEntity<T> persistentEntity = getRequiredPersistentEntity(domainType);
JdbcPersistentEntityInformation<T, ?> entityInformation = context
@ -132,23 +145,17 @@ public class JdbcEntityTemplate implements JdbcEntityOperations { @@ -132,23 +145,17 @@ public class JdbcEntityTemplate implements JdbcEntityOperations {
throw new IllegalStateException(String.format(ENTITY_NEW_AFTER_INSERT, persistentEntity));
}
publisher.publishEvent(new AfterInsert(Identifier.of(entityInformation.getRequiredId(instance)), instance));
}
@Override
public <S> void update(S instance, Class<S> domainType) {
JdbcPersistentEntity<S> persistentEntity = getRequiredPersistentEntity(domainType);
JdbcPersistentEntityInformation<S, ?> entityInformation = context
.getRequiredPersistentEntityInformation(domainType);
Specified specifiedId = Identifier.of(entityInformation.getRequiredId(instance));
publisher.publishEvent(new BeforeUpdate(specifiedId, instance));
operations.update(sql(domainType).getUpdate(), getPropertyMap(instance, persistentEntity));
publisher.publishEvent(new AfterUpdate(specifiedId, instance));
}
@SuppressWarnings("ConstantConditions")
@Override
public long count(Class<?> domainType) {
return operations.getJdbcOperations().queryForObject(sql(domainType).getCount(), Long.class);
@ -211,9 +218,21 @@ public class JdbcEntityTemplate implements JdbcEntityOperations { @@ -211,9 +218,21 @@ public class JdbcEntityTemplate implements JdbcEntityOperations {
change.executeWith(interpreter);
}
void doDelete(Object rootId, PropertyPath propertyPath) {
private void deleteTree(Object id, Object entity, Class<?> domainType) {
JdbcPersistentEntity<?> entityToDelete = context.getRequiredPersistentEntity(propertyPath.getTypeInformation());
AggregateChange change = createDeletingChange(id, entity, domainType);
Specified specifiedId = Identifier.of(id);
Optional<Object> optionalEntity = Optional.ofNullable(entity);
publisher.publishEvent(new BeforeDelete(specifiedId, optionalEntity, change));
change.executeWith(interpreter);
publisher.publishEvent(new AfterDelete(specifiedId, optionalEntity, change));
}
void doDelete(Object rootId, PropertyPath propertyPath) {
JdbcPersistentEntity<?> rootEntity = context.getRequiredPersistentEntity(propertyPath.getOwningType());
@ -228,23 +247,12 @@ public class JdbcEntityTemplate implements JdbcEntityOperations { @@ -228,23 +247,12 @@ public class JdbcEntityTemplate implements JdbcEntityOperations {
}
void doDelete(Specified specifiedId, Optional<Object> optionalEntity, Class<?> domainType) {
publisher.publishEvent(new BeforeDelete(specifiedId, optionalEntity));
void doDelete(Object id, Optional<Object> optionalEntity, Class<?> domainType) {
String deleteByIdSql = sql(domainType).getDeleteById();
MapSqlParameterSource parameter = createIdParameterSource(specifiedId.getValue(), domainType);
MapSqlParameterSource parameter = createIdParameterSource(id, domainType);
operations.update(deleteByIdSql, parameter);
publisher.publishEvent(new AfterDelete(specifiedId, optionalEntity));
}
private void deleteTree(Object id, Object entity, Class<?> domainType) {
AggregateChange change = createDeletingChange(id, entity, domainType);
change.executeWith(interpreter);
}
private <T> AggregateChange createChange(T instance) {

6
src/main/java/org/springframework/data/jdbc/mapping/event/AfterCreation.java

@ -15,6 +15,7 @@ @@ -15,6 +15,7 @@
*/
package org.springframework.data.jdbc.mapping.event;
import org.springframework.data.jdbc.core.conversion.AggregateChange;
import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
/**
@ -31,8 +32,9 @@ public class AfterCreation extends JdbcEventWithIdAndEntity { @@ -31,8 +32,9 @@ public class AfterCreation extends JdbcEventWithIdAndEntity {
/**
* @param id of the entity
* @param entity the newly instantiated entity.
* @param change
*/
public AfterCreation(Specified id, Object entity) {
super(id, entity);
public AfterCreation(Specified id, Object entity, AggregateChange change) {
super(id, entity, change);
}
}

6
src/main/java/org/springframework/data/jdbc/mapping/event/AfterDelete.java

@ -17,6 +17,7 @@ package org.springframework.data.jdbc.mapping.event; @@ -17,6 +17,7 @@ package org.springframework.data.jdbc.mapping.event;
import java.util.Optional;
import org.springframework.data.jdbc.core.conversion.AggregateChange;
import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
/**
@ -33,8 +34,9 @@ public class AfterDelete extends JdbcEventWithId { @@ -33,8 +34,9 @@ public class AfterDelete extends JdbcEventWithId {
/**
* @param id of the entity.
* @param instance the deleted entity if it is available.
* @param change the {@link AggregateChange} encoding the planned actions to be performed on the database.
*/
public AfterDelete(Specified id, Optional<Object> instance) {
super(id, instance);
public AfterDelete(Specified id, Optional<Object> instance, AggregateChange change) {
super(id, instance, change);
}
}

37
src/main/java/org/springframework/data/jdbc/mapping/event/AfterInsert.java

@ -1,37 +0,0 @@ @@ -1,37 +0,0 @@
/*
* Copyright 2017 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
*
* http://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.mapping.event;
import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
/**
* Gets published after an entity got inserted into the database.
*
* @author Jens Schauder
* @since 2.0
*/
public class AfterInsert extends AfterSave {
private static final long serialVersionUID = 1312645717283677063L;
/**
* @param id identifier of the entity triggering the event.
* @param instance the newly inserted entity.
*/
public AfterInsert(Specified id, Object instance) {
super(id, instance);
}
}

6
src/main/java/org/springframework/data/jdbc/mapping/event/AfterSave.java

@ -15,6 +15,7 @@ @@ -15,6 +15,7 @@
*/
package org.springframework.data.jdbc.mapping.event;
import org.springframework.data.jdbc.core.conversion.AggregateChange;
import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
/**
@ -30,8 +31,9 @@ public class AfterSave extends JdbcEventWithIdAndEntity { @@ -30,8 +31,9 @@ public class AfterSave extends JdbcEventWithIdAndEntity {
/**
* @param id identifier of
* @param instance the newly saved entity.
* @param change the {@link AggregateChange} encoding the planned actions to be performed on the database.
*/
AfterSave(Specified id, Object instance) {
super(id, instance);
public AfterSave(Specified id, Object instance, AggregateChange change) {
super(id, instance, change);
}
}

37
src/main/java/org/springframework/data/jdbc/mapping/event/AfterUpdate.java

@ -1,37 +0,0 @@ @@ -1,37 +0,0 @@
/*
* Copyright 2017 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
*
* http://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.mapping.event;
import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
/**
* Gets published after an entity was updated in the database.
*
* @author Jens Schauder
* @since 2.0
*/
public class AfterUpdate extends AfterSave {
private static final long serialVersionUID = -1765706904721563399L;
/**
* @param id of the entity
* @param instance the updated entity.
*/
public AfterUpdate(Specified id, Object instance) {
super(id, instance);
}
}

6
src/main/java/org/springframework/data/jdbc/mapping/event/BeforeDelete.java

@ -17,6 +17,7 @@ package org.springframework.data.jdbc.mapping.event; @@ -17,6 +17,7 @@ package org.springframework.data.jdbc.mapping.event;
import java.util.Optional;
import org.springframework.data.jdbc.core.conversion.AggregateChange;
import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
/**
@ -32,8 +33,9 @@ public class BeforeDelete extends JdbcEventWithId { @@ -32,8 +33,9 @@ public class BeforeDelete extends JdbcEventWithId {
/**
* @param id the id of the entity
* @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> BeforeDelete(Specified id, Optional<Object> entity) {
super(id, entity);
public <T> BeforeDelete(Specified id, Optional<Object> entity, AggregateChange change) {
super(id, entity, change);
}
}

36
src/main/java/org/springframework/data/jdbc/mapping/event/BeforeInsert.java

@ -1,36 +0,0 @@ @@ -1,36 +0,0 @@
/*
* Copyright 2017 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
*
* http://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.mapping.event;
/**
* Gets published before an entity gets inserted into the database. When the id-property of the entity must get set
* manually, an event listener for this event may do so. <br>
* The {@link Identifier} is {@link Unset#UNSET}
*
* @author Jens Schauder
* @since 2.0
*/
public class BeforeInsert extends BeforeSave {
private static final long serialVersionUID = -5350552051337308870L;
/**
* @param instance the entity about to get inserted.
*/
public BeforeInsert(Object instance) {
super(Unset.UNSET, instance);
}
}

7
src/main/java/org/springframework/data/jdbc/mapping/event/BeforeSave.java

@ -15,6 +15,8 @@ @@ -15,6 +15,8 @@
*/
package org.springframework.data.jdbc.mapping.event;
import org.springframework.data.jdbc.core.conversion.AggregateChange;
/**
* Subclasses of this get published before an entity gets saved to the database.
*
@ -28,8 +30,9 @@ public class BeforeSave extends JdbcEventWithEntity { @@ -28,8 +30,9 @@ public class BeforeSave extends JdbcEventWithEntity {
/**
* @param id of the entity to be saved.
* @param instance the entity about to get saved.
* @param change
*/
BeforeSave(Identifier id, Object instance) {
super(id, instance);
public BeforeSave(Identifier id, Object instance, AggregateChange change) {
super(id, instance, change);
}
}

51
src/main/java/org/springframework/data/jdbc/mapping/event/BeforeUpdate.java

@ -1,51 +0,0 @@ @@ -1,51 +0,0 @@
/*
* Copyright 2017 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
*
* http://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.mapping.event;
import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
/**
* Gets published before an entity gets updated in the database.
*
* @author Jens Schauder
* @since 2.0
*/
public class BeforeUpdate extends BeforeSave implements WithId {
private static final long serialVersionUID = 794561215071613972L;
private final Specified id;
/**
* @param id of the entity about to get updated
* @param instance the entity about to get updated.
*/
public BeforeUpdate(Specified id, Object instance) {
super(id, instance);
this.id = id;
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.mapping.event.JdbcEvent#getId()
*/
@Override
public Specified getId() {
return id;
}
}

4
src/main/java/org/springframework/data/jdbc/mapping/event/Identifier.java

@ -40,6 +40,10 @@ public interface Identifier { @@ -40,6 +40,10 @@ public interface Identifier {
return SpecifiedIdentifier.of(identifier);
}
static Identifier ofNullable(Object identifier) {
return identifier == null ? Unset.UNSET : of(identifier);
}
/**
* Creates a new {@link Identifier} for the given optional source value.
*

6
src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithEntity.java

@ -17,6 +17,8 @@ package org.springframework.data.jdbc.mapping.event; @@ -17,6 +17,8 @@ package org.springframework.data.jdbc.mapping.event;
import java.util.Optional;
import org.springframework.data.jdbc.core.conversion.AggregateChange;
/**
* A {@link SimpleJdbcEvent} which is guaranteed to have an entity.
*
@ -27,7 +29,7 @@ public class JdbcEventWithEntity extends SimpleJdbcEvent implements WithEntity { @@ -27,7 +29,7 @@ public class JdbcEventWithEntity extends SimpleJdbcEvent implements WithEntity {
private static final long serialVersionUID = 4891455396602090638L;
public JdbcEventWithEntity(Identifier id, Object entity) {
super(id, Optional.of(entity));
public JdbcEventWithEntity(Identifier id, Object entity, AggregateChange change) {
super(id, Optional.of(entity), change);
}
}

5
src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithId.java

@ -17,6 +17,7 @@ package org.springframework.data.jdbc.mapping.event; @@ -17,6 +17,7 @@ package org.springframework.data.jdbc.mapping.event;
import java.util.Optional;
import org.springframework.data.jdbc.core.conversion.AggregateChange;
import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
/**
@ -31,9 +32,9 @@ public class JdbcEventWithId extends SimpleJdbcEvent implements WithId { @@ -31,9 +32,9 @@ public class JdbcEventWithId extends SimpleJdbcEvent implements WithId {
private final Specified id;
public JdbcEventWithId(Specified id, Optional<Object> entity) {
public JdbcEventWithId(Specified id, Optional<Object> entity, AggregateChange change) {
super(id, entity);
super(id, entity, change);
this.id = id;
}

5
src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithIdAndEntity.java

@ -19,6 +19,7 @@ import lombok.Getter; @@ -19,6 +19,7 @@ import lombok.Getter;
import java.util.Optional;
import org.springframework.data.jdbc.core.conversion.AggregateChange;
import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
/**
@ -32,7 +33,7 @@ public class JdbcEventWithIdAndEntity extends JdbcEventWithId implements WithEnt @@ -32,7 +33,7 @@ public class JdbcEventWithIdAndEntity extends JdbcEventWithId implements WithEnt
private static final long serialVersionUID = -3194462549552515519L;
public JdbcEventWithIdAndEntity(Specified id, Object entity) {
super(id, Optional.of(entity));
public JdbcEventWithIdAndEntity(Specified id, Object entity, AggregateChange change) {
super(id, Optional.of(entity), change);
}
}

9
src/main/java/org/springframework/data/jdbc/mapping/event/SimpleJdbcEvent.java

@ -18,6 +18,7 @@ package org.springframework.data.jdbc.mapping.event; @@ -18,6 +18,7 @@ package org.springframework.data.jdbc.mapping.event;
import java.util.Optional;
import org.springframework.context.ApplicationEvent;
import org.springframework.data.jdbc.core.conversion.AggregateChange;
/**
* The common superclass for all events published by JDBC repositories. {@link #getSource} contains the
@ -32,12 +33,14 @@ class SimpleJdbcEvent extends ApplicationEvent implements JdbcEvent { @@ -32,12 +33,14 @@ class SimpleJdbcEvent extends ApplicationEvent implements JdbcEvent {
private static final long serialVersionUID = -1798807778668751659L;
private final Object entity;
private final AggregateChange change;
SimpleJdbcEvent(Identifier id, Optional<Object> entity) {
SimpleJdbcEvent(Identifier id, Optional<Object> entity, AggregateChange change) {
super(id);
this.entity = entity.orElse(null);
this.change = change;
}
/*
@ -57,4 +60,8 @@ class SimpleJdbcEvent extends ApplicationEvent implements JdbcEvent { @@ -57,4 +60,8 @@ class SimpleJdbcEvent extends ApplicationEvent implements JdbcEvent {
public Optional<Object> getOptionalEntity() {
return Optional.ofNullable(entity);
}
public AggregateChange getChange() {
return change;
}
}

3
src/test/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapperUnitTests.java → src/test/java/org/springframework/data/jdbc/core/EventPublishingEntityRowMapperUnitTests.java

@ -13,7 +13,7 @@ @@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jdbc.repository;
package org.springframework.data.jdbc.core;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@ -31,7 +31,6 @@ import org.mockito.Mock; @@ -31,7 +31,6 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.core.EventPublishingEntityRowMapper;
import org.springframework.data.jdbc.mapping.event.AfterCreation;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntityInformation;
import org.springframework.jdbc.core.RowMapper;

232
src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryManipulateDbActionsIntegrationTests.java

@ -0,0 +1,232 @@ @@ -0,0 +1,232 @@
/*
* Copyright 2017 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
*
* http://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.core.conversion.DbAction;
import org.springframework.data.jdbc.mapping.event.BeforeDelete;
import org.springframework.data.jdbc.mapping.event.BeforeSave;
import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories;
import org.springframework.data.jdbc.testing.TestConfiguration;
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
*/
@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 {
final @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 final 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<BeforeDelete> softDeleteListener() {
return event -> {
DummyEntity entity = (DummyEntity) event.getOptionalEntity().orElseThrow(AssertionFailedError::new);
entity.deleted = true;
List<DbAction> actions = event.getChange().getActions();
actions.clear();
actions.add(DbAction.update(entity, null));
};
}
@Bean
ApplicationListener<BeforeSave> logOnSaveListener() {
// this would actually be easier to implement with an AfterSave 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(lastLogId);
log.entity = entity;
log.text = entity.name + " saved";
List<DbAction> actions = event.getChange().getActions();
actions.add(DbAction.insert(log, null));
};
}
}
}

4
src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryPropertyConversionIntegrationTests.java

@ -40,7 +40,7 @@ import org.springframework.context.annotation.Bean; @@ -40,7 +40,7 @@ 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.jdbc.mapping.event.BeforeInsert;
import org.springframework.data.jdbc.mapping.event.BeforeSave;
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
import org.springframework.data.jdbc.testing.TestConfiguration;
import org.springframework.data.repository.CrudRepository;
@ -77,7 +77,7 @@ public class JdbcRepositoryPropertyConversionIntegrationTests { @@ -77,7 +77,7 @@ public class JdbcRepositoryPropertyConversionIntegrationTests {
@Bean
ApplicationListener applicationListener() {
return (ApplicationListener<BeforeInsert>) beforeInsert -> ((EntityWithColumnsRequiringConversions) beforeInsert
return (ApplicationListener<BeforeSave>) beforeInsert -> ((EntityWithColumnsRequiringConversions) beforeInsert
.getEntity()).setIdTimestamp(getNow());
}

55
src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java

@ -2,26 +2,27 @@ package org.springframework.data.jdbc.repository; @@ -2,26 +2,27 @@ package org.springframework.data.jdbc.repository;
import static java.util.Arrays.*;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.*;
import junit.framework.AssertionFailedError;
import lombok.Data;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.assertj.core.groups.Tuple;
import org.junit.Before;
import org.junit.Test;
import org.mockito.stubbing.Answer;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.mapping.event.AfterDelete;
import org.springframework.data.jdbc.mapping.event.AfterInsert;
import org.springframework.data.jdbc.mapping.event.AfterUpdate;
import org.springframework.data.jdbc.mapping.event.AfterSave;
import org.springframework.data.jdbc.mapping.event.BeforeDelete;
import org.springframework.data.jdbc.mapping.event.BeforeInsert;
import org.springframework.data.jdbc.mapping.event.BeforeUpdate;
import org.springframework.data.jdbc.mapping.event.BeforeSave;
import org.springframework.data.jdbc.mapping.event.Identifier;
import org.springframework.data.jdbc.mapping.event.JdbcEvent;
import org.springframework.data.jdbc.mapping.model.DefaultNamingStrategy;
@ -55,8 +56,12 @@ public class SimpleJdbcRepositoryEventsUnitTests { @@ -55,8 +56,12 @@ public class SimpleJdbcRepositoryEventsUnitTests {
repository.save(entity);
assertThat(publisher.events.get(0)).isInstanceOf(BeforeUpdate.class);
assertThat(publisher.events.get(1)).isInstanceOf(AfterUpdate.class);
assertThat(publisher.events) //
.extracting(e -> (Class) e.getClass()) //
.containsExactly( //
BeforeSave.class, //
AfterSave.class //
);
}
@Test // DATAJDBC-99
@ -67,10 +72,14 @@ public class SimpleJdbcRepositoryEventsUnitTests { @@ -67,10 +72,14 @@ public class SimpleJdbcRepositoryEventsUnitTests {
repository.saveAll(asList(entity1, entity2));
assertThat(publisher.events.get(0)).isInstanceOf(BeforeInsert.class);
assertThat(publisher.events.get(1)).isInstanceOf(AfterInsert.class);
assertThat(publisher.events.get(2)).isInstanceOf(BeforeUpdate.class);
assertThat(publisher.events.get(3)).isInstanceOf(AfterUpdate.class);
assertThat(publisher.events) //
.extracting(e -> (Class) e.getClass()) //
.containsExactly( //
BeforeSave.class, //
AfterSave.class, //
BeforeSave.class, //
AfterSave.class //
);
}
@Test // DATAJDBC-99
@ -80,14 +89,14 @@ public class SimpleJdbcRepositoryEventsUnitTests { @@ -80,14 +89,14 @@ public class SimpleJdbcRepositoryEventsUnitTests {
repository.delete(entity);
assertThat(publisher.events.get(0)).isInstanceOf(BeforeDelete.class);
assertThat(publisher.events.get(1)).isInstanceOf(AfterDelete.class);
assertThat(publisher.events.get(0).getOptionalEntity()).hasValue(entity);
assertThat(publisher.events.get(1).getOptionalEntity()).hasValue(entity);
assertThat(publisher.events.get(0).getId()).isEqualTo(Identifier.of(23L));
assertThat(publisher.events.get(1).getId()).isEqualTo(Identifier.of(23L));
assertThat(publisher.events).extracting( //
e -> (Class) e.getClass(), //
e -> e.getOptionalEntity().orElseGet(AssertionFailedError::new), //
JdbcEvent::getId //
).containsExactly( //
Tuple.tuple(BeforeDelete.class, entity, Identifier.of(23L)), //
Tuple.tuple(AfterDelete.class, entity, Identifier.of(23L)) //
);
}
@Test // DATAJDBC-99
@ -95,8 +104,12 @@ public class SimpleJdbcRepositoryEventsUnitTests { @@ -95,8 +104,12 @@ public class SimpleJdbcRepositoryEventsUnitTests {
repository.deleteById(23L);
assertThat(publisher.events.get(0)).isInstanceOf(BeforeDelete.class);
assertThat(publisher.events.get(1)).isInstanceOf(AfterDelete.class);
assertThat(publisher.events) //
.extracting(e -> (Class) e.getClass()) //
.containsExactly( //
BeforeDelete.class, //
AfterDelete.class //
);
}
private static NamedParameterJdbcOperations createIdGeneratingOperations() {

2
src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryManipulateDbActionsIntegrationTests-hsql.sql

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
CREATE TABLE dummyentity ( id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, NAME VARCHAR(100), DELETED CHAR(1), log BIGINT);
CREATE TABLE log ( id BIGINT, TEXT VARCHAR(100));

2
src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryManipulateDbActionsIntegrationTests-mysql.sql

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
CREATE TABLE dummyentity ( id BIGINT AUTO_INCREMENT PRIMARY KEY, NAME VARCHAR(100), DELETED CHAR(1), log BIGINT);
CREATE TABLE log ( id BIGINT, TEXT VARCHAR(100));

4
src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryManipulateDbActionsIntegrationTests-postgres.sql

@ -0,0 +1,4 @@ @@ -0,0 +1,4 @@
DROP TABLE dummyentity;
DROP TABLE log;
CREATE TABLE dummyentity ( id SERIAL PRIMARY KEY, NAME VARCHAR(100), DELETED CHAR(5), log BIGINT);
CREATE TABLE log ( id BIGINT, TEXT VARCHAR(100));
Loading…
Cancel
Save