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.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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 );
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
package org.hibernate.event;
|
||||||
|
|
||||||
import javax.persistence.CascadeType;
|
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;
|
package org.hibernate.event;
|
||||||
|
|
||||||
import javax.persistence.CascadeType;
|
import javax.persistence.CascadeType;
|
||||||
|
|
Loading…
Reference in New Issue