Browse Source
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.pull/202/head
24 changed files with 261 additions and 672 deletions
@ -1,232 +0,0 @@
@@ -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)); |
||||
}; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,71 @@
@@ -0,0 +1,71 @@
|
||||
/* |
||||
* 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.relational.core.mapping.event; |
||||
|
||||
import org.springframework.context.ApplicationEvent; |
||||
import org.springframework.data.relational.core.conversion.AggregateChange; |
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* Super class for events produced during deleting an aggregate. Such events have an {@link Identifier} and an |
||||
* {@link AggregateChange} and may also have an entity if the entity was provided to the method performing the delete. |
||||
* |
||||
* @author Jens Schauder |
||||
*/ |
||||
abstract public class RelationalDeleteEvent extends ApplicationEvent implements WithId, WithAggregateChange { |
||||
|
||||
private static final long serialVersionUID = -8071323168471611098L; |
||||
|
||||
private final Identifier id; |
||||
@Nullable private final Object entity; |
||||
private final AggregateChange<?> change; |
||||
|
||||
/** |
||||
* @param id the identifier of the aggregate that gets deleted. Must not be {@literal null}. |
||||
* @param entity is the aggregate root that gets deleted. Might be {@literal null}. |
||||
* @param change the {@link AggregateChange} for the deletion containing more detailed information about the deletion |
||||
* process. |
||||
*/ |
||||
RelationalDeleteEvent(Identifier id, @Nullable Object entity, AggregateChange<?> change) { |
||||
|
||||
super(id); |
||||
|
||||
Assert.notNull(id, "Id must not be null."); |
||||
Assert.notNull(change, "Change must not be null."); |
||||
|
||||
this.id = id; |
||||
this.entity = entity; |
||||
this.change = change; |
||||
} |
||||
|
||||
@Override |
||||
public Identifier getId() { |
||||
return id; |
||||
} |
||||
|
||||
@Override |
||||
@Nullable |
||||
public Object getEntity() { |
||||
return entity; |
||||
} |
||||
|
||||
@Override |
||||
public AggregateChange<?> getAggregateChange() { |
||||
return change; |
||||
} |
||||
|
||||
} |
||||
@ -1,52 +0,0 @@
@@ -1,52 +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.relational.core.mapping.event; |
||||
|
||||
import java.util.Optional; |
||||
|
||||
import org.springframework.data.relational.core.conversion.AggregateChange; |
||||
import org.springframework.data.relational.core.mapping.event.Identifier.Specified; |
||||
import org.springframework.lang.Nullable; |
||||
|
||||
/** |
||||
* A {@link SimpleRelationalEvent} guaranteed to have an identifier. |
||||
* |
||||
* @author Jens Schauder |
||||
*/ |
||||
public class RelationalEventWithId extends SimpleRelationalEvent implements WithId { |
||||
|
||||
private static final long serialVersionUID = -8071323168471611098L; |
||||
|
||||
private final Specified id; |
||||
|
||||
public RelationalEventWithId(Specified id, Optional<?> entity, @Nullable AggregateChange change) { |
||||
|
||||
super(id, entity, change); |
||||
|
||||
this.id = id; |
||||
} |
||||
|
||||
/** |
||||
* Events with an identifier will always return a {@link Specified} one. |
||||
* |
||||
* @deprecated since 1.1, obtain the id from the entity instead. |
||||
*/ |
||||
@Override |
||||
@Deprecated |
||||
public Specified getId() { |
||||
return id; |
||||
} |
||||
} |
||||
@ -1,39 +0,0 @@
@@ -1,39 +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.relational.core.mapping.event; |
||||
|
||||
import lombok.Getter; |
||||
|
||||
import java.util.Optional; |
||||
|
||||
import org.springframework.data.relational.core.conversion.AggregateChange; |
||||
import org.springframework.data.relational.core.mapping.event.Identifier.Specified; |
||||
import org.springframework.lang.Nullable; |
||||
|
||||
/** |
||||
* A {@link SimpleRelationalEvent} which is guaranteed to have an identifier and an entity. |
||||
* |
||||
* @author Jens Schauder |
||||
*/ |
||||
@Getter |
||||
public class RelationalEventWithIdAndEntity extends RelationalEventWithId implements WithEntity { |
||||
|
||||
private static final long serialVersionUID = -3194462549552515519L; |
||||
|
||||
public RelationalEventWithIdAndEntity(Specified id, Object entity, @Nullable AggregateChange change) { |
||||
super(id, Optional.of(entity), change); |
||||
} |
||||
} |
||||
@ -0,0 +1,41 @@
@@ -0,0 +1,41 @@
|
||||
package org.springframework.data.relational.core.mapping.event;/* |
||||
* Copyright 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. |
||||
*/ |
||||
|
||||
import org.springframework.data.relational.core.conversion.AggregateChange; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* Events triggered during saving of an aggregate. |
||||
* Events of this type always have an {@link AggregateChange} and an entity. |
||||
*/ |
||||
public abstract class RelationalSaveEvent extends RelationalEventWithEntity implements WithAggregateChange{ |
||||
|
||||
private final AggregateChange<?> change; |
||||
|
||||
RelationalSaveEvent(Object entity, AggregateChange<?> change) { |
||||
|
||||
super(entity); |
||||
|
||||
Assert.notNull(change, "Change must not be null"); |
||||
|
||||
this.change = change; |
||||
} |
||||
|
||||
@Override |
||||
public AggregateChange<?> getAggregateChange() { |
||||
return change; |
||||
} |
||||
} |
||||
@ -1,76 +0,0 @@
@@ -1,76 +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.relational.core.mapping.event; |
||||
|
||||
import java.util.Optional; |
||||
|
||||
import org.springframework.context.ApplicationEvent; |
||||
import org.springframework.data.relational.core.conversion.AggregateChange; |
||||
import org.springframework.lang.Nullable; |
||||
|
||||
/** |
||||
* The common superclass for all events published by JDBC repositories. {@link #getSource} contains the |
||||
* {@link Identifier} of the entity triggering the event. |
||||
* |
||||
* @author Jens Schauder |
||||
* @author Oliver Gierke |
||||
*/ |
||||
class SimpleRelationalEvent extends ApplicationEvent implements RelationalEvent { |
||||
|
||||
private static final long serialVersionUID = -1798807778668751659L; |
||||
|
||||
private final Object entity; |
||||
private final AggregateChange change; |
||||
|
||||
SimpleRelationalEvent(Identifier id, Optional<?> entity, @Nullable AggregateChange change) { |
||||
|
||||
super(id); |
||||
|
||||
this.entity = entity.orElse(null); |
||||
this.change = change; |
||||
} |
||||
|
||||
/** |
||||
* @deprecated since 1.1, obtain the id from the entity instead. |
||||
*/ |
||||
@Override |
||||
@Deprecated |
||||
public Identifier getId() { |
||||
return (Identifier) getSource(); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.jdbc.core.mapping.event.JdbcEvent#getOptionalEntity() |
||||
*/ |
||||
@Override |
||||
public Optional<Object> getOptionalEntity() { |
||||
return Optional.ofNullable(entity); |
||||
} |
||||
|
||||
/** |
||||
* Returns the an {@link AggregateChange} instance representing the SQL statements performed by the action that |
||||
* triggered this event. |
||||
* |
||||
* @return Guaranteed to be not {@literal null}. |
||||
* @deprecated There is currently no replacement for this. If something like this is required please create an issue |
||||
* outlining your use case. |
||||
*/ |
||||
@Deprecated |
||||
public AggregateChange getChange() { |
||||
return change; |
||||
} |
||||
} |
||||
@ -1,44 +0,0 @@
@@ -1,44 +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.relational.core.mapping.event; |
||||
|
||||
import lombok.NonNull; |
||||
import lombok.Value; |
||||
|
||||
import java.util.Optional; |
||||
|
||||
import org.springframework.data.relational.core.mapping.event.Identifier.Specified; |
||||
|
||||
/** |
||||
* Simple value object for {@link Specified}. |
||||
* |
||||
* @author Jens Schauder |
||||
* @author Oliver Gierke |
||||
*/ |
||||
@Value(staticConstructor = "of") |
||||
class SpecifiedIdentifier implements Specified { |
||||
|
||||
@NonNull Object value; |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.jdbc.core.mapping.event.Identifier#getOptionalValue() |
||||
*/ |
||||
@Override |
||||
public Optional<?> getOptionalValue() { |
||||
return Optional.of(value); |
||||
} |
||||
} |
||||
@ -1,57 +0,0 @@
@@ -1,57 +0,0 @@
|
||||
/* |
||||
* Copyright 2018-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.relational.core.mapping.event; |
||||
|
||||
import static org.assertj.core.api.Assertions.*; |
||||
|
||||
import java.util.Optional; |
||||
|
||||
import org.junit.Test; |
||||
|
||||
/** |
||||
* Unit tests for {@link Identifier} |
||||
* |
||||
* @author Jens Schauder |
||||
*/ |
||||
public class IdentifierUnitTests { |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
@Test |
||||
public void specifiedOffersTheIdentifierValue() { |
||||
|
||||
Identifier.Specified identifier = Identifier.of("x"); |
||||
|
||||
assertThat(identifier.getValue()).isEqualTo("x"); |
||||
assertThat((Optional<Object>) identifier.getOptionalValue()).contains("x"); |
||||
} |
||||
|
||||
@Test |
||||
public void indentifierOfNullHasEmptyValue() { |
||||
|
||||
Identifier identifier = Identifier.ofNullable(null); |
||||
|
||||
assertThat(identifier.getOptionalValue()).isEmpty(); |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
@Test |
||||
public void indentifierOfXHasValueX() { |
||||
|
||||
Identifier identifier = Identifier.ofNullable("x"); |
||||
|
||||
assertThat((Optional<Object>) identifier.getOptionalValue()).hasValue("x"); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue