mirror of
https://github.com/google/nomulus.git
synced 2025-07-09 12:43:24 +02:00
Add a listener to invoke entity callbacks (#551)
* Add a listener to invoke entity callbacks * Resolve comments * Add test
This commit is contained in:
parent
ec22d4d1a0
commit
9db4d1a082
3 changed files with 505 additions and 0 deletions
|
@ -0,0 +1,196 @@
|
||||||
|
// Copyright 2020 The Nomulus Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// 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 google.registry.persistence;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import javax.persistence.Embeddable;
|
||||||
|
import javax.persistence.Embedded;
|
||||||
|
import javax.persistence.MappedSuperclass;
|
||||||
|
import javax.persistence.PostLoad;
|
||||||
|
import javax.persistence.PostPersist;
|
||||||
|
import javax.persistence.PostRemove;
|
||||||
|
import javax.persistence.PostUpdate;
|
||||||
|
import javax.persistence.PrePersist;
|
||||||
|
import javax.persistence.PreRemove;
|
||||||
|
import javax.persistence.PreUpdate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A listener class to invoke entity callbacks in cases where Hibernate doesn't invoke the callback
|
||||||
|
* as expected.
|
||||||
|
*
|
||||||
|
* <p>JPA defines a few annotations, e.g. {@link PostLoad}, that we can use for the application to
|
||||||
|
* react to certain events that occur inside the persistence mechanism. However, Hibernate only
|
||||||
|
* supports a few basic use cases, e.g. defining a {@link PostLoad} method directly in an {@link
|
||||||
|
* javax.persistence.Entity} class or in an {@link Embeddable} class. If the annotated method is
|
||||||
|
* defined in an {@link Embeddable} class that is a property of another {@link Embeddable} class, or
|
||||||
|
* it is defined in a parent class of the {@link Embeddable} class, Hibernate doesn't invoke it.
|
||||||
|
*
|
||||||
|
* <p>This listener is added in core/src/main/resources/META-INF/orm.xml as a default entity
|
||||||
|
* listener whose annotated methods will be invoked by Hibernate when corresponding events happen.
|
||||||
|
* For example, {@link EntityCallbacksListener#prePersist} will be invoked before the entity is
|
||||||
|
* persisted to the database, then it will recursively invoke any other {@link PrePersist} method
|
||||||
|
* that should be invoked but not handled by Hibernate due to the bug.
|
||||||
|
*
|
||||||
|
* @see <a
|
||||||
|
* href="https://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#events-jpa-callbacks">JPA
|
||||||
|
* Callbacks</a>
|
||||||
|
* @see <a href="https://hibernate.atlassian.net/browse/HHH-13316">HHH-13316</a>
|
||||||
|
*/
|
||||||
|
public class EntityCallbacksListener {
|
||||||
|
|
||||||
|
@PrePersist
|
||||||
|
void prePersist(Object entity) {
|
||||||
|
EntityCallbackExecutor.create(PrePersist.class).execute(entity, entity.getClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreRemove
|
||||||
|
void preRemove(Object entity) {
|
||||||
|
EntityCallbackExecutor.create(PreRemove.class).execute(entity, entity.getClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostPersist
|
||||||
|
void postPersist(Object entity) {
|
||||||
|
EntityCallbackExecutor.create(PostPersist.class).execute(entity, entity.getClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostRemove
|
||||||
|
void postRemove(Object entity) {
|
||||||
|
EntityCallbackExecutor.create(PostRemove.class).execute(entity, entity.getClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreUpdate
|
||||||
|
void preUpdate(Object entity) {
|
||||||
|
EntityCallbackExecutor.create(PreUpdate.class).execute(entity, entity.getClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostUpdate
|
||||||
|
void postUpdate(Object entity) {
|
||||||
|
EntityCallbackExecutor.create(PostUpdate.class).execute(entity, entity.getClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostLoad
|
||||||
|
void postLoad(Object entity) {
|
||||||
|
EntityCallbackExecutor.create(PostLoad.class).execute(entity, entity.getClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class EntityCallbackExecutor {
|
||||||
|
Class<? extends Annotation> callbackType;
|
||||||
|
|
||||||
|
private EntityCallbackExecutor(Class<? extends Annotation> callbackType) {
|
||||||
|
this.callbackType = callbackType;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static EntityCallbackExecutor create(Class<? extends Annotation> callbackType) {
|
||||||
|
return new EntityCallbackExecutor(callbackType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes eligible callbacks in {@link Embedded} properties recursively.
|
||||||
|
*
|
||||||
|
* @param entity the Java object of the entity class
|
||||||
|
* @param entityType either the type of the entity or an ancestor type
|
||||||
|
*/
|
||||||
|
private void execute(Object entity, Class<?> entityType) {
|
||||||
|
Class<?> parentType = entityType.getSuperclass();
|
||||||
|
if (parentType != null && parentType.isAnnotationPresent(MappedSuperclass.class)) {
|
||||||
|
execute(entity, parentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
findEmbeddedProperties(entity, entityType)
|
||||||
|
.forEach(
|
||||||
|
normalEmbedded -> {
|
||||||
|
// For each normal embedded property, we don't execute its callback method because
|
||||||
|
// it is handled by Hibernate. However, for the embedded property defined in the
|
||||||
|
// entity's parent class, we need to treat it as a nested embedded property and
|
||||||
|
// invoke its callback function.
|
||||||
|
if (entity.getClass().equals(entityType)) {
|
||||||
|
executeCallbackForNormalEmbeddedProperty(
|
||||||
|
normalEmbedded, normalEmbedded.getClass());
|
||||||
|
} else {
|
||||||
|
executeCallbackForNestedEmbeddedProperty(
|
||||||
|
normalEmbedded, normalEmbedded.getClass());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executeCallbackForNestedEmbeddedProperty(
|
||||||
|
Object nestedEmbeddedObject, Class<?> nestedEmbeddedType) {
|
||||||
|
Class<?> parentType = nestedEmbeddedType.getSuperclass();
|
||||||
|
if (parentType != null && parentType.isAnnotationPresent(MappedSuperclass.class)) {
|
||||||
|
executeCallbackForNestedEmbeddedProperty(nestedEmbeddedObject, parentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
findEmbeddedProperties(nestedEmbeddedObject, nestedEmbeddedType)
|
||||||
|
.forEach(
|
||||||
|
embeddedProperty ->
|
||||||
|
executeCallbackForNestedEmbeddedProperty(
|
||||||
|
embeddedProperty, embeddedProperty.getClass()));
|
||||||
|
|
||||||
|
for (Method method : nestedEmbeddedType.getDeclaredMethods()) {
|
||||||
|
if (method.isAnnotationPresent(callbackType)) {
|
||||||
|
invokeMethod(method, nestedEmbeddedObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executeCallbackForNormalEmbeddedProperty(
|
||||||
|
Object normalEmbeddedObject, Class<?> normalEmbeddedType) {
|
||||||
|
Class<?> parentType = normalEmbeddedType.getSuperclass();
|
||||||
|
if (parentType != null && parentType.isAnnotationPresent(MappedSuperclass.class)) {
|
||||||
|
executeCallbackForNormalEmbeddedProperty(normalEmbeddedObject, parentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
findEmbeddedProperties(normalEmbeddedObject, normalEmbeddedType)
|
||||||
|
.forEach(
|
||||||
|
embeddedProperty ->
|
||||||
|
executeCallbackForNestedEmbeddedProperty(
|
||||||
|
embeddedProperty, embeddedProperty.getClass()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Stream<Object> findEmbeddedProperties(Object object, Class<?> clazz) {
|
||||||
|
return Arrays.stream(clazz.getDeclaredFields())
|
||||||
|
.filter(
|
||||||
|
field ->
|
||||||
|
field.isAnnotationPresent(Embedded.class)
|
||||||
|
|| field.getType().isAnnotationPresent(Embeddable.class))
|
||||||
|
.map(field -> getFieldObject(field, object))
|
||||||
|
.filter(Objects::nonNull);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object getFieldObject(Field field, Object object) {
|
||||||
|
field.setAccessible(true);
|
||||||
|
try {
|
||||||
|
return field.get(object);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void invokeMethod(Method method, Object object) {
|
||||||
|
method.setAccessible(true);
|
||||||
|
try {
|
||||||
|
method.invoke(object);
|
||||||
|
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,4 +10,11 @@
|
||||||
<basic name="amount" access="FIELD"/>
|
<basic name="amount" access="FIELD"/>
|
||||||
</attributes>
|
</attributes>
|
||||||
</embeddable>
|
</embeddable>
|
||||||
|
<persistence-unit-metadata>
|
||||||
|
<persistence-unit-defaults>
|
||||||
|
<entity-listeners>
|
||||||
|
<entity-listener class="google.registry.persistence.EntityCallbacksListener" />
|
||||||
|
</entity-listeners>
|
||||||
|
</persistence-unit-defaults>
|
||||||
|
</persistence-unit-metadata>
|
||||||
</entity-mappings>
|
</entity-mappings>
|
||||||
|
|
|
@ -0,0 +1,302 @@
|
||||||
|
// Copyright 2020 The Nomulus Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// 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 google.registry.persistence;
|
||||||
|
|
||||||
|
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static com.google.common.truth.Truth.assertWithMessage;
|
||||||
|
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import google.registry.persistence.transaction.JpaTestRules;
|
||||||
|
import google.registry.persistence.transaction.JpaTestRules.JpaUnitTestRule;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import javax.persistence.Embeddable;
|
||||||
|
import javax.persistence.Embedded;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.MappedSuperclass;
|
||||||
|
import javax.persistence.PostLoad;
|
||||||
|
import javax.persistence.PostPersist;
|
||||||
|
import javax.persistence.PostRemove;
|
||||||
|
import javax.persistence.PostUpdate;
|
||||||
|
import javax.persistence.PrePersist;
|
||||||
|
import javax.persistence.PreRemove;
|
||||||
|
import javax.persistence.PreUpdate;
|
||||||
|
import javax.persistence.Transient;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.JUnit4;
|
||||||
|
|
||||||
|
/** Unit tests for {@link EntityCallbacksListener}. */
|
||||||
|
@RunWith(JUnit4.class)
|
||||||
|
public class EntityCallbacksListenerTest {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final JpaUnitTestRule jpaRule =
|
||||||
|
new JpaTestRules.Builder().withEntityClass(TestEntity.class).buildUnitTestRule();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void verifyAllCallbacks_executedExpectedTimes() {
|
||||||
|
TestEntity testPersist = new TestEntity();
|
||||||
|
jpaTm().transact(() -> jpaTm().saveNew(testPersist));
|
||||||
|
checkAll(testPersist, 1, 0, 0, 0);
|
||||||
|
|
||||||
|
TestEntity testUpdate = new TestEntity();
|
||||||
|
TestEntity updated =
|
||||||
|
jpaTm()
|
||||||
|
.transact(
|
||||||
|
() -> {
|
||||||
|
TestEntity merged = jpaTm().getEntityManager().merge(testUpdate);
|
||||||
|
merged.foo++;
|
||||||
|
jpaTm().getEntityManager().flush();
|
||||||
|
return merged;
|
||||||
|
});
|
||||||
|
// Note that when we get the merged entity, its @PostLoad callbacks are also invoked
|
||||||
|
checkAll(updated, 0, 1, 0, 1);
|
||||||
|
|
||||||
|
TestEntity testLoad =
|
||||||
|
jpaTm().transact(() -> jpaTm().load(VKey.createSql(TestEntity.class, "id"))).get();
|
||||||
|
checkAll(testLoad, 0, 0, 0, 1);
|
||||||
|
|
||||||
|
TestEntity testRemove =
|
||||||
|
jpaTm()
|
||||||
|
.transact(
|
||||||
|
() -> {
|
||||||
|
TestEntity removed = jpaTm().load(VKey.createSql(TestEntity.class, "id")).get();
|
||||||
|
jpaTm().getEntityManager().remove(removed);
|
||||||
|
return removed;
|
||||||
|
});
|
||||||
|
checkAll(testRemove, 0, 0, 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void verifyAllManagedEntities_haveNoMethodWithEmbedded() {
|
||||||
|
ImmutableSet<Class> violations =
|
||||||
|
PersistenceXmlUtility.getManagedClasses().stream()
|
||||||
|
.filter(clazz -> clazz.isAnnotationPresent(Entity.class))
|
||||||
|
.filter(EntityCallbacksListenerTest::hasMethodAnnotatedWithEmbedded)
|
||||||
|
.collect(toImmutableSet());
|
||||||
|
assertWithMessage(
|
||||||
|
"Found entity classes having methods annotated with @Embedded. EntityCallbacksListener"
|
||||||
|
+ " only supports annotating fields with @Embedded.")
|
||||||
|
.that(violations)
|
||||||
|
.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void verifyHasMethodAnnotatedWithEmbedded_work() {
|
||||||
|
assertThat(hasMethodAnnotatedWithEmbedded(ViolationEntity.class)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean hasMethodAnnotatedWithEmbedded(Class<?> entityType) {
|
||||||
|
boolean result = false;
|
||||||
|
Class<?> parentType = entityType.getSuperclass();
|
||||||
|
if (parentType != null && parentType.isAnnotationPresent(MappedSuperclass.class)) {
|
||||||
|
result = hasMethodAnnotatedWithEmbedded(parentType);
|
||||||
|
}
|
||||||
|
for (Method method : entityType.getDeclaredMethods()) {
|
||||||
|
if (method.isAnnotationPresent(Embedded.class)) {
|
||||||
|
result = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void checkAll(
|
||||||
|
TestEntity testEntity,
|
||||||
|
int expectedPersist,
|
||||||
|
int expectedUpdate,
|
||||||
|
int expectedRemove,
|
||||||
|
int expectedLoad) {
|
||||||
|
assertThat(testEntity.entityEmbedded.entityEmbeddedNested.entityEmbeddedNestedPostPersist)
|
||||||
|
.isEqualTo(expectedPersist);
|
||||||
|
assertThat(testEntity.entityEmbedded.entityEmbeddedNested.entityEmbeddedNestedPrePersist)
|
||||||
|
.isEqualTo(expectedPersist);
|
||||||
|
|
||||||
|
assertThat(testEntity.entityEmbedded.entityEmbeddedNested.entityEmbeddedNestedPreUpdate)
|
||||||
|
.isEqualTo(expectedUpdate);
|
||||||
|
assertThat(testEntity.entityEmbedded.entityEmbeddedNested.entityEmbeddedNestedPostUpdate)
|
||||||
|
.isEqualTo(expectedUpdate);
|
||||||
|
|
||||||
|
assertThat(testEntity.entityEmbedded.entityEmbeddedNested.entityEmbeddedNestedPreRemove)
|
||||||
|
.isEqualTo(expectedRemove);
|
||||||
|
assertThat(testEntity.entityEmbedded.entityEmbeddedNested.entityEmbeddedNestedPostRemove)
|
||||||
|
.isEqualTo(expectedRemove);
|
||||||
|
|
||||||
|
assertThat(testEntity.entityPostLoad).isEqualTo(expectedLoad);
|
||||||
|
assertThat(testEntity.entityEmbedded.entityEmbeddedPostLoad).isEqualTo(expectedLoad);
|
||||||
|
assertThat(testEntity.entityEmbedded.entityEmbeddedNested.entityEmbeddedNestedPostLoad)
|
||||||
|
.isEqualTo(expectedLoad);
|
||||||
|
assertThat(testEntity.entityEmbedded.entityEmbeddedParentPostLoad).isEqualTo(expectedLoad);
|
||||||
|
|
||||||
|
assertThat(testEntity.parentPostLoad).isEqualTo(expectedLoad);
|
||||||
|
assertThat(testEntity.parentEmbedded.parentEmbeddedPostLoad).isEqualTo(expectedLoad);
|
||||||
|
assertThat(testEntity.parentEmbedded.parentEmbeddedNested.parentEmbeddedNestedPostLoad)
|
||||||
|
.isEqualTo(expectedLoad);
|
||||||
|
assertThat(testEntity.parentEmbedded.parentEmbeddedParentPostLoad).isEqualTo(expectedLoad);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "TestEntity")
|
||||||
|
private static class TestEntity extends ParentEntity {
|
||||||
|
@Id String name = "id";
|
||||||
|
int foo = 0;
|
||||||
|
|
||||||
|
@Transient int entityPostLoad = 0;
|
||||||
|
|
||||||
|
@Embedded EntityEmbedded entityEmbedded = new EntityEmbedded();
|
||||||
|
|
||||||
|
@PostLoad
|
||||||
|
void entityPostLoad() {
|
||||||
|
entityPostLoad++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Embeddable
|
||||||
|
private static class EntityEmbedded extends EntityEmbeddedParent {
|
||||||
|
@Embedded EntityEmbeddedNested entityEmbeddedNested = new EntityEmbeddedNested();
|
||||||
|
|
||||||
|
@Transient int entityEmbeddedPostLoad = 0;
|
||||||
|
|
||||||
|
String entityEmbedded = "placeholder";
|
||||||
|
|
||||||
|
@PostLoad
|
||||||
|
void entityEmbeddedPrePersist() {
|
||||||
|
entityEmbeddedPostLoad++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@MappedSuperclass
|
||||||
|
private static class EntityEmbeddedParent {
|
||||||
|
@Transient int entityEmbeddedParentPostLoad = 0;
|
||||||
|
|
||||||
|
String entityEmbeddedParent = "placeholder";
|
||||||
|
|
||||||
|
@PostLoad
|
||||||
|
void entityEmbeddedParentPostLoad() {
|
||||||
|
entityEmbeddedParentPostLoad++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Embeddable
|
||||||
|
private static class EntityEmbeddedNested {
|
||||||
|
@Transient int entityEmbeddedNestedPrePersist = 0;
|
||||||
|
@Transient int entityEmbeddedNestedPreRemove = 0;
|
||||||
|
@Transient int entityEmbeddedNestedPostPersist = 0;
|
||||||
|
@Transient int entityEmbeddedNestedPostRemove = 0;
|
||||||
|
@Transient int entityEmbeddedNestedPreUpdate = 0;
|
||||||
|
@Transient int entityEmbeddedNestedPostUpdate = 0;
|
||||||
|
@Transient int entityEmbeddedNestedPostLoad = 0;
|
||||||
|
|
||||||
|
String entityEmbeddedNested = "placeholder";
|
||||||
|
|
||||||
|
@PrePersist
|
||||||
|
void entityEmbeddedNestedPrePersist() {
|
||||||
|
entityEmbeddedNestedPrePersist++;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreRemove
|
||||||
|
void entityEmbeddedNestedPreRemove() {
|
||||||
|
entityEmbeddedNestedPreRemove++;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostPersist
|
||||||
|
void entityEmbeddedNestedPostPersist() {
|
||||||
|
entityEmbeddedNestedPostPersist++;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostRemove
|
||||||
|
void entityEmbeddedNestedPostRemove() {
|
||||||
|
entityEmbeddedNestedPostRemove++;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreUpdate
|
||||||
|
void entityEmbeddedNestedPreUpdate() {
|
||||||
|
entityEmbeddedNestedPreUpdate++;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostUpdate
|
||||||
|
void entityEmbeddedNestedPostUpdate() {
|
||||||
|
entityEmbeddedNestedPostUpdate++;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostLoad
|
||||||
|
void entityEmbeddedNestedPostLoad() {
|
||||||
|
entityEmbeddedNestedPostLoad++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@MappedSuperclass
|
||||||
|
private static class ParentEntity {
|
||||||
|
@Embedded ParentEmbedded parentEmbedded = new ParentEmbedded();
|
||||||
|
@Transient int parentPostLoad = 0;
|
||||||
|
|
||||||
|
String parentEntity = "placeholder";
|
||||||
|
|
||||||
|
@PostLoad
|
||||||
|
void parentPostLoad() {
|
||||||
|
parentPostLoad++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Embeddable
|
||||||
|
private static class ParentEmbedded extends ParentEmbeddedParent {
|
||||||
|
@Transient int parentEmbeddedPostLoad = 0;
|
||||||
|
|
||||||
|
String parentEmbedded = "placeholder";
|
||||||
|
|
||||||
|
@Embedded ParentEmbeddedNested parentEmbeddedNested = new ParentEmbeddedNested();
|
||||||
|
|
||||||
|
@PostLoad
|
||||||
|
void parentEmbeddedPostLoad() {
|
||||||
|
parentEmbeddedPostLoad++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Embeddable
|
||||||
|
private static class ParentEmbeddedNested {
|
||||||
|
@Transient int parentEmbeddedNestedPostLoad = 0;
|
||||||
|
|
||||||
|
String parentEmbeddedNested = "placeholder";
|
||||||
|
|
||||||
|
@PostLoad
|
||||||
|
void parentEmbeddedNestedPostLoad() {
|
||||||
|
parentEmbeddedNestedPostLoad++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@MappedSuperclass
|
||||||
|
private static class ParentEmbeddedParent {
|
||||||
|
@Transient int parentEmbeddedParentPostLoad = 0;
|
||||||
|
|
||||||
|
String parentEmbeddedParent = "placeholder";
|
||||||
|
|
||||||
|
@PostLoad
|
||||||
|
void parentEmbeddedParentPostLoad() {
|
||||||
|
parentEmbeddedParentPostLoad++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
private static class ViolationEntity {
|
||||||
|
|
||||||
|
@Embedded
|
||||||
|
EntityEmbedded getEntityEmbedded() {
|
||||||
|
return new EntityEmbedded();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue