HHH-17460 - Ongoing JPA 32 work

This commit is contained in:
Steve Ebersole 2024-03-12 16:07:25 -05:00
parent 9b46ced2b3
commit 808544579c
4 changed files with 307 additions and 54 deletions

View File

@ -63,7 +63,6 @@ import org.hibernate.annotations.Tables;
import org.hibernate.annotations.TypeBinderType; import org.hibernate.annotations.TypeBinderType;
import org.hibernate.annotations.View; import org.hibernate.annotations.View;
import org.hibernate.annotations.Where; import org.hibernate.annotations.Where;
import org.hibernate.annotations.common.reflection.ReflectionManager;
import org.hibernate.binder.TypeBinder; import org.hibernate.binder.TypeBinder;
import org.hibernate.boot.model.IdentifierGeneratorDefinition; import org.hibernate.boot.model.IdentifierGeneratorDefinition;
import org.hibernate.boot.model.NamedEntityGraphDefinition; import org.hibernate.boot.model.NamedEntityGraphDefinition;
@ -74,6 +73,7 @@ import org.hibernate.boot.model.naming.ImplicitEntityNameSource;
import org.hibernate.boot.model.naming.NamingStrategyHelper; import org.hibernate.boot.model.naming.NamingStrategyHelper;
import org.hibernate.boot.model.relational.QualifiedTableName; import org.hibernate.boot.model.relational.QualifiedTableName;
import org.hibernate.boot.models.HibernateAnnotations; import org.hibernate.boot.models.HibernateAnnotations;
import org.hibernate.boot.models.categorize.spi.JpaEventListener;
import org.hibernate.boot.spi.AccessType; import org.hibernate.boot.spi.AccessType;
import org.hibernate.boot.spi.InFlightMetadataCollector; import org.hibernate.boot.spi.InFlightMetadataCollector;
import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataBuildingContext;
@ -85,6 +85,7 @@ import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.StringHelper;
import org.hibernate.jdbc.Expectation; import org.hibernate.jdbc.Expectation;
import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.jpa.event.internal.CallbackDefinitionResolver;
import org.hibernate.jpa.event.spi.CallbackType; import org.hibernate.jpa.event.spi.CallbackType;
import org.hibernate.mapping.BasicValue; import org.hibernate.mapping.BasicValue;
import org.hibernate.mapping.CheckConstraint; import org.hibernate.mapping.CheckConstraint;
@ -166,8 +167,6 @@ import static org.hibernate.internal.util.StringHelper.isEmpty;
import static org.hibernate.internal.util.StringHelper.isNotEmpty; import static org.hibernate.internal.util.StringHelper.isNotEmpty;
import static org.hibernate.internal.util.StringHelper.nullIfEmpty; import static org.hibernate.internal.util.StringHelper.nullIfEmpty;
import static org.hibernate.internal.util.StringHelper.unqualify; import static org.hibernate.internal.util.StringHelper.unqualify;
import static org.hibernate.jpa.event.internal.CallbackDefinitionResolverLegacyImpl.resolveEmbeddableCallbacks;
import static org.hibernate.jpa.event.internal.CallbackDefinitionResolverLegacyImpl.resolveEntityCallbacks;
import static org.hibernate.mapping.SimpleValue.DEFAULT_ID_GEN_STRATEGY; import static org.hibernate.mapping.SimpleValue.DEFAULT_ID_GEN_STRATEGY;
@ -655,7 +654,7 @@ public class EntityBinder {
propertyAccessor, propertyAccessor,
context context
); );
final InheritanceState state = inheritanceStates.get( idPropertyOnBaseClass.getClassOrElementType() ); final InheritanceState state = inheritanceStates.get( idPropertyOnBaseClass.getClassOrElementType().determineRawClass() );
if ( state == null ) { if ( state == null ) {
return false; //while it is likely a user error, let's consider it is something that might happen return false; //while it is likely a user error, let's consider it is something that might happen
} }
@ -687,11 +686,10 @@ public class EntityBinder {
// Fill simple value and property since and Id is a property // Fill simple value and property since and Id is a property
final PersistentClass persistentClass = propertyHolder.getPersistentClass(); final PersistentClass persistentClass = propertyHolder.getPersistentClass();
if ( !(persistentClass instanceof RootClass) ) { if ( !( persistentClass instanceof RootClass rootClass ) ) {
throw new AnnotationException( "Entity '" + persistentClass.getEntityName() throw new AnnotationException( "Entity '" + persistentClass.getEntityName()
+ "' is a subclass in an entity inheritance hierarchy and may not redefine the identifier of the root entity" ); + "' is a subclass in an entity inheritance hierarchy and may not redefine the identifier of the root entity" );
} }
final RootClass rootClass = (RootClass) persistentClass;
final Component id = fillEmbeddable( final Component id = fillEmbeddable(
propertyHolder, propertyHolder,
inferredData, inferredData,
@ -1225,17 +1223,29 @@ public class EntityBinder {
} }
} }
/**
* See {@link JpaEventListener} for a better (?) alternative
*/
private static void bindCallbacks(ClassDetails entityClass, PersistentClass persistentClass, MetadataBuildingContext context) { private static void bindCallbacks(ClassDetails entityClass, PersistentClass persistentClass, MetadataBuildingContext context) {
final ReflectionManager reflection = context.getBootstrapContext().getReflectionManager();
for ( CallbackType callbackType : CallbackType.values() ) { for ( CallbackType callbackType : CallbackType.values() ) {
persistentClass.addCallbackDefinitions( resolveEntityCallbacks( reflection, entityClass, callbackType ) ); persistentClass.addCallbackDefinitions( CallbackDefinitionResolver.resolveEntityCallbacks(
context,
entityClass,
callbackType
) );
} }
context.getMetadataCollector().addSecondPass( persistentClasses -> { context.getMetadataCollector().addSecondPass( persistentClasses -> {
for ( Property property : persistentClass.getDeclaredProperties() ) { for ( Property property : persistentClass.getDeclaredProperties() ) {
final Class<?> mappedClass = persistentClass.getMappedClass(); final Class<?> mappedClass = persistentClass.getMappedClass();
if ( property.isComposite() ) { if ( property.isComposite() ) {
for ( CallbackType type : CallbackType.values() ) { for ( CallbackType type : CallbackType.values() ) {
property.addCallbackDefinitions( resolveEmbeddableCallbacks( reflection, mappedClass, property, type ) ); property.addCallbackDefinitions( CallbackDefinitionResolver.resolveEmbeddableCallbacks(
context,
mappedClass,
property,
type
) );
} }
} }
} }
@ -2101,39 +2111,6 @@ public class EntityBinder {
return null; return null;
} }
private static <T extends Annotation> String tableMember(Class<T> annotationType, T sqlAnnotation) {
if (SQLInsert.class == annotationType) {
return ((SQLInsert) sqlAnnotation).table();
}
else if (SQLUpdate.class == annotationType) {
return ((SQLUpdate) sqlAnnotation).table();
}
else if (SQLDelete.class == annotationType) {
return ((SQLDelete) sqlAnnotation).table();
}
else if (SQLDeleteAll.class == annotationType) {
return ((SQLDeleteAll) sqlAnnotation).table();
}
else {
throw new AssertionFailure("Unknown annotation type");
}
}
private static <T extends Annotation> Annotation[] valueMember(Class<T> repeatableType, T sqlAnnotation) {
if (SQLInserts.class == repeatableType) {
return ((SQLInserts) sqlAnnotation).value();
}
else if (SQLUpdates.class == repeatableType) {
return ((SQLUpdates) sqlAnnotation).value();
}
else if (SQLDeletes.class == repeatableType) {
return ((SQLDeletes) sqlAnnotation).value();
}
else {
throw new AssertionFailure("Unknown annotation type");
}
}
//Used for @*ToMany @JoinTable //Used for @*ToMany @JoinTable
public Join addJoinTable(AnnotationUsage<JoinTable> joinTable, PropertyHolder holder, boolean noDelayInPkColumnCreation) { public Join addJoinTable(AnnotationUsage<JoinTable> joinTable, PropertyHolder holder, boolean noDelayInPkColumnCreation) {
return addJoin( return addJoin(

View File

@ -164,8 +164,11 @@ public interface BootstrapContext {
* @apiNote Supported for internal use only. This method will go away as * @apiNote Supported for internal use only. This method will go away as
* we migrate away from Hibernate Commons Annotations to Jandex for * we migrate away from Hibernate Commons Annotations to Jandex for
* annotation handling and XMl to annotation merging. * annotation handling and XMl to annotation merging.
*
* @deprecated HCANN is deprecated in favor of hibernate-models
*/ */
@Internal @Internal
@Deprecated
ReflectionManager getReflectionManager(); ReflectionManager getReflectionManager();
/** /**

View File

@ -38,18 +38,12 @@ public enum OptimisticLockStyle {
ALL; ALL;
public static OptimisticLockStyle fromLockType(OptimisticLockType type) { public static OptimisticLockStyle fromLockType(OptimisticLockType type) {
switch ( type ) { return switch ( type ) {
case VERSION: case VERSION -> VERSION;
return VERSION; case NONE -> NONE;
case NONE: case DIRTY -> DIRTY;
return NONE; case ALL -> ALL;
case DIRTY: };
return DIRTY;
case ALL:
return ALL;
default:
throw new AssertionFailure( "Unrecognized OptimisticLockType" );
}
} }
public boolean isAllOrDirty() { public boolean isAllOrDirty() {

View File

@ -0,0 +1,279 @@
/*
* 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.annotation.ElementType;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.boot.models.categorize.spi.GlobalRegistrations;
import org.hibernate.boot.models.categorize.spi.JpaEventListener;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.jpa.event.spi.CallbackDefinition;
import org.hibernate.jpa.event.spi.CallbackType;
import org.hibernate.mapping.Property;
import org.hibernate.models.spi.AnnotationUsage;
import org.hibernate.models.spi.ClassDetails;
import org.hibernate.models.spi.ClassDetailsRegistry;
import org.hibernate.models.spi.MethodDetails;
import org.hibernate.models.spi.SourceModelBuildingContext;
import org.hibernate.property.access.spi.Getter;
import org.jboss.logging.Logger;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.ExcludeDefaultListeners;
import jakarta.persistence.ExcludeSuperclassListeners;
import jakarta.persistence.MappedSuperclass;
import jakarta.persistence.PersistenceException;
/**
* Resolves JPA callback definitions
*
* @author Steve Ebersole
*/
public final class CallbackDefinitionResolver {
private static final Logger log = Logger.getLogger( CallbackDefinitionResolver.class );
public static List<CallbackDefinition> resolveEntityCallbacks(
MetadataBuildingContext metadataBuildingContext,
ClassDetails entityClass,
CallbackType callbackType) {
final GlobalRegistrations globalRegistrations = metadataBuildingContext.getMetadataCollector().getGlobalRegistrations();
final List<JpaEventListener> globalListenerRegistrations = globalRegistrations.getEntityListenerRegistrations();
List<CallbackDefinition> callbackDefinitions = new ArrayList<>();
List<String> callbacksMethodNames = new ArrayList<>();
List<ClassDetails> orderedListeners = new ArrayList<>();
ClassDetails currentClazz = entityClass;
boolean stopListeners = false;
boolean stopDefaultListeners = false;
do {
CallbackDefinition callbackDefinition = null;
final List<MethodDetails> methodsDetailsList = currentClazz.getMethods();
for ( MethodDetails methodDetails : methodsDetailsList ) {
if ( !methodDetails.hasAnnotationUsage( callbackType.getCallbackAnnotation() ) ) {
continue;
}
if ( callbacksMethodNames.contains( methodDetails.getName() ) ) {
continue;
}
//overridden method, remove the superclass overridden method
if ( callbackDefinition == null ) {
final Method javaMethod = (Method) methodDetails.toJavaMember();
callbackDefinition = new EntityCallback.Definition( javaMethod, callbackType );
Class<?> returnType = javaMethod.getReturnType();
Class<?>[] args = javaMethod.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() + " - " + methodDetails
);
}
ReflectHelper.ensureAccessibility( javaMethod );
if ( log.isDebugEnabled() ) {
log.debugf(
"Adding %s as %s callback for entity %s",
methodDetails.getName(),
callbackType.getCallbackAnnotation().getSimpleName(),
entityClass.getName()
);
}
callbackDefinitions.add( 0, callbackDefinition ); //superclass first
callbacksMethodNames.add( 0, methodDetails.getName() );
}
else {
throw new PersistenceException(
"You can only annotate one callback method with "
+ callbackType.getCallbackAnnotation().getName() + " in bean class: " + entityClass.getName()
);
}
}
if ( !stopListeners ) {
applyListeners( currentClazz, orderedListeners );
stopListeners = currentClazz.hasAnnotationUsage( ExcludeSuperclassListeners.class );
stopDefaultListeners = currentClazz.hasAnnotationUsage( ExcludeDefaultListeners.class );
}
do {
currentClazz = currentClazz.getSuperClass();
}
while ( currentClazz != null
&& !( currentClazz.hasAnnotationUsage( Entity.class )
|| currentClazz.hasAnnotationUsage( MappedSuperclass.class ) )
);
}
while ( currentClazz != null );
//handle default listeners
if ( !stopDefaultListeners ) {
if ( CollectionHelper.isNotEmpty( globalListenerRegistrations ) ) {
int defaultListenerSize = globalListenerRegistrations.size();
for ( int i = defaultListenerSize - 1; i >= 0; i-- ) {
orderedListeners.add( globalListenerRegistrations.get( i ).getCallbackClass() );
}
}
}
for ( ClassDetails listenerClassDetails : orderedListeners ) {
CallbackDefinition callbackDefinition = null;
if ( listenerClassDetails != null ) {
for ( MethodDetails methodDetails : listenerClassDetails.getMethods() ) {
if ( methodDetails.hasAnnotationUsage( callbackType.getCallbackAnnotation() ) ) {
final String methodName = methodDetails.getName();
//overridden method, remove the superclass overridden method
if ( callbackDefinition == null ) {
final Method method = (Method) methodDetails.toJavaMember();
callbackDefinition = new ListenerCallback.Definition(
listenerClassDetails.toJavaClass(),
method,
callbackType
);
final Class<?> returnType = method.getReturnType();
final Class<?>[] args = method.getParameterTypes();
if ( returnType != Void.TYPE || args.length != 1 ) {
throw new PersistenceException(
"Callback methods annotated in a listener bean class must return void and take one argument: "
+ callbackType.getCallbackAnnotation().getName() + " - " + methodDetails
);
}
ReflectHelper.ensureAccessibility( method );
if ( log.isDebugEnabled() ) {
log.debugf(
"Adding %s as %s callback for entity %s",
methodName,
callbackType.getCallbackAnnotation().getSimpleName(),
entityClass.getName()
);
}
callbackDefinitions.add( 0, callbackDefinition ); // listeners first
}
else {
throw new PersistenceException(
"You can only annotate one callback method with "
+ callbackType.getCallbackAnnotation().getName()
+ " in bean class: " + entityClass.getName()
+ " and callback listener: " + listenerClassDetails.getName()
);
}
}
}
}
}
return callbackDefinitions;
}
public static List<CallbackDefinition> resolveEmbeddableCallbacks(
MetadataBuildingContext metadataBuildingContext,
Class<?> entityClass,
Property embeddableProperty,
CallbackType callbackType) {
final SourceModelBuildingContext hibernateModelsContext = metadataBuildingContext.getMetadataCollector().getSourceModelBuildingContext();
final ClassDetailsRegistry classDetailsRegistry = hibernateModelsContext.getClassDetailsRegistry();
final Class<?> embeddableClass = embeddableProperty.getType().getReturnedClass();
final ClassDetails embeddableClassDetails = classDetailsRegistry.getClassDetails( embeddableClass.getName() );
final Getter embeddableGetter = embeddableProperty.getGetter( entityClass );
final List<CallbackDefinition> callbackDefinitions = new ArrayList<>();
final List<String> callbacksMethodNames = new ArrayList<>();
ClassDetails currentClazz = embeddableClassDetails;
do {
CallbackDefinition callbackDefinition = null;
final List<MethodDetails> methodsDetailsList = currentClazz.getMethods();
for ( MethodDetails methodDetails : methodsDetailsList ) {
if ( !methodDetails.hasAnnotationUsage( callbackType.getCallbackAnnotation() ) ) {
continue;
}
final Method method = (Method) methodDetails.toJavaMember();
final String methodName = method.getName();
if ( callbacksMethodNames.contains( methodName ) ) {
throw new PersistenceException(
"You can only annotate one callback method with "
+ callbackType.getCallbackAnnotation().getName() + " in bean class: " + currentClazz.getName()
);
}
//overridden method, remove the superclass overridden method
if ( callbackDefinition == null ) {
callbackDefinition = new EmbeddableCallback.Definition( 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() + " - " + methodDetails
);
}
ReflectHelper.ensureAccessibility( method );
if ( log.isDebugEnabled() ) {
log.debugf(
"Adding %s as %s callback for entity %s",
methodName,
callbackType.getCallbackAnnotation().getSimpleName(),
currentClazz.getName()
);
}
callbackDefinitions.add( 0, callbackDefinition ); //superclass first
callbacksMethodNames.add( 0, methodName );
}
}
do {
currentClazz = currentClazz.getSuperClass();
}
while ( currentClazz != null && !currentClazz.hasAnnotationUsage( MappedSuperclass.class ) );
}
while ( currentClazz != null );
return callbackDefinitions;
}
private static boolean useAnnotationAnnotatedByListener;
static {
//check whether reading annotations of annotations is useful or not
useAnnotationAnnotatedByListener = false;
Target target = EntityListeners.class.getAnnotation( Target.class );
if ( target != null ) {
for ( ElementType type : target.value() ) {
if ( type.equals( ElementType.ANNOTATION_TYPE ) ) {
useAnnotationAnnotatedByListener = true;
break;
}
}
}
}
private static void applyListeners(ClassDetails currentClazz, List<ClassDetails> listOfListeners) {
final AnnotationUsage<EntityListeners> entityListeners = currentClazz.getAnnotationUsage( EntityListeners.class );
if ( entityListeners != null ) {
final List<ClassDetails> listeners = entityListeners.getList( "value" );
listOfListeners.addAll( listeners );
}
if ( useAnnotationAnnotatedByListener ) {
final List<AnnotationUsage<?>> metaAnnotatedUsageList = currentClazz.getMetaAnnotated( EntityListeners.class );
for ( AnnotationUsage<?> metaAnnotatedUsage : metaAnnotatedUsageList ) {
final AnnotationUsage<EntityListeners> metaAnnotatedListeners = metaAnnotatedUsage.getAnnotationDescriptor().getAnnotationUsage( EntityListeners.class );
final List<ClassDetails> listeners = metaAnnotatedListeners.getList( "value" );
listOfListeners.addAll( listeners );
}
}
}
}