HHH-15834 add @TypeRegistration annotation

This commit is contained in:
Gavin 2022-12-07 12:12:01 +01:00 committed by Gavin King
parent 60468dadf0
commit a7a455c39a
13 changed files with 315 additions and 9 deletions

View File

@ -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[]
}

View File

@ -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 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* 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
*/

View File

@ -18,10 +18,16 @@ import static java.lang.annotation.ElementType.TYPE;
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.
* <p>
* 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 )

View File

@ -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 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
* <p>
* 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}.
*/

View File

@ -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.
* <p>
* 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<? extends UserType<?>> userType();
}

View File

@ -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();
}

View File

@ -113,6 +113,7 @@ import jakarta.persistence.AttributeConverter;
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 InFlightMetadataCollectorImpl implements InFlightMetadataCollector
return registeredCompositeUserTypes.get( embeddableType );
}
private Map<CollectionClassification, CollectionTypeRegistrationDescriptor> collectionTypeRegistrations;
private Map<Class<?>, Class<? extends UserType<?>>> registeredUserTypes;
@Override
public void registerUserType(Class<?> basicType, Class<? extends UserType<?>> userType) {
if ( registeredUserTypes == null ) {
registeredUserTypes = new HashMap<>();
}
registeredUserTypes.put( basicType, userType );
}
@Override
public Class<? extends UserType<?>> findRegisteredUserType(Class<?> basicType) {
if ( registeredUserTypes == null ) {
return null;
}
return registeredUserTypes.get( basicType );
}
private Map<CollectionClassification, CollectionTypeRegistrationDescriptor> collectionTypeRegistrations;
@Override
public void addCollectionTypeRegistration(CollectionTypeRegistration registrationAnnotation) {

View File

@ -57,6 +57,7 @@ import org.hibernate.usertype.CompositeUserType;
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 @@ public interface InFlightMetadataCollector extends Mapping, MetadataImplementor
void registerCompositeUserType(Class<?> embeddableType, Class<? extends CompositeUserType<?>> userType);
Class<? extends CompositeUserType<?>> findRegisteredCompositeUserType(Class<?> embeddableType);
void registerUserType(Class<?> embeddableType, Class<? extends UserType<?>> userType);
Class<? extends UserType<?>> findRegisteredUserType(Class<?> basicType);
void addCollectionTypeRegistration(CollectionTypeRegistration registrationAnnotation);
void addCollectionTypeRegistration(CollectionClassification classification, CollectionTypeRegistrationDescriptor descriptor);
CollectionTypeRegistrationDescriptor findCollectionTypeRegistration(CollectionClassification classification);

View File

@ -56,6 +56,8 @@ import org.hibernate.annotations.ParamDef;
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 final class AnnotationBinder {
handleTypeDescriptorRegistrations( annotatedPackage, context );
bindEmbeddableInstantiatorRegistrations( annotatedPackage, context );
bindUserTypeRegistrations( annotatedPackage, context );
bindCompositeUserTypeRegistrations( annotatedPackage, context );
handleConverterRegistrations( annotatedPackage, context );
@ -536,6 +539,7 @@ public final class AnnotationBinder {
final Map<String, IdentifierGeneratorDefinition> generators = buildGenerators( annotatedClass, context );
handleTypeDescriptorRegistrations( annotatedClass, context );
bindEmbeddableInstantiatorRegistrations( annotatedClass, context );
bindUserTypeRegistrations( annotatedClass, context );
bindCompositeUserTypeRegistrations( annotatedClass, context );
handleConverterRegistrations( annotatedClass, context );
@ -686,6 +690,35 @@ public final class AnnotationBinder {
}
}
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) {

View File

@ -324,6 +324,17 @@ public class BasicValueBinder implements JdbcTypeIndicators {
// 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<? extends UserType<?>> registeredUserTypeImpl =
buildingContext.getMetadataCollector().findRegisteredUserType( basicClass );
if ( registeredUserTypeImpl != null ) {
applyExplicitType( registeredUserTypeImpl, new Parameter[0] );
return;
}
}
switch ( kind ) {
case ATTRIBUTE: {

View File

@ -44,6 +44,18 @@ import org.hibernate.metamodel.spi.ValueAccess;
* <p>
* Every implementor of {@code CompositeUserType} must be immutable
* and must declare a public default constructor.
* <p>
* A custom type may be applied to an attribute of an entity either:
* <ul>
* <li>explicitly, using
* {@link org.hibernate.annotations.CompositeType @CompositeType},
* or
* <li>implicitly, using
* {@link org.hibernate.annotations.CompositeTypeRegistration @CompositeTypeRegistration}.
* </ul>
*
* @see org.hibernate.annotations.CompositeType
* @see org.hibernate.annotations.CompositeTypeRegistration
*/
@Incubating
public interface CompositeUserType<J> extends EmbeddableInstantiator {

View File

@ -52,10 +52,21 @@ import org.hibernate.type.spi.TypeConfiguration;
* 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}.
* <p>
* A custom type may be applied to an attribute of an entity either:
* <ul>
* <li>explicitly, using {@link org.hibernate.annotations.Type @Type},
* or
* <li>implicitly, using
* {@link org.hibernate.annotations.TypeRegistration @TypeRegistration}.
* </ul>
*
* @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<J> {

View File

@ -9,7 +9,6 @@ package org.hibernate.orm.test.mapping.embeddable.strategy.usertype.registered;
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;