From a7a455c39a64d4a0d297c76f37cf13a3ce0050db Mon Sep 17 00:00:00 2001 From: Gavin Date: Wed, 7 Dec 2022 12:12:01 +0100 Subject: [PATCH] HHH-15834 add @TypeRegistration annotation --- .../bitset/BitSetRegisteredUserTypeTest.java | 137 ++++++++++++++++++ .../hibernate/annotations/CompositeType.java | 7 +- .../CompositeTypeRegistration.java | 12 +- .../java/org/hibernate/annotations/Type.java | 7 +- .../annotations/TypeRegistration.java | 41 ++++++ .../annotations/TypeRegistrations.java | 28 ++++ .../InFlightMetadataCollectorImpl.java | 20 ++- .../boot/spi/InFlightMetadataCollector.java | 4 + .../org/hibernate/cfg/AnnotationBinder.java | 33 +++++ .../cfg/annotations/BasicValueBinder.java | 11 ++ .../hibernate/usertype/CompositeUserType.java | 12 ++ .../java/org/hibernate/usertype/UserType.java | 11 ++ .../strategy/usertype/registered/Person.java | 1 - 13 files changed, 315 insertions(+), 9 deletions(-) create mode 100644 documentation/src/test/java/org/hibernate/userguide/mapping/basic/bitset/BitSetRegisteredUserTypeTest.java create mode 100644 hibernate-core/src/main/java/org/hibernate/annotations/TypeRegistration.java create mode 100644 hibernate-core/src/main/java/org/hibernate/annotations/TypeRegistrations.java diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/bitset/BitSetRegisteredUserTypeTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/bitset/BitSetRegisteredUserTypeTest.java new file mode 100644 index 0000000000..4167428289 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/bitset/BitSetRegisteredUserTypeTest.java @@ -0,0 +1,137 @@ +/* + * 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.userguide.mapping.basic.bitset; + +import jakarta.persistence.Column; +import jakarta.persistence.ColumnResult; +import jakarta.persistence.ConstructorResult; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.NamedNativeQuery; +import jakarta.persistence.SqlResultSetMapping; +import org.hibernate.annotations.TypeRegistration; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import java.util.BitSet; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; + +public class BitSetRegisteredUserTypeTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Product.class + }; + } + + @Test + public void test() { + + BitSet bitSet = BitSet.valueOf(new long[] {1, 2, 3}); + + doInHibernate(this::sessionFactory, session -> { + Product product = new Product(); + product.setId(1); + product.setBitSet(bitSet); + session.persist(product); + }); + + doInHibernate(this::sessionFactory, session -> { + Product product = session.get(Product.class, 1); + assertEquals(bitSet, product.getBitSet()); + }); + } + + @Test + public void testNativeQuery() { + BitSet bitSet = BitSet.valueOf(new long[] {1, 2, 3}); + + doInHibernate(this::sessionFactory, session -> { + Product product = new Product(); + product.setId(1); + product.setBitSet(bitSet); + session.persist(product); + }); + + doInHibernate(this::sessionFactory, session -> { + Product product = (Product) session.getNamedNativeQuery( + "find_person_by_bitset") + .setParameter("id", 1L) + .getSingleResult(); + + assertEquals(bitSet, product.getBitSet()); + }); + } + + @Override + protected boolean isCleanupTestDataRequired() { + return true; + } + + @NamedNativeQuery( + name = "find_person_by_bitset", + query = + "SELECT " + + " pr.id AS \"pr.id\", " + + " pr.bitset_col AS \"pr.bitset\" " + + "FROM Product pr " + + "WHERE pr.id = :id", + resultSetMapping = "Person" + ) + @SqlResultSetMapping( + name = "Person", + classes = @ConstructorResult( + targetClass = Product.class, + columns = { + @ColumnResult(name = "pr.id"), + @ColumnResult(name = "pr.bitset", type = BitSetUserType.class) + } + ) + ) + //tag::basic-custom-type-registered-BitSetUserType-mapping-example[] + @Entity(name = "Product") + @TypeRegistration(basicClass = BitSet.class, userType = BitSetUserType.class) + public static class Product { + + @Id + private Integer id; + + @Column(name = "bitset_col") + private BitSet bitSet; + + //Constructors, getters, and setters are omitted for brevity + //end::basic-custom-type-registered-BitSetUserType-mapping-example[] + public Product() { + } + + public Product(Number id, BitSet bitSet) { + this.id = id.intValue(); + this.bitSet = bitSet; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public BitSet getBitSet() { + return bitSet; + } + + public void setBitSet(BitSet bitSet) { + this.bitSet = bitSet; + } + //tag::basic-custom-type-BitSetUserType-mapping-example[] + } + //end::basic-custom-type-BitSetUserType-mapping-example[] +} diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/CompositeType.java b/hibernate-core/src/main/java/org/hibernate/annotations/CompositeType.java index 28c33c430e..7137f9b916 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/CompositeType.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/CompositeType.java @@ -7,6 +7,7 @@ package org.hibernate.annotations; import java.lang.annotation.Retention; +import java.lang.annotation.Target; import org.hibernate.usertype.CompositeUserType; @@ -16,11 +17,13 @@ /** * Applies a custom {@link CompositeUserType} for the attribute mapping. + * + * @see CompositeUserType + * @see CompositeTypeRegistration */ -@java.lang.annotation.Target({METHOD, FIELD}) +@Target({METHOD, FIELD}) @Retention(RUNTIME) public @interface CompositeType { - /** * The custom type implementor class */ diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/CompositeTypeRegistration.java b/hibernate-core/src/main/java/org/hibernate/annotations/CompositeTypeRegistration.java index 4e9e3a5cda..687a6b992c 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/CompositeTypeRegistration.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/CompositeTypeRegistration.java @@ -18,10 +18,16 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; /** - * Registers a custom composite user type implementation to be used - * for all references to a particular {@link jakarta.persistence.Embeddable}. + * Registers a custom {@linkplain CompositeUserType composite user type} + * implementation to be used by default for all references to a particular + * {@linkplain jakarta.persistence.Embeddable embeddable} class. *

- * May be overridden for a specific embedded using {@link org.hibernate.annotations.CompositeType} + * May be overridden for a specific entity field or property using + * {@link CompositeType}. + * + * @see CompositeUserType + * @see CompositeType + * @see TypeRegistration */ @Target( {TYPE, ANNOTATION_TYPE, PACKAGE} ) @Retention( RUNTIME ) diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/Type.java b/hibernate-core/src/main/java/org/hibernate/annotations/Type.java index 266d0ce8c5..a4889b2033 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/Type.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/Type.java @@ -7,6 +7,7 @@ package org.hibernate.annotations; import java.lang.annotation.Retention; +import java.lang.annotation.Target; import org.hibernate.usertype.UserType; @@ -19,11 +20,13 @@ *

* This is usually mutually exclusive with the compositional approach of * {@link JavaType}, {@link JdbcType}, etc. + * + * @see UserType + * @see TypeRegistration */ -@java.lang.annotation.Target({METHOD, FIELD}) +@Target({METHOD, FIELD}) @Retention(RUNTIME) public @interface Type { - /** * The implementation class which implements {@link UserType}. */ diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/TypeRegistration.java b/hibernate-core/src/main/java/org/hibernate/annotations/TypeRegistration.java new file mode 100644 index 0000000000..01458dc862 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/annotations/TypeRegistration.java @@ -0,0 +1,41 @@ +/* + * 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.usertype.UserType; + +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.PACKAGE; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Registers a custom {@linkplain UserType user type} implementation + * to be used by default for all references to a particular basic type. + *

+ * May be overridden for a specific entity field or property using + * {@link Type}. + * + * @see UserType + * @see Type + * @see CompositeTypeRegistration + * + * @author Gavin King + * + * @since 6.2 + */ +@Target( {TYPE, ANNOTATION_TYPE, PACKAGE} ) +@Retention( RUNTIME ) +@Repeatable( TypeRegistrations.class ) +public @interface TypeRegistration { + Class basicClass(); + Class> userType(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/TypeRegistrations.java b/hibernate-core/src/main/java/org/hibernate/annotations/TypeRegistrations.java new file mode 100644 index 0000000000..f51329ca67 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/annotations/TypeRegistrations.java @@ -0,0 +1,28 @@ +/* + * 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 java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.PACKAGE; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Grouping of {@link TypeRegistration} + * + * @author Gavin King + * + * @since 6.2 + */ +@Target( {TYPE, ANNOTATION_TYPE, PACKAGE} ) +@Retention( RUNTIME ) +public @interface TypeRegistrations { + TypeRegistration[] value(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java index 1275cf0b7f..331e8d3f68 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java @@ -113,6 +113,7 @@ import jakarta.persistence.Embeddable; import jakarta.persistence.Entity; import jakarta.persistence.MapsId; +import org.hibernate.usertype.UserType; /** * The implementation of the {@linkplain InFlightMetadataCollector in-flight @@ -429,8 +430,25 @@ public Class> findRegisteredCompositeUserType(Cla return registeredCompositeUserTypes.get( embeddableType ); } - private Map collectionTypeRegistrations; + private Map, Class>> registeredUserTypes; + @Override + public void registerUserType(Class basicType, Class> userType) { + if ( registeredUserTypes == null ) { + registeredUserTypes = new HashMap<>(); + } + registeredUserTypes.put( basicType, userType ); + } + @Override + public Class> findRegisteredUserType(Class basicType) { + if ( registeredUserTypes == null ) { + return null; + } + + return registeredUserTypes.get( basicType ); + } + + private Map collectionTypeRegistrations; @Override public void addCollectionTypeRegistration(CollectionTypeRegistration registrationAnnotation) { diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/InFlightMetadataCollector.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/InFlightMetadataCollector.java index 472dceb0f7..698a70c5ad 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/InFlightMetadataCollector.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/InFlightMetadataCollector.java @@ -57,6 +57,7 @@ import org.hibernate.usertype.UserCollectionType; import jakarta.persistence.AttributeConverter; +import org.hibernate.usertype.UserType; /** * An in-flight representation of {@link org.hibernate.boot.Metadata} while it is being built. @@ -306,6 +307,9 @@ void addTableNameBinding( void registerCompositeUserType(Class embeddableType, Class> userType); Class> findRegisteredCompositeUserType(Class embeddableType); + void registerUserType(Class embeddableType, Class> userType); + Class> findRegisteredUserType(Class basicType); + void addCollectionTypeRegistration(CollectionTypeRegistration registrationAnnotation); void addCollectionTypeRegistration(CollectionClassification classification, CollectionTypeRegistrationDescriptor descriptor); CollectionTypeRegistrationDescriptor findCollectionTypeRegistration(CollectionClassification classification); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java index 96f674bc0a..d2d3f2f453 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java @@ -56,6 +56,8 @@ import org.hibernate.annotations.Parameter; import org.hibernate.annotations.Parent; import org.hibernate.annotations.TimeZoneStorage; +import org.hibernate.annotations.TypeRegistration; +import org.hibernate.annotations.TypeRegistrations; import org.hibernate.annotations.ValueGenerationType; import org.hibernate.annotations.common.reflection.ReflectionManager; import org.hibernate.annotations.common.reflection.XAnnotatedElement; @@ -307,6 +309,7 @@ public static void bindPackage(ClassLoaderService cls, String packageName, Metad handleTypeDescriptorRegistrations( annotatedPackage, context ); bindEmbeddableInstantiatorRegistrations( annotatedPackage, context ); + bindUserTypeRegistrations( annotatedPackage, context ); bindCompositeUserTypeRegistrations( annotatedPackage, context ); handleConverterRegistrations( annotatedPackage, context ); @@ -536,6 +539,7 @@ public static void bindClass( final Map generators = buildGenerators( annotatedClass, context ); handleTypeDescriptorRegistrations( annotatedClass, context ); bindEmbeddableInstantiatorRegistrations( annotatedClass, context ); + bindUserTypeRegistrations( annotatedClass, context ); bindCompositeUserTypeRegistrations( annotatedClass, context ); handleConverterRegistrations( annotatedClass, context ); @@ -686,6 +690,35 @@ private static void bindCompositeUserTypeRegistrations( } } + private static void bindUserTypeRegistrations( + XAnnotatedElement annotatedElement, + MetadataBuildingContext context) { + final TypeRegistration typeRegistration = + annotatedElement.getAnnotation( TypeRegistration.class ); + if ( typeRegistration != null ) { + handleUserTypeRegistration( context, typeRegistration ); + } + else { + final TypeRegistrations typeRegistrations = + annotatedElement.getAnnotation( TypeRegistrations.class ); + if ( typeRegistrations != null ) { + final TypeRegistration[] registrations = typeRegistrations.value(); + for ( TypeRegistration registration : registrations ) { + handleUserTypeRegistration( context, registration ); + } + } + } + } + + private static void handleUserTypeRegistration( + MetadataBuildingContext context, + TypeRegistration compositeTypeRegistration) { + context.getMetadataCollector().registerUserType( + compositeTypeRegistration.basicClass(), + compositeTypeRegistration.userType() + ); + } + private static void handleCompositeUserTypeRegistration( MetadataBuildingContext context, CompositeTypeRegistration compositeTypeRegistration) { diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/BasicValueBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/BasicValueBinder.java index 6b265476c2..4de0aeadd3 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/BasicValueBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/BasicValueBinder.java @@ -324,6 +324,17 @@ public void setType( // An explicit custom UserType has top precedence when we get to BasicValue resolution. return; } + else if ( modelTypeXClass != null ) { + final Class basicClass = buildingContext.getBootstrapContext() + .getReflectionManager() + .toClass( modelTypeXClass ); + final Class> registeredUserTypeImpl = + buildingContext.getMetadataCollector().findRegisteredUserType( basicClass ); + if ( registeredUserTypeImpl != null ) { + applyExplicitType( registeredUserTypeImpl, new Parameter[0] ); + return; + } + } switch ( kind ) { case ATTRIBUTE: { diff --git a/hibernate-core/src/main/java/org/hibernate/usertype/CompositeUserType.java b/hibernate-core/src/main/java/org/hibernate/usertype/CompositeUserType.java index 9fe87cb161..47e220c9ac 100644 --- a/hibernate-core/src/main/java/org/hibernate/usertype/CompositeUserType.java +++ b/hibernate-core/src/main/java/org/hibernate/usertype/CompositeUserType.java @@ -44,6 +44,18 @@ *

* Every implementor of {@code CompositeUserType} must be immutable * and must declare a public default constructor. + *

+ * A custom type may be applied to an attribute of an entity either: + *

+ * + * @see org.hibernate.annotations.CompositeType + * @see org.hibernate.annotations.CompositeTypeRegistration */ @Incubating public interface CompositeUserType extends EmbeddableInstantiator { diff --git a/hibernate-core/src/main/java/org/hibernate/usertype/UserType.java b/hibernate-core/src/main/java/org/hibernate/usertype/UserType.java index 78ce8a2378..a97af1403c 100644 --- a/hibernate-core/src/main/java/org/hibernate/usertype/UserType.java +++ b/hibernate-core/src/main/java/org/hibernate/usertype/UserType.java @@ -52,10 +52,21 @@ * may be used in queries. If a custom type does have attributes, and * can be thought of as something more like an embeddable object, it * might be better to implement {@link CompositeUserType}. + *

+ * A custom type may be applied to an attribute of an entity either: + *

* * @see org.hibernate.type.Type * @see org.hibernate.type.CustomType * + * @see org.hibernate.annotations.Type + * @see org.hibernate.annotations.TypeRegistration + * * @author Gavin King */ public interface UserType { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/strategy/usertype/registered/Person.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/strategy/usertype/registered/Person.java index 55c8aa341e..80d86e5f89 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/strategy/usertype/registered/Person.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/strategy/usertype/registered/Person.java @@ -9,7 +9,6 @@ import java.util.HashSet; import java.util.Set; -import org.hibernate.annotations.CompositeType; import org.hibernate.annotations.CompositeTypeRegistration; import org.hibernate.orm.test.mapping.embeddable.strategy.usertype.embedded.Name; import org.hibernate.orm.test.mapping.embeddable.strategy.usertype.embedded.NameCompositeUserType;