HHH-12326 - PreUpdate/PrePersist not working for @Embeddable entities

This commit is contained in:
Vlad Mihalcea 2018-02-28 17:13:14 +02:00
parent 75ea23cab3
commit 6b3bbfcd19
8 changed files with 252 additions and 5 deletions

View File

@ -8,6 +8,7 @@ package org.hibernate.event.service.internal;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.Map; import java.util.Map;
import org.hibernate.HibernateException; import org.hibernate.HibernateException;
@ -46,6 +47,7 @@ import org.hibernate.jpa.event.internal.CallbackBuilderLegacyImpl;
import org.hibernate.jpa.event.internal.CallbackRegistryImpl; import org.hibernate.jpa.event.internal.CallbackRegistryImpl;
import org.hibernate.jpa.event.spi.CallbackBuilder; import org.hibernate.jpa.event.spi.CallbackBuilder;
import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.resource.beans.spi.ManagedBeanRegistry; import org.hibernate.resource.beans.spi.ManagedBeanRegistry;
import org.hibernate.service.spi.ServiceRegistryImplementor; import org.hibernate.service.spi.ServiceRegistryImplementor;
import org.hibernate.service.spi.Stoppable; import org.hibernate.service.spi.Stoppable;
@ -148,6 +150,19 @@ public class EventListenerRegistryImpl implements EventListenerRegistry, Stoppab
continue; continue;
} }
callbackBuilder.buildCallbacksForEntity( persistentClass.getClassName(), callbackRegistry ); callbackBuilder.buildCallbacksForEntity( persistentClass.getClassName(), callbackRegistry );
for ( Iterator propertyIterator = persistentClass.getDeclaredPropertyIterator();
propertyIterator.hasNext(); ) {
Property property = (Property) propertyIterator.next();
if ( property.getType().isComponentType() ) {
callbackBuilder.buildCallbacksForEmbeddable(
property,
persistentClass.getClassName(),
callbackRegistry
);
}
}
} }
} }

View File

@ -28,6 +28,8 @@ import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.jpa.event.spi.Callback; import org.hibernate.jpa.event.spi.Callback;
import org.hibernate.jpa.event.spi.CallbackBuilder; import org.hibernate.jpa.event.spi.CallbackBuilder;
import org.hibernate.jpa.event.spi.CallbackType; import org.hibernate.jpa.event.spi.CallbackType;
import org.hibernate.mapping.Property;
import org.hibernate.property.access.spi.Getter;
import org.hibernate.resource.beans.spi.ManagedBeanRegistry; import org.hibernate.resource.beans.spi.ManagedBeanRegistry;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
@ -67,7 +69,7 @@ public class CallbackBuilderLegacyImpl implements CallbackBuilder {
); );
continue; continue;
} }
final Callback[] callbacks = resolveCallbacks( entityXClass, callbackType, reflectionManager ); final Callback[] callbacks = resolveEntityCallbacks( entityXClass, callbackType, reflectionManager );
callbackRegistrar.registerCallbacks( entityClass, callbacks ); callbackRegistrar.registerCallbacks( entityClass, callbacks );
} }
} }
@ -76,13 +78,35 @@ public class CallbackBuilderLegacyImpl implements CallbackBuilder {
} }
} }
@Override
public void buildCallbacksForEmbeddable(
Property embeddableProperty, String entityClassName, CallbackRegistrar callbackRegistrar) {
try {
final XClass entityXClass = reflectionManager.classForName( entityClassName );
final Class entityClass = reflectionManager.toClass( entityXClass );
for ( CallbackType callbackType : CallbackType.values() ) {
final Callback[] callbacks = resolveEmbeddableCallbacks(
entityClass,
embeddableProperty,
callbackType,
reflectionManager
);
callbackRegistrar.registerCallbacks( entityClass, callbacks );
}
}
catch (ClassLoadingException e) {
throw new MappingException( "Class not found: ", e );
}
}
@Override @Override
public void release() { public void release() {
// nothing to do // nothing to do
} }
@SuppressWarnings({"unchecked", "WeakerAccess"}) @SuppressWarnings({"unchecked", "WeakerAccess"})
public Callback[] resolveCallbacks(XClass beanClass, CallbackType callbackType, ReflectionManager reflectionManager) { public Callback[] resolveEntityCallbacks(XClass beanClass, CallbackType callbackType, ReflectionManager reflectionManager) {
List<Callback> callbacks = new ArrayList<>(); List<Callback> callbacks = new ArrayList<>();
List<String> callbacksMethodNames = new ArrayList<>(); List<String> callbacksMethodNames = new ArrayList<>();
List<Class> orderedListeners = new ArrayList<>(); List<Class> orderedListeners = new ArrayList<>();
@ -207,6 +231,65 @@ public class CallbackBuilderLegacyImpl implements CallbackBuilder {
return callbacks.toArray( new Callback[callbacks.size()] ); return callbacks.toArray( new Callback[callbacks.size()] );
} }
@SuppressWarnings({"unchecked", "WeakerAccess"})
public Callback[] resolveEmbeddableCallbacks(Class entityClass, Property embeddableProperty, CallbackType callbackType, ReflectionManager reflectionManager) {
final String embeddableClassName = embeddableProperty.getType().getReturnedClass().getName();
final XClass embeddableXClass = reflectionManager.classForName( embeddableClassName );
final Getter embeddableGetter = embeddableProperty.getGetter( entityClass );
List<Callback> callbacks = new ArrayList<>();
List<String> callbacksMethodNames = new ArrayList<>();
XClass currentClazz = embeddableXClass;
do {
Callback callback = null;
List<XMethod> methods = currentClazz.getDeclaredMethods();
for ( final XMethod xMethod : methods ) {
if ( xMethod.isAnnotationPresent( callbackType.getCallbackAnnotation() ) ) {
Method method = reflectionManager.toMethod( xMethod );
final String methodName = method.getName();
if ( !callbacksMethodNames.contains( methodName ) ) {
//overridden method, remove the superclass overridden method
if ( callback == null ) {
callback = new EmbeddableCallback( embeddableGetter, method, callbackType );
Class returnType = method.getReturnType();
Class[] args = method.getParameterTypes();
if ( returnType != Void.TYPE || args.length != 0 ) {
throw new RuntimeException(
"Callback methods annotated on the bean class must return void and take no arguments: "
+ callbackType.getCallbackAnnotation().getName() + " - " + xMethod
);
}
ReflectHelper.ensureAccessibility( method );
log.debugf(
"Adding %s as %s callback for entity %s",
methodName,
callbackType.getCallbackAnnotation().getSimpleName(),
embeddableXClass.getName()
);
callbacks.add( 0, callback ); //superclass first
callbacksMethodNames.add( 0, methodName );
}
else {
throw new PersistenceException(
"You can only annotate one callback method with "
+ callbackType.getCallbackAnnotation().getName() + " in bean class: " + embeddableXClass.getName()
);
}
}
}
}
do {
currentClazz = currentClazz.getSuperclass();
}
while ( currentClazz != null && !currentClazz.isAnnotationPresent( MappedSuperclass.class ) );
}
while ( currentClazz != null );
return callbacks.toArray( new Callback[callbacks.size()] );
}
private static boolean useAnnotationAnnotatedByListener; private static boolean useAnnotationAnnotatedByListener;
static { static {

View File

@ -9,6 +9,7 @@ package org.hibernate.jpa.event.internal;
import java.util.HashMap; import java.util.HashMap;
import javax.persistence.PersistenceException; import javax.persistence.PersistenceException;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.jpa.event.spi.Callback; import org.hibernate.jpa.event.spi.Callback;
import org.hibernate.jpa.event.spi.CallbackRegistry; import org.hibernate.jpa.event.spi.CallbackRegistry;
import org.hibernate.jpa.event.spi.CallbackType; import org.hibernate.jpa.event.spi.CallbackType;
@ -43,8 +44,10 @@ public class CallbackRegistryImpl implements CallbackRegistry, CallbackBuilder.C
} }
final HashMap<Class, Callback[]> map = determineAppropriateCallbackMap( callbacks[0].getCallbackType() ); final HashMap<Class, Callback[]> map = determineAppropriateCallbackMap( callbacks[0].getCallbackType() );
if ( map.containsKey( entityClass ) ) { Callback[] entityCallbacks = map.get( entityClass );
throw new PersistenceException( "Error build callback listeners; entity [" + entityClass.getName() + " was already processed" );
if ( entityCallbacks != null ) {
callbacks = ArrayHelper.join( entityCallbacks, callbacks );
} }
map.put( entityClass, callbacks ); map.put( entityClass, callbacks );
} }

View File

@ -0,0 +1,51 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.jpa.event.internal;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.hibernate.jpa.event.spi.CallbackType;
import org.hibernate.property.access.spi.Getter;
/**
* Represents a JPA callback on the embeddable type
*
* @author Vlad Mihalcea
*/
public class EmbeddableCallback extends AbstractCallback {
private final Getter embeddableGetter;
private final Method callbackMethod;
@SuppressWarnings("WeakerAccess")
public EmbeddableCallback(Getter embeddableGetter, Method callbackMethod, CallbackType callbackType) {
super( callbackType );
this.embeddableGetter = embeddableGetter;
this.callbackMethod = callbackMethod;
}
@Override
public boolean performCallback(Object entity) {
try {
Object embeddable = embeddableGetter.get( entity );
callbackMethod.invoke( embeddable );
return true;
}
catch (InvocationTargetException e) {
//keep runtime exceptions as is
if ( e.getTargetException() instanceof RuntimeException ) {
throw (RuntimeException) e.getTargetException();
}
else {
throw new RuntimeException( e.getTargetException() );
}
}
catch (Exception e) {
throw new RuntimeException( e );
}
}
}

View File

@ -6,6 +6,8 @@
*/ */
package org.hibernate.jpa.event.spi; package org.hibernate.jpa.event.spi;
import org.hibernate.mapping.Property;
/** /**
* Contract for walking an entity hierarchy and building a list of JPA callbacks * Contract for walking an entity hierarchy and building a list of JPA callbacks
* *
@ -26,7 +28,12 @@ public interface CallbackBuilder {
void registerCallbacks(Class entityClass, Callback[] callbacks); void registerCallbacks(Class entityClass, Callback[] callbacks);
} }
void buildCallbacksForEntity(String entityName, CallbackRegistrar callbackRegistrar); void buildCallbacksForEntity(String entityClassName, CallbackRegistrar callbackRegistrar);
void buildCallbacksForEmbeddable(
Property embeddableProperty,
String entityClassName,
CallbackRegistrar callbackRegistrar);
void release(); void release();
} }

View File

@ -0,0 +1,76 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.event;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.PrePersist;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.testing.TestForIssue;
import org.junit.Test;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
import static org.junit.Assert.assertEquals;
/**
* @author Vlad Mihalcea
*/
@TestForIssue(jiraKey = "HHH-12326")
public class EmbeddableCallbackTest extends BaseEntityManagerFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] { Employee.class };
}
@Test
public void test() {
doInJPA( this::entityManagerFactory, entityManager -> {
Employee employee = new Employee();
employee.id = 1;
entityManager.persist( employee );
} );
doInJPA( this::entityManagerFactory, entityManager -> {
Employee employee = entityManager.find( Employee.class, 1 );
assertEquals( "Vlad", employee.name );
assertEquals( "Developer Advocate", employee.details.jobTitle );
} );
}
@Entity(name = "Employee")
public static class Employee {
@Id
private Integer id;
private String name;
private EmployeeDetails details = new EmployeeDetails();
@PrePersist
public void setUp() {
name = "Vlad";
}
}
@Embeddable
public static class EmployeeDetails {
private String jobTitle;
@PrePersist
public void setUp() {
jobTitle = "Developer Advocate";
}
}
}

View File

@ -1,3 +1,9 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.event; package org.hibernate.event;
import javax.persistence.CascadeType; import javax.persistence.CascadeType;

View File

@ -1,3 +1,9 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.event; package org.hibernate.event;
import javax.persistence.CascadeType; import javax.persistence.CascadeType;