HHH-14885 - New composite user-type

`@EmbeddableInstantiatorRegistration`
This commit is contained in:
Steve Ebersole 2021-12-01 18:16:29 -06:00
parent 924c2b29c3
commit 61d178ef1b
13 changed files with 351 additions and 6 deletions

View File

@ -288,6 +288,20 @@ include::{instantiatorTestDir}/embeddable/Person.java[tags=embeddable-instantiat
====
Additionally, instantiators can be registered:
[[embeddable-instantiator-registration-ex]]
.`@EmbeddableInstantiatorRegistration`
====
[source, JAVA, indent=0]
----
include::{instantiatorTestDir}/registered/Name.java[tags=embeddable-instantiator-registration]
include::{instantiatorTestDir}/registered/Person.java[tags=embeddable-instantiator-registration]
----
====
[[embeddable-multiple-namingstrategy]]
==== Embeddables and ImplicitNamingStrategy

View File

@ -16,7 +16,7 @@ import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Allows supplying a custom instantiator implementation
* Specifies a custom instantiator implementation
*/
@Target( {TYPE, FIELD, METHOD, ANNOTATION_TYPE} )
@Retention( RUNTIME )

View File

@ -0,0 +1,29 @@
/*
* 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.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
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 instantiator implementation
*/
@Target( {TYPE, ANNOTATION_TYPE, PACKAGE} )
@Retention( RUNTIME )
@Repeatable( EmbeddableInstantiatorRegistrations.class )
public @interface EmbeddableInstantiatorRegistration {
Class<?> embeddableClass();
Class<? extends EmbeddableInstantiator> instantiator();
}

View File

@ -0,0 +1,24 @@
/*
* 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;
/**
* @author Steve Ebersole
*/
@Target( {TYPE, ANNOTATION_TYPE, PACKAGE} )
@Retention( RUNTIME )
public @interface EmbeddableInstantiatorRegistrations {
EmbeddableInstantiatorRegistration[] value();
}

View File

@ -98,6 +98,7 @@ import org.hibernate.mapping.RootClass;
import org.hibernate.mapping.SimpleValue;
import org.hibernate.mapping.Table;
import org.hibernate.mapping.UniqueKey;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
import org.hibernate.query.named.NamedObjectRepository;
import org.hibernate.query.sqm.function.SqmFunctionDescriptor;
import org.hibernate.type.descriptor.java.JavaType;
@ -399,6 +400,26 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector
getTypeConfiguration().getJdbcTypeDescriptorRegistry().addDescriptor( typeCode, jdbcType );
}
private Map<Class<?>, Class<? extends EmbeddableInstantiator>> registeredInstantiators;
@Override
public void registerEmbeddableInstantiator(Class<?> embeddableType, Class<? extends EmbeddableInstantiator> instantiator) {
if ( registeredInstantiators == null ) {
registeredInstantiators = new HashMap<>();
}
registeredInstantiators.put( embeddableType, instantiator );
}
@Override
public Class<? extends EmbeddableInstantiator> findRegisteredEmbeddableInstantiator(Class<?> embeddableType) {
if ( registeredInstantiators == null ) {
return null;
}
return registeredInstantiators.get( embeddableType );
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// attribute converters

View File

@ -49,6 +49,7 @@ import org.hibernate.mapping.Join;
import org.hibernate.mapping.MappedSuperclass;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Table;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
@ -319,6 +320,9 @@ public interface InFlightMetadataCollector extends Mapping, MetadataImplementor
void addJavaTypeRegistration(Class<?> javaType, JavaType<?> jtd);
void addJdbcTypeRegistration(int typeCode, JdbcType jdbcType);
void registerEmbeddableInstantiator(Class<?> embeddableType, Class<? extends EmbeddableInstantiator> instantiator);
Class<? extends EmbeddableInstantiator> findRegisteredEmbeddableInstantiator(Class<?> embeddableType);
interface DelayedPropertyReferenceHandler extends Serializable {
void process(InFlightMetadataCollector metadataCollector);
}

View File

@ -33,6 +33,8 @@ import org.hibernate.annotations.Columns;
import org.hibernate.annotations.Comment;
import org.hibernate.annotations.DiscriminatorFormula;
import org.hibernate.annotations.DiscriminatorOptions;
import org.hibernate.annotations.EmbeddableInstantiatorRegistration;
import org.hibernate.annotations.EmbeddableInstantiatorRegistrations;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchProfile;
import org.hibernate.annotations.FetchProfiles;
@ -350,6 +352,7 @@ public final class AnnotationBinder {
}
handleTypeDescriptorRegistrations( pckg, context );
bindEmbeddableInstantiatorRegistrations( pckg, context );
bindGenericGenerators( pckg, context );
bindQueries( pckg, context );
@ -782,6 +785,7 @@ public final class AnnotationBinder {
// try to find class level generators
HashMap<String, IdentifierGeneratorDefinition> classGenerators = buildGenerators( clazzToProcess, context );
handleTypeDescriptorRegistrations( clazzToProcess, context );
bindEmbeddableInstantiatorRegistrations( clazzToProcess, context );
// check properties
final InheritanceState.ElementsToProcess elementsToProcess = inheritanceState.getElementsToProcess();
@ -910,6 +914,36 @@ public final class AnnotationBinder {
context.getMetadataCollector().addJavaTypeRegistration( annotation.javaType(), jtd );
}
private static void bindEmbeddableInstantiatorRegistrations(XAnnotatedElement annotatedElement, MetadataBuildingContext context) {
final ManagedBeanRegistry managedBeanRegistry = context.getBootstrapContext()
.getServiceRegistry()
.getService( ManagedBeanRegistry.class );
final EmbeddableInstantiatorRegistration instantiatorReg = annotatedElement.getAnnotation( EmbeddableInstantiatorRegistration.class );
if ( instantiatorReg != null ) {
handleEmbeddableInstantiatorRegistration( context, managedBeanRegistry, instantiatorReg );
}
else {
final EmbeddableInstantiatorRegistrations annotation = annotatedElement.getAnnotation( EmbeddableInstantiatorRegistrations.class );
if ( annotation != null ) {
final EmbeddableInstantiatorRegistration[] registrations = annotation.value();
for ( int i = 0; i < registrations.length; i++ ) {
handleEmbeddableInstantiatorRegistration( context, managedBeanRegistry, registrations[i] );
}
}
}
}
private static void handleEmbeddableInstantiatorRegistration(
MetadataBuildingContext context,
ManagedBeanRegistry managedBeanRegistry,
EmbeddableInstantiatorRegistration annotation) {
context.getMetadataCollector().registerEmbeddableInstantiator(
annotation.embeddableClass(),
annotation.instantiator()
);
}
/**
* Process all discriminator-related metadata per rules for "single table" inheritance
*/
@ -2309,7 +2343,7 @@ public final class AnnotationBinder {
}
final AccessType propertyAccessor = entityBinder.getPropertyAccessor( property );
final Class<? extends EmbeddableInstantiator> customInstantiatorImpl = determineCustomInstantiator( property, returnedClass );
final Class<? extends EmbeddableInstantiator> customInstantiatorImpl = determineCustomInstantiator( property, returnedClass, context );
propertyBinder = bindComponent(
inferredData,
@ -2457,7 +2491,10 @@ public final class AnnotationBinder {
}
}
private static Class<? extends EmbeddableInstantiator> determineCustomInstantiator(XProperty property, XClass returnedClass) {
private static Class<? extends EmbeddableInstantiator> determineCustomInstantiator(
XProperty property,
XClass returnedClass,
MetadataBuildingContext context) {
if ( property.isAnnotationPresent( EmbeddedId.class ) ) {
// we don't allow custom instantiators for composite ids
return null;
@ -2473,6 +2510,11 @@ public final class AnnotationBinder {
return classAnnotation.value();
}
final Class embeddableClass = context.getBootstrapContext().getReflectionManager().toClass( returnedClass );
if ( embeddableClass != null ) {
return context.getMetadataCollector().findRegisteredEmbeddableInstantiator( embeddableClass );
}
return null;
}
@ -2912,6 +2954,7 @@ public final class AnnotationBinder {
buildingContext.getMetadataCollector().addSecondPass( secondPass );
handleTypeDescriptorRegistrations( property, buildingContext );
bindEmbeddableInstantiatorRegistrations( property, buildingContext );
}
else {
Map<String, IdentifierGeneratorDefinition> localGenerators = new HashMap<>( buildGenerators( property, buildingContext ) );

View File

@ -1610,7 +1610,7 @@ public abstract class CollectionBinder {
false,
false,
true,
resolveCustomInstantiator( property, elementClass ),
resolveCustomInstantiator( property, elementClass, buildingContext ),
buildingContext,
inheritanceStatePerClass
);
@ -1672,17 +1672,25 @@ public abstract class CollectionBinder {
}
private Class<? extends EmbeddableInstantiator> resolveCustomInstantiator(XProperty property, XClass embeddableClass) {
private Class<? extends EmbeddableInstantiator> resolveCustomInstantiator(
XProperty property,
XClass propertyClass,
MetadataBuildingContext context) {
final org.hibernate.annotations.EmbeddableInstantiator propertyAnnotation = property.getAnnotation( org.hibernate.annotations.EmbeddableInstantiator.class );
if ( propertyAnnotation != null ) {
return propertyAnnotation.value();
}
final org.hibernate.annotations.EmbeddableInstantiator classAnnotation = embeddableClass.getAnnotation( org.hibernate.annotations.EmbeddableInstantiator.class );
final org.hibernate.annotations.EmbeddableInstantiator classAnnotation = propertyClass.getAnnotation( org.hibernate.annotations.EmbeddableInstantiator.class );
if ( classAnnotation != null ) {
return classAnnotation.value();
}
final Class<?> embeddableClass = context.getBootstrapContext().getReflectionManager().toClass( propertyClass );
if ( embeddableClass != null ) {
return context.getMetadataCollector().findRegisteredEmbeddableInstantiator( embeddableClass );
}
return null;
}

View File

@ -0,0 +1,44 @@
/*
* 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.orm.test.mapping.embeddable.strategy.instantiator.registered;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Steve Ebersole
*/
@DomainModel( annotatedClasses = { Person.class, Name.class } )
@SessionFactory
public class InstantiationTests {
@Test
public void basicTest(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
final Person mick = new Person( 1, new Name( "Mick", "Jagger" ) );
session.persist( mick );
final Person john = new Person( 2, new Name( "John", "Doe" ) );
john.addAlias( new Name( "Jon", "Doe" ) );
session.persist( john );
} );
scope.inTransaction( (session) -> {
final Person mick = session.createQuery( "from Person where id = 1", Person.class ).uniqueResult();
assertThat( mick.getName().getFirstName() ).isEqualTo( "Mick" );
} );
scope.inTransaction( (session) -> {
final Person john = session.createQuery( "from Person p join fetch p.aliases where p.id = 2", Person.class ).uniqueResult();
assertThat( john.getName().getFirstName() ).isEqualTo( "John" );
assertThat( john.getAliases() ).hasSize( 1 );
final Name alias = john.getAliases().iterator().next();
assertThat( alias.getFirstName() ).isEqualTo( "Jon" );
} );
}
}

View File

@ -0,0 +1,31 @@
/*
* 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.orm.test.mapping.embeddable.strategy.instantiator.registered;
//tag::embeddable-instantiator-registration[]
public class Name {
private final String first;
private final String last;
private Name() {
throw new UnsupportedOperationException();
}
public Name(String first, String last) {
this.first = first;
this.last = last;
}
public String getFirstName() {
return first;
}
public String getLastName() {
return last;
}
}
//end::embeddable-instantiator-registration[]

View File

@ -0,0 +1,36 @@
/*
* 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.orm.test.mapping.embeddable.strategy.instantiator.registered;
import java.util.function.Supplier;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
/**
* @author Steve Ebersole
*/
public class NameInstantiator implements EmbeddableInstantiator {
@Override
public Object instantiate(Supplier<Object[]> valuesAccess, SessionFactoryImplementor sessionFactory) {
final Object[] values = valuesAccess.get();
// alphabetical
final String first = (String) values[0];
final String last = (String) values[1];
return new Name( first, last );
}
@Override
public boolean isInstance(Object object, SessionFactoryImplementor sessionFactory) {
return object instanceof Name;
}
@Override
public boolean isSameClass(Object object, SessionFactoryImplementor sessionFactory) {
return object.getClass().equals( Name.class );
}
}

View File

@ -0,0 +1,80 @@
/*
* 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.orm.test.mapping.embeddable.strategy.instantiator.registered; /**
* @author Steve Ebersole
*/
import java.util.HashSet;
import java.util.Set;
import org.hibernate.annotations.EmbeddableInstantiatorRegistration;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Embedded;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
//tag::embeddable-instantiator-registration[]
@Entity(name = "Person")
@Table(name = "persons")
@EmbeddableInstantiatorRegistration( embeddableClass = Name.class, instantiator = NameInstantiator.class )
public class Person {
@Id
public Integer id;
@Embedded
public Name name;
@ElementCollection
@Embedded
public Set<Name> aliases;
//end::embeddable-instantiator-registration[]
private Person() {
// for Hibernate use
}
public Person(Integer id, Name name) {
this.id = id;
this.name = name;
}
public Integer getId() {
return id;
}
public Name getName() {
return name;
}
public void setName(Name name) {
this.name = name;
}
public void setId(Integer id) {
this.id = id;
}
public Set<Name> getAliases() {
return aliases;
}
public void setAliases(Set<Name> aliases) {
this.aliases = aliases;
}
public void addAlias(Name alias) {
if ( aliases == null ) {
aliases = new HashSet<>();
}
aliases.add( alias );
}
//tag::embeddable-instantiator-registration[]
}
//end::embeddable-instantiator-registration[]

View File

@ -0,0 +1,11 @@
/*
* 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
*/
/**
* Tests for custom {@link org.hibernate.metamodel.spi.EmbeddableInstantiator} usage
*/
package org.hibernate.orm.test.mapping.embeddable.strategy.instantiator.registered;