diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateTemplate.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateTemplate.java index 02a21b58..eab91a0d 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateTemplate.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateTemplate.java @@ -208,7 +208,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations { T entity = accessStrategy.findById(id, domainType); if (entity != null) { - return triggerAfterLoad(entity); + return triggerAfterConvert(entity); } return entity; } @@ -236,7 +236,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations { Assert.notNull(domainType, "Domain type must not be null!"); Iterable all = accessStrategy.findAll(domainType, sort); - return triggerAfterLoad(all); + return triggerAfterConvert(all); } /* @@ -248,7 +248,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations { Assert.notNull(domainType, "Domain type must not be null!"); - Iterable items = triggerAfterLoad(accessStrategy.findAll(domainType, pageable)); + Iterable items = triggerAfterConvert(accessStrategy.findAll(domainType, pageable)); List content = StreamSupport.stream(items.spliterator(), false).collect(Collectors.toList()); return PageableExecutionUtils.getPage(content, pageable, () -> accessStrategy.count(domainType)); @@ -264,7 +264,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations { Assert.notNull(domainType, "Domain type must not be null!"); Iterable all = accessStrategy.findAll(domainType); - return triggerAfterLoad(all); + return triggerAfterConvert(all); } /* @@ -278,7 +278,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations { Assert.notNull(domainType, "Domain type must not be null!"); Iterable allById = accessStrategy.findAllById(ids, domainType); - return triggerAfterLoad(allById); + return triggerAfterConvert(allById); } /* @@ -385,22 +385,24 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations { return aggregateChange; } - private Iterable triggerAfterLoad(Iterable all) { + private Iterable triggerAfterConvert(Iterable all) { List result = new ArrayList<>(); for (T e : all) { - result.add(triggerAfterLoad(e)); + result.add(triggerAfterConvert(e)); } return result; } - private T triggerAfterLoad(T entity) { + private T triggerAfterConvert(T entity) { publisher.publishEvent(new AfterLoadEvent<>(entity)); + publisher.publishEvent(new AfterConvertEvent<>(entity)); - return entityCallbacks.callback(AfterLoadCallback.class, entity); + entity = entityCallbacks.callback(AfterLoadCallback.class, entity); + return entityCallbacks.callback(AfterConvertCallback.class, entity); } private T triggerBeforeConvert(T aggregateRoot) { diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java index e9ee9d36..76e23366 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java @@ -32,6 +32,8 @@ import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.relational.core.dialect.Dialect; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; +import org.springframework.data.relational.core.mapping.event.AfterConvertCallback; +import org.springframework.data.relational.core.mapping.event.AfterConvertEvent; import org.springframework.data.relational.core.mapping.event.AfterLoadCallback; import org.springframework.data.relational.core.mapping.event.AfterLoadEvent; import org.springframework.data.repository.core.NamedQueries; @@ -157,9 +159,11 @@ class JdbcQueryLookupStrategy implements QueryLookupStrategy { if (entity != null) { publisher.publishEvent(new AfterLoadEvent<>(entity)); + publisher.publishEvent(new AfterConvertEvent<>(entity)); if (callbacks != null) { - return callbacks.callback(AfterLoadCallback.class, entity); + entity = callbacks.callback(AfterLoadCallback.class, entity); + return callbacks.callback(AfterConvertCallback.class, entity); } } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateUnitTests.java index 9d5dc731..2362db62 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateUnitTests.java @@ -41,6 +41,7 @@ import org.springframework.data.relational.core.conversion.MutableAggregateChang 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.data.relational.core.mapping.event.AfterConvertCallback; 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; @@ -136,7 +137,9 @@ public class JdbcAggregateTemplateUnitTests { 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(alfred2), any())).thenReturn(alfred2); when(callbacks.callback(any(Class.class), eq(neumann1), any())).thenReturn(neumann2); + when(callbacks.callback(any(Class.class), eq(neumann2), any())).thenReturn(neumann2); Iterable all = template.findAll(SampleEntity.class); @@ -158,12 +161,16 @@ public class JdbcAggregateTemplateUnitTests { when(dataAccessStrategy.findAll(SampleEntity.class, Sort.by("name"))).thenReturn(asList(alfred1, neumann1)); when(callbacks.callback(any(Class.class), eq(alfred1), any())).thenReturn(alfred2); + when(callbacks.callback(any(Class.class), eq(alfred2), any())).thenReturn(alfred2); when(callbacks.callback(any(Class.class), eq(neumann1), any())).thenReturn(neumann2); + when(callbacks.callback(any(Class.class), eq(neumann2), any())).thenReturn(neumann2); Iterable all = template.findAll(SampleEntity.class, Sort.by("name")); verify(callbacks).callback(AfterLoadCallback.class, alfred1); + verify(callbacks).callback(AfterConvertCallback.class, alfred2); verify(callbacks).callback(AfterLoadCallback.class, neumann1); + verify(callbacks).callback(AfterConvertCallback.class, neumann2); assertThat(all).containsExactly(alfred2, neumann2); } @@ -180,12 +187,16 @@ public class JdbcAggregateTemplateUnitTests { when(dataAccessStrategy.findAll(SampleEntity.class, PageRequest.of(0, 20))).thenReturn(asList(alfred1, neumann1)); when(callbacks.callback(any(Class.class), eq(alfred1), any())).thenReturn(alfred2); + when(callbacks.callback(any(Class.class), eq(alfred2), any())).thenReturn(alfred2); when(callbacks.callback(any(Class.class), eq(neumann1), any())).thenReturn(neumann2); + when(callbacks.callback(any(Class.class), eq(neumann2), any())).thenReturn(neumann2); Iterable all = template.findAll(SampleEntity.class, PageRequest.of(0, 20)); verify(callbacks).callback(AfterLoadCallback.class, alfred1); + verify(callbacks).callback(AfterConvertCallback.class, alfred2); verify(callbacks).callback(AfterLoadCallback.class, neumann1); + verify(callbacks).callback(AfterConvertCallback.class, neumann2); assertThat(all).containsExactly(alfred2, neumann2); } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java index 44def8a4..66201877 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java @@ -59,6 +59,7 @@ import org.springframework.data.jdbc.testing.EnabledOnFeature; import org.springframework.data.jdbc.testing.TestConfiguration; import org.springframework.data.jdbc.testing.TestDatabaseFeatures; import org.springframework.data.relational.core.mapping.event.AbstractRelationalEvent; +import org.springframework.data.relational.core.mapping.event.AfterConvertEvent; import org.springframework.data.relational.core.mapping.event.AfterLoadEvent; import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.core.NamedQueries; @@ -309,7 +310,7 @@ public class JdbcRepositoryIntegrationTests { repository.findAllWithSql(); - assertThat(eventListener.events).hasSize(1).hasOnlyElementsOfType(AfterLoadEvent.class); + assertThat(eventListener.events).hasSize(2).hasOnlyElementsOfTypes(AfterLoadEvent.class, AfterConvertEvent.class); } @Test // DATAJDBC-318 diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java index 5ab000f8..31d51e86 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java @@ -51,15 +51,7 @@ import org.springframework.data.relational.core.dialect.Dialect; import org.springframework.data.relational.core.dialect.H2Dialect; import org.springframework.data.relational.core.dialect.HsqlDbDialect; import org.springframework.data.relational.core.mapping.RelationalMappingContext; -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.BeforeConvertEvent; -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.relational.core.mapping.event.*; import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; @@ -198,7 +190,9 @@ public class SimpleJdbcRepositoryEventsUnitTests { .extracting(e -> (Class) e.getClass()) // .containsExactly( // AfterLoadEvent.class, // - AfterLoadEvent.class // + AfterConvertEvent.class, // + AfterLoadEvent.class, // + AfterConvertEvent.class // ); } @@ -217,7 +211,9 @@ public class SimpleJdbcRepositoryEventsUnitTests { .extracting(e -> (Class) e.getClass()) // .containsExactly( // AfterLoadEvent.class, // - AfterLoadEvent.class // + AfterConvertEvent.class, // + AfterLoadEvent.class, // + AfterConvertEvent.class // ); } @@ -234,7 +230,8 @@ public class SimpleJdbcRepositoryEventsUnitTests { assertThat(publisher.events) // .extracting(e -> (Class) e.getClass()) // .containsExactly( // - AfterLoadEvent.class // + AfterLoadEvent.class, // + AfterConvertEvent.class // ); } @@ -253,7 +250,9 @@ public class SimpleJdbcRepositoryEventsUnitTests { .extracting(e -> (Class) e.getClass()) // .containsExactly( // AfterLoadEvent.class, // - AfterLoadEvent.class // + AfterConvertEvent.class, // + AfterLoadEvent.class, // + AfterConvertEvent.class // ); } @@ -273,7 +272,9 @@ public class SimpleJdbcRepositoryEventsUnitTests { .extracting(e -> (Class) e.getClass()) // .containsExactly( // AfterLoadEvent.class, // - AfterLoadEvent.class // + AfterConvertEvent.class, // + AfterLoadEvent.class, // + AfterConvertEvent.class // ); } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AbstractRelationalEventListener.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AbstractRelationalEventListener.java index 99be6dc8..dee5ae0e 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AbstractRelationalEventListener.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AbstractRelationalEventListener.java @@ -57,6 +57,8 @@ public class AbstractRelationalEventListener implements ApplicationListener) event); + } else if (event instanceof AfterConvertEvent) { + onAfterConvert((AfterConvertEvent) event); } else if (event instanceof AfterDeleteEvent) { onAfterDelete((AfterDeleteEvent) event); } else if (event instanceof AfterSaveEvent) { @@ -110,6 +112,7 @@ public class AbstractRelationalEventListener implements ApplicationListener event) { @@ -118,6 +121,18 @@ public class AbstractRelationalEventListener implements ApplicationListener event) { + + if (LOG.isDebugEnabled()) { + LOG.debug("onAfterConvert({})", event.getEntity()); + } + } + /** * Captures {@link AfterDeleteEvent}. * diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AfterConvertCallback.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AfterConvertCallback.java new file mode 100644 index 00000000..f2ca2ec7 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AfterConvertCallback.java @@ -0,0 +1,37 @@ +/* + * Copyright 2019-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.relational.core.mapping.event; + +import org.springframework.data.mapping.callback.EntityCallback; + +/** + * An {@link EntityCallback} that gets invoked after an aggregate was converted from the database into an entity. + * + * @author Jens Schauder + * @since 2.6 + */ +@FunctionalInterface +public interface AfterConvertCallback extends EntityCallback { + + /** + * Entity callback method invoked after an aggregate root was converted. Can return either the same or a modified + * instance of the domain object. + * + * @param aggregate the converted aggregate. + * @return the converted and possibly modified aggregate. + */ + T onAfterConvert(T aggregate); +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AfterConvertEvent.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AfterConvertEvent.java new file mode 100644 index 00000000..c47b003b --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AfterConvertEvent.java @@ -0,0 +1,35 @@ +/* + * Copyright 2017-2021 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; + +/** + * Gets published after instantiation and setting of all the properties of an entity. This allows to do some + * postprocessing of entities if the entities are mutable. For immutable entities use {@link AfterConvertCallback}. + * + * @author Jens Schauder + * @since 2.6 + */ +public class AfterConvertEvent extends RelationalEventWithEntity { + + private static final long serialVersionUID = 7343072117054666699L; + + /** + * @param entity the newly instantiated entity. Must not be {@literal null}. + */ + public AfterConvertEvent(E entity) { + super(entity); + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AfterLoadCallback.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AfterLoadCallback.java index 39c8c898..4f7589e2 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AfterLoadCallback.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AfterLoadCallback.java @@ -23,7 +23,9 @@ import org.springframework.data.mapping.callback.EntityCallback; * @author Jens Schauder * @author Mark Paluch * @since 1.1 + * @deprecated Use {@link AfterConvertCallback} instead. */ +@Deprecated @FunctionalInterface public interface AfterLoadCallback extends EntityCallback { diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AfterLoadEvent.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AfterLoadEvent.java index 21048a52..c1a03468 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AfterLoadEvent.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AfterLoadEvent.java @@ -20,7 +20,9 @@ package org.springframework.data.relational.core.mapping.event; * postprocessing of entities if the entities are mutable. For immutable entities use {@link AfterLoadCallback}. * * @author Jens Schauder + * @deprecated Use {@link AfterConvertEvent} instead. */ +@Deprecated public class AfterLoadEvent extends RelationalEventWithEntity { private static final long serialVersionUID = 7343072117054666699L; diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/event/AbstractRelationalEventListenerUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/event/AbstractRelationalEventListenerUnitTests.java index 64b1a275..3f829247 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/event/AbstractRelationalEventListenerUnitTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/event/AbstractRelationalEventListenerUnitTests.java @@ -43,6 +43,14 @@ public class AbstractRelationalEventListenerUnitTests { assertThat(events).containsExactly("afterLoad"); } + @Test // GH-1053 + public void afterConvert() { + + listener.onApplicationEvent(new AfterConvertEvent<>(dummyEntity)); + + assertThat(events).containsExactly("afterConvert"); + } + @Test // DATAJDBC-454 public void beforeConvert() { @@ -122,6 +130,11 @@ public class AbstractRelationalEventListenerUnitTests { events.add("afterLoad"); } + @Override + protected void onAfterConvert(AfterConvertEvent event) { + events.add("afterConvert"); + } + @Override protected void onAfterDelete(AfterDeleteEvent event) { events.add("afterDelete"); diff --git a/src/main/asciidoc/jdbc.adoc b/src/main/asciidoc/jdbc.adoc index 8d5590f1..be343568 100644 --- a/src/main/asciidoc/jdbc.adoc +++ b/src/main/asciidoc/jdbc.adoc @@ -871,6 +871,9 @@ The following table describes the available events: | After an aggregate root gets saved (that is, inserted or updated). | {javadoc-base}org/springframework/data/relational/core/mapping/event/AfterLoadEvent.html[`AfterLoadEvent`] +| After an aggregate root gets created from a database `ResultSet` and all its properties get set. _Note: This is deprecated. Use `AfterConvert` instead_ + +| {javadoc-base}org/springframework/data/relational/core/mapping/event/AfterConvertEvent.html[`AfterConvertEvent`] | After an aggregate root gets created from a database `ResultSet` and all its properties get set. |=== @@ -903,6 +906,9 @@ This is the correct callback if you want to set an id programmatically. | After an aggregate root gets saved (that is, inserted or updated). | {javadoc-base}org/springframework/data/relational/core/mapping/event/AfterLoadCallback.html[`AfterLoadCallback`] +| After an aggregate root gets created from a database `ResultSet` and all its property get set. _This is deprecated, use `AfterConvertCallback` instead_ + +| {javadoc-base}org/springframework/data/relational/core/mapping/event/AfterConvertCallback.html[`AfterConvertCallback`] | After an aggregate root gets created from a database `ResultSet` and all its property get set. |===