HHH-15959 add TypeBinders + fix multiple AttributeBinders on a single field

This commit is contained in:
Gavin 2023-01-01 18:21:07 +01:00 committed by Gavin King
parent e48a8120a9
commit df5980226c
14 changed files with 219 additions and 53 deletions

View File

@ -24,6 +24,8 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
* will be called when the annotation is discovered by Hibernate. * will be called when the annotation is discovered by Hibernate.
* *
* @author Gavin King * @author Gavin King
*
* @see TypeBinderType
*/ */
@Target(ANNOTATION_TYPE) @Target(ANNOTATION_TYPE)
@Retention(RUNTIME) @Retention(RUNTIME)

View File

@ -0,0 +1,38 @@
/*
* 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.annotations;
import org.hibernate.Incubating;
import org.hibernate.binder.TypeBinder;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Associates a user-defined annotation with a {@link TypeBinder},
* allowing the annotation to drive some custom model binding.
* <p>
* The user-defined annotation may be used to annotate entity and
* embeddable classes. The {@code TypeBinder} will be called when
* the annotation is discovered by Hibernate.
*
* @author Gavin King
*
* @see AttributeBinderType
*/
@Target(ANNOTATION_TYPE)
@Retention(RUNTIME)
@Incubating
public @interface TypeBinderType {
/**
* @return a type which implements {@link TypeBinder}
*/
Class<? extends TypeBinder<?>> binder();
}

View File

@ -0,0 +1,48 @@
/*
* 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.binder;
import org.hibernate.Incubating;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.PersistentClass;
import java.lang.annotation.Annotation;
/**
* Allows a user-written annotation to drive some customized model binding.
* <p>
* An implementation of this interface interacts directly with model objects
* like {@link PersistentClass} and {@link Component} to implement the
* semantics of some {@link org.hibernate.annotations.TypeBinderType
* custom mapping annotation}.
*
* @see org.hibernate.annotations.TypeBinderType
*
* @author Gavin King
*/
@Incubating
public interface TypeBinder<A extends Annotation> {
/**
* Perform some custom configuration of the model relating to the given annotated
* {@link PersistentClass entity class}.
*
* @param annotation an annotation of the entity class that is declared as an
* {@link org.hibernate.annotations.TypeBinderType}
* @param persistentClass the entity class
*/
void bind(A annotation, MetadataBuildingContext buildingContext, PersistentClass persistentClass);
/**
* Perform some custom configuration of the model relating to the given annotated
* {@link Component embeddable class}.
*
* @param annotation an annotation of the embeddable class that is declared as an
* {@link org.hibernate.annotations.TypeBinderType}
* @param embeddableClass the embeddable class
*/
void bind(A annotation, MetadataBuildingContext buildingContext, Component embeddableClass);
}

View File

@ -37,6 +37,5 @@ public class AttributeAccessorBinder implements AttributeBinder<AttributeAccesso
else { else {
throw new AnnotationException("'@AttributeAccessor' annotation must specify a 'strategy'"); throw new AnnotationException("'@AttributeAccessor' annotation must specify a 'strategy'");
} }
} }
} }

View File

@ -6,6 +6,7 @@
*/ */
/** /**
* Built-in implementations of {@link org.hibernate.binder.AttributeBinder}. * Built-in implementations of {@link org.hibernate.binder.AttributeBinder}
* and {@link org.hibernate.binder.TypeBinder}.
*/ */
package org.hibernate.binder.internal; package org.hibernate.binder.internal;

View File

@ -6,13 +6,23 @@
*/ */
/** /**
* This package defines an easy way to extend Hibernate with * This package defines an easy way to extend Hibernate with user-defined
* user-defined annotations that define customized O/R mappings * annotations that define customized O/R mappings of annotated entities
* of annotated entity attributes. * and annotated entity attributes.
* <p> * <ul>
* The meta-annotation * <li>The meta-annotation
* {@link org.hibernate.annotations.AttributeBinderType} * {@link org.hibernate.annotations.TypeBinderType @TypeBinderType}
* associates a {@link org.hibernate.binder.TypeBinder} with a
* user-written annotation which targets entity and embeddable
* {@linkplain java.lang.annotation.ElementType#TYPE types}.
* <li>The meta-annotation
* {@link org.hibernate.annotations.AttributeBinderType @AttributeBinderType}
* associates an {@link org.hibernate.binder.AttributeBinder} * associates an {@link org.hibernate.binder.AttributeBinder}
* with a user-written annotation. * with a user-written annotation which targets
* {@linkplain java.lang.annotation.ElementType#FIELD fields} and
* properties of entity types and embeddable classes.
*
* @see org.hibernate.binder.AttributeBinder
* @see org.hibernate.binder.TypeBinder
*/ */
package org.hibernate.binder; package org.hibernate.binder;

View File

@ -19,10 +19,12 @@ import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne; import jakarta.persistence.OneToOne;
import org.hibernate.AnnotationException; import org.hibernate.AnnotationException;
import org.hibernate.annotations.Instantiator; import org.hibernate.annotations.Instantiator;
import org.hibernate.annotations.TypeBinderType;
import org.hibernate.annotations.common.reflection.XAnnotatedElement; import org.hibernate.annotations.common.reflection.XAnnotatedElement;
import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.XMethod; import org.hibernate.annotations.common.reflection.XMethod;
import org.hibernate.annotations.common.reflection.XProperty; import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.binder.TypeBinder;
import org.hibernate.boot.spi.AccessType; import org.hibernate.boot.spi.AccessType;
import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.boot.spi.PropertyData; import org.hibernate.boot.spi.PropertyData;
@ -37,6 +39,7 @@ import org.hibernate.property.access.spi.PropertyAccessStrategy;
import org.hibernate.resource.beans.spi.ManagedBeanRegistry; import org.hibernate.resource.beans.spi.ManagedBeanRegistry;
import org.hibernate.usertype.CompositeUserType; import org.hibernate.usertype.CompositeUserType;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.ArrayList; import java.util.ArrayList;
@ -45,6 +48,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import static org.hibernate.boot.model.internal.BinderHelper.isGlobalGeneratorNameGlobal; import static org.hibernate.boot.model.internal.BinderHelper.isGlobalGeneratorNameGlobal;
import static org.hibernate.boot.model.internal.HCANNHelper.findContainingAnnotations;
import static org.hibernate.boot.model.internal.PropertyBinder.addElementsOfClass; import static org.hibernate.boot.model.internal.PropertyBinder.addElementsOfClass;
import static org.hibernate.boot.model.internal.PropertyBinder.processElementAnnotations; import static org.hibernate.boot.model.internal.PropertyBinder.processElementAnnotations;
import static org.hibernate.boot.model.internal.GeneratorBinder.buildGenerators; import static org.hibernate.boot.model.internal.GeneratorBinder.buildGenerators;
@ -109,7 +113,15 @@ public class EmbeddableBinder {
actualColumns = null; actualColumns = null;
} }
return bindEmbeddable( return createEmbeddedProperty(
inferredData,
propertyHolder,
entityBinder,
context,
isComponentEmbedded,
propertyBinder.isId(),
inheritanceStatePerClass,
bindEmbeddable(
inferredData, inferredData,
propertyHolder, propertyHolder,
entityBinder.getPropertyAccessor( property ), entityBinder.getPropertyAccessor( property ),
@ -125,6 +137,7 @@ public class EmbeddableBinder {
compositeUserType, compositeUserType,
actualColumns, actualColumns,
columns columns
)
); );
} }
@ -134,7 +147,7 @@ public class EmbeddableBinder {
|| returnedClass.isAnnotationPresent( Embeddable.class ); || returnedClass.isAnnotationPresent( Embeddable.class );
} }
private static PropertyBinder bindEmbeddable( private static Component bindEmbeddable(
PropertyData inferredData, PropertyData inferredData,
PropertyHolder propertyHolder, PropertyHolder propertyHolder,
AccessType propertyAccessor, AccessType propertyAccessor,
@ -189,18 +202,44 @@ public class EmbeddableBinder {
component.setKey( true ); component.setKey( true );
checkEmbeddedId( inferredData, propertyHolder, referencedEntityName, component ); checkEmbeddedId( inferredData, propertyHolder, referencedEntityName, component );
} }
callTypeBinders( component, context, inferredData.getPropertyClass() );
return component;
}
private static void callTypeBinders(Component component, MetadataBuildingContext context, XClass annotatedClass ) {
for ( Annotation containingAnnotation : findContainingAnnotations( annotatedClass, TypeBinderType.class) ) {
final TypeBinderType binderType = containingAnnotation.annotationType().getAnnotation( TypeBinderType.class );
try {
final TypeBinder binder = binderType.binder().newInstance();
binder.bind( containingAnnotation, context, component );
}
catch ( Exception e ) {
throw new AnnotationException( "error processing @TypeBinderType annotation '" + containingAnnotation + "'", e );
}
}
}
private static PropertyBinder createEmbeddedProperty(
PropertyData inferredData,
PropertyHolder propertyHolder,
EntityBinder entityBinder,
MetadataBuildingContext context,
boolean isComponentEmbedded,
boolean isId,
Map<XClass, InheritanceState> inheritanceStatePerClass,
Component component) {
final PropertyBinder binder = new PropertyBinder(); final PropertyBinder binder = new PropertyBinder();
binder.setDeclaringClass( inferredData.getDeclaringClass() ); binder.setDeclaringClass( inferredData.getDeclaringClass() );
binder.setName( inferredData.getPropertyName() ); binder.setName( inferredData.getPropertyName() );
binder.setValue( component ); binder.setValue(component);
binder.setProperty( inferredData.getProperty() ); binder.setProperty( inferredData.getProperty() );
binder.setAccessType( inferredData.getDefaultAccess() ); binder.setAccessType( inferredData.getDefaultAccess() );
binder.setEmbedded( isComponentEmbedded ); binder.setEmbedded(isComponentEmbedded);
binder.setHolder( propertyHolder ); binder.setHolder(propertyHolder);
binder.setId( isId ); binder.setId(isId);
binder.setEntityBinder( entityBinder ); binder.setEntityBinder(entityBinder);
binder.setInheritanceStatePerClass( inheritanceStatePerClass ); binder.setInheritanceStatePerClass(inheritanceStatePerClass);
binder.setBuildingContext( context ); binder.setBuildingContext(context);
binder.makePropertyAndBind(); binder.makePropertyAndBind();
return binder; return binder;
} }

View File

@ -77,10 +77,12 @@ import org.hibernate.annotations.SelectBeforeUpdate;
import org.hibernate.annotations.Subselect; import org.hibernate.annotations.Subselect;
import org.hibernate.annotations.Synchronize; import org.hibernate.annotations.Synchronize;
import org.hibernate.annotations.Tables; import org.hibernate.annotations.Tables;
import org.hibernate.annotations.TypeBinderType;
import org.hibernate.annotations.Where; import org.hibernate.annotations.Where;
import org.hibernate.annotations.common.reflection.ReflectionManager; import org.hibernate.annotations.common.reflection.ReflectionManager;
import org.hibernate.annotations.common.reflection.XAnnotatedElement; import org.hibernate.annotations.common.reflection.XAnnotatedElement;
import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.annotations.common.reflection.XClass;
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;
import org.hibernate.boot.model.internal.InheritanceState.ElementsToProcess; import org.hibernate.boot.model.internal.InheritanceState.ElementsToProcess;
@ -131,6 +133,7 @@ import static org.hibernate.boot.model.internal.GeneratorBinder.makeIdGenerator;
import static org.hibernate.boot.model.internal.BinderHelper.toAliasEntityMap; import static org.hibernate.boot.model.internal.BinderHelper.toAliasEntityMap;
import static org.hibernate.boot.model.internal.BinderHelper.toAliasTableMap; import static org.hibernate.boot.model.internal.BinderHelper.toAliasTableMap;
import static org.hibernate.boot.model.internal.EmbeddableBinder.fillEmbeddable; import static org.hibernate.boot.model.internal.EmbeddableBinder.fillEmbeddable;
import static org.hibernate.boot.model.internal.HCANNHelper.findContainingAnnotations;
import static org.hibernate.boot.model.internal.InheritanceState.getInheritanceStateOfSuperEntity; import static org.hibernate.boot.model.internal.InheritanceState.getInheritanceStateOfSuperEntity;
import static org.hibernate.boot.model.internal.PropertyBinder.addElementsOfClass; import static org.hibernate.boot.model.internal.PropertyBinder.addElementsOfClass;
import static org.hibernate.boot.model.internal.PropertyBinder.processElementAnnotations; import static org.hibernate.boot.model.internal.PropertyBinder.processElementAnnotations;
@ -233,6 +236,20 @@ public class EntityBinder {
// comment, checkConstraint, and indexes are processed here // comment, checkConstraint, and indexes are processed here
entityBinder.processComplementaryTableDefinitions(); entityBinder.processComplementaryTableDefinitions();
bindCallbacks( clazzToProcess, persistentClass, context ); bindCallbacks( clazzToProcess, persistentClass, context );
entityBinder.callTypeBinders( persistentClass );
}
private void callTypeBinders(PersistentClass persistentClass) {
for ( Annotation containingAnnotation : findContainingAnnotations( annotatedClass, TypeBinderType.class ) ) {
final TypeBinderType binderType = containingAnnotation.annotationType().getAnnotation( TypeBinderType.class );
try {
final TypeBinder binder = binderType.binder().newInstance();
binder.bind( containingAnnotation, context, persistentClass );
}
catch ( Exception e ) {
throw new AnnotationException( "error processing @TypeBinderType annotation '" + containingAnnotation + "'", e );
}
}
} }
private void handleIdentifier( private void handleIdentifier(

View File

@ -9,6 +9,8 @@ package org.hibernate.boot.model.internal;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement; import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Member; import java.lang.reflect.Member;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.Internal; import org.hibernate.Internal;
import org.hibernate.annotations.common.reflection.XAnnotatedElement; import org.hibernate.annotations.common.reflection.XAnnotatedElement;
@ -156,21 +158,20 @@ public final class HCANNHelper {
* *
* @implNote Searches only one level deep * @implNote Searches only one level deep
*/ */
public static <A extends Annotation, T extends Annotation> A findContainingAnnotation( public static List<Annotation> findContainingAnnotations(
XAnnotatedElement annotatedElement, XAnnotatedElement annotatedElement,
Class<T> annotationType) { Class<? extends Annotation> annotationType) {
final List<Annotation> result = new ArrayList<>();
final Annotation[] annotations = annotatedElement.getAnnotations(); final Annotation[] annotations = annotatedElement.getAnnotations();
for ( Annotation annotation : annotations ) { for ( Annotation annotation : annotations ) {
// annotation = @Sequence final Annotation metaAnn = annotation.annotationType().getAnnotation( annotationType );
final T metaAnn = annotation.annotationType().getAnnotation( annotationType );
if ( metaAnn != null ) { if ( metaAnn != null ) {
//noinspection unchecked result.add( annotation );
return (A) annotation;
} }
} }
return null; return result;
} }
} }

View File

@ -84,11 +84,12 @@ import static org.hibernate.boot.model.internal.EmbeddableBinder.createComposite
import static org.hibernate.boot.model.internal.EmbeddableBinder.createEmbeddable; import static org.hibernate.boot.model.internal.EmbeddableBinder.createEmbeddable;
import static org.hibernate.boot.model.internal.EmbeddableBinder.isEmbedded; import static org.hibernate.boot.model.internal.EmbeddableBinder.isEmbedded;
import static org.hibernate.boot.model.internal.HCANNHelper.findAnnotation; import static org.hibernate.boot.model.internal.HCANNHelper.findAnnotation;
import static org.hibernate.boot.model.internal.HCANNHelper.findContainingAnnotation; import static org.hibernate.boot.model.internal.HCANNHelper.findContainingAnnotations;
import static org.hibernate.boot.model.internal.TimeZoneStorageHelper.resolveTimeZoneStorageCompositeUserType; import static org.hibernate.boot.model.internal.TimeZoneStorageHelper.resolveTimeZoneStorageCompositeUserType;
import static org.hibernate.boot.model.internal.ToOneBinder.bindManyToOne; import static org.hibernate.boot.model.internal.ToOneBinder.bindManyToOne;
import static org.hibernate.boot.model.internal.ToOneBinder.bindOneToOne; import static org.hibernate.boot.model.internal.ToOneBinder.bindOneToOne;
import static org.hibernate.internal.util.StringHelper.qualify; import static org.hibernate.internal.util.StringHelper.qualify;
import static org.hibernate.internal.util.collections.CollectionHelper.combine;
import static org.hibernate.mapping.Constraint.hashedName; import static org.hibernate.mapping.Constraint.hashedName;
/** /**
@ -269,15 +270,14 @@ public class PropertyBinder {
@SuppressWarnings({"rawtypes", "unchecked"}) @SuppressWarnings({"rawtypes", "unchecked"})
private void callAttributeBinders(Property prop) { private void callAttributeBinders(Property prop) {
final Annotation containingAnnotation = findContainingAnnotation( property, AttributeBinderType.class); for ( Annotation containingAnnotation : findContainingAnnotations(property, AttributeBinderType.class ) ) {
if ( containingAnnotation != null ) {
final AttributeBinderType binderType = containingAnnotation.annotationType().getAnnotation( AttributeBinderType.class ); final AttributeBinderType binderType = containingAnnotation.annotationType().getAnnotation( AttributeBinderType.class );
try { try {
final AttributeBinder binder = binderType.binder().newInstance(); final AttributeBinder binder = binderType.binder().newInstance();
binder.bind( containingAnnotation, buildingContext, entityBinder.getPersistentClass(), prop ); binder.bind( containingAnnotation, buildingContext, entityBinder.getPersistentClass(), prop );
} }
catch (Exception e) { catch ( Exception e ) {
throw new AnnotationException( "error processing @AttributeBinderType annotation", e ); throw new AnnotationException( "error processing @AttributeBinderType annotation '" + containingAnnotation + "'", e );
} }
} }
} }
@ -1270,16 +1270,21 @@ public class PropertyBinder {
+ "' belongs to an '@IdClass' and may not be annotated '@Id' or '@EmbeddedId'" ); + "' belongs to an '@IdClass' and may not be annotated '@Id' or '@EmbeddedId'" );
} }
final XProperty idProperty = inferredData.getProperty(); final XProperty idProperty = inferredData.getProperty();
final Annotation idGeneratorAnnotation = findContainingAnnotation( idProperty, IdGeneratorType.class ); final List<Annotation> idGeneratorAnnotations = findContainingAnnotations( idProperty, IdGeneratorType.class );
final Annotation generatorAnnotation = findContainingAnnotation( idProperty, ValueGenerationType.class ); final List<Annotation> generatorAnnotations = findContainingAnnotations( idProperty, ValueGenerationType.class );
//TODO: validate that we don't have too many generator annotations and throw generatorAnnotations.removeAll( idGeneratorAnnotations );
if ( idGeneratorAnnotation != null ) { if ( idGeneratorAnnotations.size() + generatorAnnotations.size() > 1 ) {
idValue.setCustomIdGeneratorCreator( identifierGeneratorCreator( idProperty, idGeneratorAnnotation ) ); throw new AnnotationException( "Property '"+ getPath( propertyHolder, inferredData )
+ "' has too many generator annotations " + combine( idGeneratorAnnotations, generatorAnnotations ) );
} }
else if ( generatorAnnotation != null ) { if ( !idGeneratorAnnotations.isEmpty() ) {
idValue.setCustomIdGeneratorCreator( identifierGeneratorCreator( idProperty, idGeneratorAnnotations.get(0) ) );
}
else if ( !generatorAnnotations.isEmpty() ) {
// idValue.setCustomGeneratorCreator( generatorCreator( idProperty, generatorAnnotation ) ); // idValue.setCustomGeneratorCreator( generatorCreator( idProperty, generatorAnnotation ) );
throw new AnnotationException( "Property '"+ getPath( propertyHolder, inferredData ) throw new AnnotationException( "Property '"+ getPath( propertyHolder, inferredData )
+ "' is annotated '" + generatorAnnotation.annotationType() + "' which is not an '@IdGeneratorType'" ); + "' is annotated '" + generatorAnnotations.get(0).annotationType()
+ "' which is not an '@IdGeneratorType'" );
} }
else { else {
final XClass entityClass = inferredData.getClassOrElement(); final XClass entityClass = inferredData.getClassOrElement();

View File

@ -51,8 +51,8 @@ import static org.hibernate.id.IdentifierGeneratorHelper.POST_INSERT_INDICATOR;
/** /**
* A mapping model object that represents an {@linkplain jakarta.persistence.Embeddable embeddable class}. * A mapping model object that represents an {@linkplain jakarta.persistence.Embeddable embeddable class}.
* <p> *
* Note that the name of this class is historical and unfortunate. An embeddable class holds a "component" * @apiNote The name of this class is historical and unfortunate. An embeddable class holds a "component"
* of the state of an entity. It has absolutely nothing to do with modularity in software engineering. * of the state of an entity. It has absolutely nothing to do with modularity in software engineering.
* *
* @author Gavin King * @author Gavin King

View File

@ -73,7 +73,7 @@ public class Table implements Serializable, ContributableDatabaseObject {
private List<Function<SqlStringGenerationContext, InitCommand>> initCommandProducers; private List<Function<SqlStringGenerationContext, InitCommand>> initCommandProducers;
@Deprecated(since="6.2") @Remove @Deprecated(since="6.2", forRemoval = true)
public Table() { public Table() {
this( "orm" ); this( "orm" );
} }

View File

@ -133,7 +133,10 @@
</li> </li>
<li> <li>
{@link org.hibernate.context.spi} defines support for context-bound "current" sessions {@link org.hibernate.context.spi} defines support for context-bound "current" sessions
and contextual multi-tenancy, and contextual multi-tenancy, and
</li>
<li>
{@link org.hibernate.binder} allows for user-defined mapping annotations.
</li> </li>
</ul> </ul>
<p> <p>

View File

@ -133,7 +133,10 @@
</li> </li>
<li> <li>
{@link org.hibernate.context.spi} defines support for context-bound "current" sessions {@link org.hibernate.context.spi} defines support for context-bound "current" sessions
and contextual multi-tenancy, and contextual multi-tenancy, and
</li>
<li>
{@link org.hibernate.binder} allows for user-defined mapping annotations.
</li> </li>
</ul> </ul>
<p> <p>