HHH-12326 - PreUpdate/PrePersist not working for @Embeddable entities
This commit is contained in:
parent
75ea23cab3
commit
6b3bbfcd19
|
@ -8,6 +8,7 @@ package org.hibernate.event.service.internal;
|
|||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
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.spi.CallbackBuilder;
|
||||
import org.hibernate.mapping.PersistentClass;
|
||||
import org.hibernate.mapping.Property;
|
||||
import org.hibernate.resource.beans.spi.ManagedBeanRegistry;
|
||||
import org.hibernate.service.spi.ServiceRegistryImplementor;
|
||||
import org.hibernate.service.spi.Stoppable;
|
||||
|
@ -148,6 +150,19 @@ public class EventListenerRegistryImpl implements EventListenerRegistry, Stoppab
|
|||
continue;
|
||||
}
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -28,6 +28,8 @@ import org.hibernate.internal.util.ReflectHelper;
|
|||
import org.hibernate.jpa.event.spi.Callback;
|
||||
import org.hibernate.jpa.event.spi.CallbackBuilder;
|
||||
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.jboss.logging.Logger;
|
||||
|
@ -67,7 +69,7 @@ public class CallbackBuilderLegacyImpl implements CallbackBuilder {
|
|||
);
|
||||
continue;
|
||||
}
|
||||
final Callback[] callbacks = resolveCallbacks( entityXClass, callbackType, reflectionManager );
|
||||
final Callback[] callbacks = resolveEntityCallbacks( entityXClass, callbackType, reflectionManager );
|
||||
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
|
||||
public void release() {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
@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<String> callbacksMethodNames = new ArrayList<>();
|
||||
List<Class> orderedListeners = new ArrayList<>();
|
||||
|
@ -207,6 +231,65 @@ public class CallbackBuilderLegacyImpl implements CallbackBuilder {
|
|||
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;
|
||||
|
||||
static {
|
||||
|
|
|
@ -9,6 +9,7 @@ package org.hibernate.jpa.event.internal;
|
|||
import java.util.HashMap;
|
||||
import javax.persistence.PersistenceException;
|
||||
|
||||
import org.hibernate.internal.util.collections.ArrayHelper;
|
||||
import org.hibernate.jpa.event.spi.Callback;
|
||||
import org.hibernate.jpa.event.spi.CallbackRegistry;
|
||||
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() );
|
||||
if ( map.containsKey( entityClass ) ) {
|
||||
throw new PersistenceException( "Error build callback listeners; entity [" + entityClass.getName() + " was already processed" );
|
||||
Callback[] entityCallbacks = map.get( entityClass );
|
||||
|
||||
if ( entityCallbacks != null ) {
|
||||
callbacks = ArrayHelper.join( entityCallbacks, callbacks );
|
||||
}
|
||||
map.put( entityClass, callbacks );
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,6 +6,8 @@
|
|||
*/
|
||||
package org.hibernate.jpa.event.spi;
|
||||
|
||||
import org.hibernate.mapping.Property;
|
||||
|
||||
/**
|
||||
* 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 buildCallbacksForEntity(String entityName, CallbackRegistrar callbackRegistrar);
|
||||
void buildCallbacksForEntity(String entityClassName, CallbackRegistrar callbackRegistrar);
|
||||
|
||||
void buildCallbacksForEmbeddable(
|
||||
Property embeddableProperty,
|
||||
String entityClassName,
|
||||
CallbackRegistrar callbackRegistrar);
|
||||
|
||||
void release();
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
import javax.persistence.CascadeType;
|
||||
|
|
|
@ -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;
|
||||
|
||||
import javax.persistence.CascadeType;
|
||||
|
|
Loading…
Reference in New Issue