HHH-14885 - New composite user-type

Working support for `@EmbeddableInstantiator` on either the embedded site or on the embeddable class.
This commit is contained in:
Steve Ebersole 2021-12-01 17:35:43 -06:00
parent 8ab27a0ff0
commit 924c2b29c3
28 changed files with 676 additions and 75 deletions

View File

@ -1,6 +1,10 @@
[[embeddables]]
=== Embeddable types
:rootProjectDir: ../../../../../../..
:sourcedir: ../../../../../test/java/org/hibernate/userguide/mapping/embeddable
:coreProjectDir: {rootProjectDir}/hibernate-core
:coreTestSrcDir: {rootProjectDir}/hibernate-core/src/test/java
:instantiatorTestDir: {coreTestSrcDir}/org/hibernate/orm/test/mapping/embeddable/strategy/instantiator
:extrasdir: extras
Historically Hibernate called these components.
@ -76,28 +80,22 @@ include::{sourcedir}/SimpleEmbeddableEquivalentTest.java[tag=embeddable-type-map
The composition form is certainly more object-oriented, and that becomes more evident as we work with multiple embeddable types.
[[embeddable-multiple]]
==== Multiple embeddable types
Although from an object-oriented perspective, it's much more convenient to work with embeddable types, this example doesn't work as-is.
When the same embeddable type is included multiple times in the same parent entity type, the Jakarta Persistence specification demands to set the associated column names explicitly.
This requirement is due to how object properties are mapped to database columns.
By default, Jakarta Persistence expects a database column having the same name with its associated object property.
When including multiple embeddables, the implicit name-based mapping rule doesn't work anymore because multiple object properties could end-up being mapped to the same database column.
We have a few options to handle this issue.
[[embeddable-override]]
==== Overriding Embeddable types
Jakarta Persistence defines the `@AttributeOverride` annotation to handle this scenario.
This way, the mapping conflict is resolved by setting up explicit name-based property-column type mappings.
Although from an object-oriented perspective, it's much more convenient to work with embeddable types, when we reuse the same
embeddable multiple times on the same class, the Jakarta Persistence specification requires to set the associated column names explicitly.
If an Embeddable type is used multiple times in some entity, you need to use the
{jpaJavadocUrlPrefix}AttributeOverride.html[`@AttributeOverride`] and
{jpaJavadocUrlPrefix}AssociationOverride.html[`@AssociationOverride`] annotations
to override the default column names defined by the Embeddable.
This requirement is due to how object properties are mapped to database columns.
By default, Jakarta Persistence expects a database column having the same name with its associated object property.
When including multiple embeddables, the implicit name-based mapping rule doesn't work anymore because multiple object
properties could end-up being mapped to the same database column.
When an embeddable type is used multiple times, Jakarta Persistence defines the `@AttributeOverride`
and `@AssociationOverride` annotations to handle this scenario to override the default column names defined
by the Embeddable.
NOTE: See <<embeddable-multiple-namingstrategy>> for an alternative to using `@AttributeOverride` and `@AssociationOverride`
Considering you have the following `Publisher` embeddable type
which defines a `@ManyToOne` association with the `Country` entity:
@ -135,46 +133,6 @@ include::{extrasdir}/embeddable/embeddable-type-override-mapping-example.sql[]
----
====
[[embeddable-multiple-namingstrategy]]
==== Embeddables and ImplicitNamingStrategy
[IMPORTANT]
====
The `ImplicitNamingStrategyComponentPathImpl` is a Hibernate-specific feature.
Users concerned with Jakarta Persistence provider portability should instead prefer explicit column naming with `@AttributeOverride`.
====
Hibernate naming strategies are covered in detail in <<chapters/domain/naming.adoc#naming,Naming>>.
However, for the purposes of this discussion, Hibernate has the capability to interpret implicit column names in a way that is safe for use with multiple embeddable types.
[[embeddable-multiple-namingstrategy-entity-mapping]]
.Implicit multiple embeddable type mapping
====
[source,java]
----
include::{sourcedir}/EmbeddableImplicitOverrideTest.java[tag=embeddable-multiple-namingstrategy-entity-mapping, indent=0]
----
====
To make it work, you need to use the `ImplicitNamingStrategyComponentPathImpl` naming strategy.
[[embeddable-multiple-ImplicitNamingStrategyComponentPathImpl]]
.Enabling implicit embeddable type mapping using the component path naming strategy
====
[source,java]
----
include::{sourcedir}/EmbeddableImplicitOverrideTest.java[tag=embeddable-multiple-ImplicitNamingStrategyComponentPathImpl, indent=0]
----
====
Now the "path" to attributes are used in the implicit column naming:
[source,sql]
----
include::{extrasdir}/embeddable/embeddable-multiple-namingstrategy-entity-mapping.sql[]
----
You could even develop your own naming strategy to do other types of implicit naming strategies.
[[embeddable-collections]]
==== Collections of embeddable types
@ -293,3 +251,81 @@ include::{sourcedir}/ParentTest.java[tags=embeddable-Parent-fetching-example]
====
Therefore, the `@Parent` annotation is used to define the association between an embeddable type and the owning entity.
[[embeddable-instantiator]]
==== Custom instantiation
Jakarta Persistence requires embeddable classes to follow Java Bean conventions. Part of this is the
definition of a non-arg constructor. However, not all value compositions applications might map as embeddable
values follow Java Bean conventions - e.g. a struct or Java 15 record.
Hibernate allows the use of a custom instantiator for creating the embeddable instances through the
`org.hibernate.metamodel.spi.EmbeddableInstantiator` contract. The custom instantiator is specified
using the `@org.hibernate.annotations.EmbeddableInstantiator` annotation. Which can either be defined
on the property:
[[embeddable-instantiator-property-ex]]
.`@EmbeddableInstantiator` on attribute
====
[source, JAVA, indent=0]
----
include::{instantiatorTestDir}/embedded/Name.java[tags=embeddable-instantiator-property]
include::{instantiatorTestDir}/embedded/Person.java[tags=embeddable-instantiator-property]
----
====
or on the embeddable class:
[[embeddable-instantiator-class-ex]]
.`@EmbeddableInstantiator` on class
====
[source, JAVA, indent=0]
----
include::{instantiatorTestDir}/embeddable/Name.java[tags=embeddable-instantiator-class]
include::{instantiatorTestDir}/embeddable/Person.java[tags=embeddable-instantiator-class]
----
====
[[embeddable-multiple-namingstrategy]]
==== Embeddables and ImplicitNamingStrategy
[IMPORTANT]
====
The `ImplicitNamingStrategyComponentPathImpl` is a Hibernate-specific feature.
Users concerned with Jakarta Persistence provider portability should instead prefer explicit column naming with `@AttributeOverride`.
====
Hibernate naming strategies are covered in detail in <<chapters/domain/naming.adoc#naming,Naming>>.
However, for the purposes of this discussion, Hibernate has the capability to interpret implicit column names in a way that is safe for use with multiple embeddable types.
[[embeddable-multiple-namingstrategy-entity-mapping]]
.Implicit multiple embeddable type mapping
====
[source,java]
----
include::{sourcedir}/EmbeddableImplicitOverrideTest.java[tag=embeddable-multiple-namingstrategy-entity-mapping, indent=0]
----
====
To make it work, you need to use the `ImplicitNamingStrategyComponentPathImpl` naming strategy.
[[embeddable-multiple-ImplicitNamingStrategyComponentPathImpl]]
.Enabling implicit embeddable type mapping using the component path naming strategy
====
[source,java]
----
include::{sourcedir}/EmbeddableImplicitOverrideTest.java[tag=embeddable-multiple-ImplicitNamingStrategyComponentPathImpl, indent=0]
----
====
Now the "path" to attributes are used in the implicit column naming:
[source,sql]
----
include::{extrasdir}/embeddable/embeddable-multiple-namingstrategy-entity-mapping.sql[]
----
You could even develop your own naming strategy to do other types of implicit naming strategies.

View File

@ -0,0 +1,25 @@
/*
* 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.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Allows supplying a custom instantiator implementation
*/
@Target( {TYPE, FIELD, METHOD, ANNOTATION_TYPE} )
@Retention( RUNTIME )
public @interface EmbeddableInstantiator {
Class<? extends org.hibernate.metamodel.spi.EmbeddableInstantiator> value();
}

View File

@ -120,6 +120,7 @@ import org.hibernate.mapping.SingleTableSubclass;
import org.hibernate.mapping.Subclass;
import org.hibernate.mapping.ToOne;
import org.hibernate.mapping.UnionSubclass;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
import org.hibernate.resource.beans.spi.ManagedBeanRegistry;
import org.hibernate.type.descriptor.java.BasicJavaType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
@ -1158,6 +1159,7 @@ public final class AnnotationBinder {
true,
true,
false,
null,
context,
inheritanceStatePerClass
);
@ -2286,6 +2288,11 @@ public final class AnnotationBinder {
}
}
if ( ! isComponent ) {
if ( property.isAnnotationPresent( Embedded.class ) ) {
}
}
isComponent = isComponent
|| property.isAnnotationPresent( Embedded.class )
|| property.isAnnotationPresent( EmbeddedId.class )
@ -2300,7 +2307,10 @@ public final class AnnotationBinder {
);
referencedEntityName = mapsIdProperty.getClassOrElementName();
}
AccessType propertyAccessor = entityBinder.getPropertyAccessor( property );
final AccessType propertyAccessor = entityBinder.getPropertyAccessor( property );
final Class<? extends EmbeddableInstantiator> customInstantiatorImpl = determineCustomInstantiator( property, returnedClass );
propertyBinder = bindComponent(
inferredData,
propertyHolder,
@ -2312,6 +2322,7 @@ public final class AnnotationBinder {
isId,
inheritanceStatePerClass,
referencedEntityName,
customInstantiatorImpl,
isOverridden ? ( Ejb3JoinColumn[] ) columns : null
);
}
@ -2446,6 +2457,25 @@ public final class AnnotationBinder {
}
}
private static Class<? extends EmbeddableInstantiator> determineCustomInstantiator(XProperty property, XClass returnedClass) {
if ( property.isAnnotationPresent( EmbeddedId.class ) ) {
// we don't allow custom instantiators for composite ids
return null;
}
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 = returnedClass.getAnnotation( org.hibernate.annotations.EmbeddableInstantiator.class );
if ( classAnnotation != null ) {
return classAnnotation.value();
}
return null;
}
private static boolean isGlobalGeneratorNameGlobal(MetadataBuildingContext context) {
return context.getBootstrapContext().getJpaCompliance().isGlobalGeneratorScopeEnabled();
}
@ -2659,10 +2689,11 @@ public final class AnnotationBinder {
boolean isId, //is an identifier
Map<XClass, InheritanceState> inheritanceStatePerClass,
String referencedEntityName, //is a component who is overridden by a @MapsId
Class<? extends EmbeddableInstantiator> customInstantiatorImpl,
Ejb3JoinColumn[] columns) {
Component comp;
if ( referencedEntityName != null ) {
comp = createComponent( propertyHolder, inferredData, isComponentEmbedded, isIdentifierMapper, buildingContext );
comp = createComponent( propertyHolder, inferredData, isComponentEmbedded, isIdentifierMapper, customInstantiatorImpl, buildingContext );
SecondPass sp = new CopyIdentifierComponentSecondPass(
comp,
referencedEntityName,
@ -2675,7 +2706,7 @@ public final class AnnotationBinder {
comp = fillComponent(
propertyHolder, inferredData, propertyAccessor, !isId, entityBinder,
isComponentEmbedded, isIdentifierMapper,
false, buildingContext, inheritanceStatePerClass
false, customInstantiatorImpl, buildingContext, inheritanceStatePerClass
);
}
if ( isId ) {
@ -2721,6 +2752,7 @@ public final class AnnotationBinder {
boolean isComponentEmbedded,
boolean isIdentifierMapper,
boolean inSecondPass,
Class<? extends EmbeddableInstantiator> customInstantiatorImpl,
MetadataBuildingContext buildingContext,
Map<XClass, InheritanceState> inheritanceStatePerClass) {
return fillComponent(
@ -2733,6 +2765,7 @@ public final class AnnotationBinder {
isComponentEmbedded,
isIdentifierMapper,
inSecondPass,
customInstantiatorImpl,
buildingContext,
inheritanceStatePerClass
);
@ -2748,6 +2781,7 @@ public final class AnnotationBinder {
boolean isComponentEmbedded,
boolean isIdentifierMapper,
boolean inSecondPass,
Class<? extends EmbeddableInstantiator> customInstantiatorImpl,
MetadataBuildingContext buildingContext,
Map<XClass, InheritanceState> inheritanceStatePerClass) {
/**
@ -2755,7 +2789,8 @@ public final class AnnotationBinder {
* Because it's a value type, there is no bidirectional association, hence second pass
* ordering does not matter
*/
Component comp = createComponent( propertyHolder, inferredData, isComponentEmbedded, isIdentifierMapper, buildingContext );
Component comp = createComponent( propertyHolder, inferredData, isComponentEmbedded, isIdentifierMapper, customInstantiatorImpl, buildingContext );
String subpath = BinderHelper.getPath( propertyHolder, inferredData );
LOG.tracev( "Binding component with path: {0}", subpath );
PropertyHolder subHolder = PropertyHolderBuilder.buildPropertyHolder(
@ -2899,6 +2934,7 @@ public final class AnnotationBinder {
PropertyData inferredData,
boolean isComponentEmbedded,
boolean isIdentifierMapper,
Class<? extends EmbeddableInstantiator> customInstantiatorImpl,
MetadataBuildingContext context) {
Component comp = new Component( context, propertyHolder.getPersistentClass() );
comp.setEmbedded( isComponentEmbedded );
@ -2911,6 +2947,7 @@ public final class AnnotationBinder {
else {
comp.setComponentClassName( inferredData.getClassOrElementName() );
}
comp.setCustomInstantiator( customInstantiatorImpl );
return comp;
}
@ -2954,6 +2991,7 @@ public final class AnnotationBinder {
isEmbedded,
isIdentifierMapper,
false,
null,
buildingContext,
inheritanceStatePerClass
);

View File

@ -93,6 +93,7 @@ import org.hibernate.mapping.Property;
import org.hibernate.mapping.Selectable;
import org.hibernate.mapping.SimpleValue;
import org.hibernate.mapping.Table;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
import org.jboss.logging.Logger;
@ -1597,6 +1598,7 @@ public abstract class CollectionBinder {
inferredData = new PropertyPreloadedData( AccessType.PROPERTY, "collection&&element", elementClass );
}
}
//TODO be smart with isNullable
boolean isNullable = true;
Component component = AnnotationBinder.fillComponent(
@ -1608,6 +1610,7 @@ public abstract class CollectionBinder {
false,
false,
true,
resolveCustomInstantiator( property, elementClass ),
buildingContext,
inheritanceStatePerClass
);
@ -1669,6 +1672,20 @@ public abstract class CollectionBinder {
}
private Class<? extends EmbeddableInstantiator> resolveCustomInstantiator(XProperty property, XClass embeddableClass) {
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 );
if ( classAnnotation != null ) {
return classAnnotation.value();
}
return null;
}
private String extractHqlOrderBy(jakarta.persistence.OrderBy jpaOrderBy) {
if ( jpaOrderBy != null ) {
return jpaOrderBy.value(); // Null not possible. In case of empty expression, apply default ordering.

View File

@ -296,6 +296,7 @@ public class MapBinder extends CollectionBinder {
false,
false,
true,
null,
buildingContext,
inheritanceStatePerClass
);

View File

@ -41,6 +41,7 @@ import org.hibernate.mapping.RootClass;
import org.hibernate.mapping.SimpleValue;
import org.hibernate.mapping.ToOne;
import org.hibernate.mapping.Value;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
import org.hibernate.property.access.spi.PropertyAccessStrategy;
import org.hibernate.tuple.AnnotationValueGeneration;
import org.hibernate.tuple.GenerationTiming;
@ -222,6 +223,7 @@ public class PropertyBinder {
new PropertyPreloadedData(null, null, null),
true,
false,
resolveCustomInstantiator( property, returnedClass ),
buildingContext
);
rootClass.setIdentifier( identifier );
@ -260,6 +262,20 @@ public class PropertyBinder {
return prop;
}
private Class<? extends EmbeddableInstantiator> resolveCustomInstantiator(XProperty property, XClass embeddableClass) {
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 );
if ( classAnnotation != null ) {
return classAnnotation.value();
}
return null;
}
//used when the value is provided and the binding is done elsewhere
public Property makeProperty() {
validateMake();

View File

@ -29,6 +29,7 @@ import org.hibernate.id.factory.IdentifierGeneratorFactory;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.internal.util.collections.JoinedIterator;
import org.hibernate.metamodel.RepresentationMode;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
import org.hibernate.property.access.spi.Setter;
import org.hibernate.tuple.component.ComponentMetamodel;
import org.hibernate.type.ComponentType;
@ -54,6 +55,7 @@ public class Component extends SimpleValue implements MetaAttributable {
private boolean isKey;
private String roleName;
private Class<? extends EmbeddableInstantiator> customInstantiator;
private Map<RepresentationMode,String> tuplizerImpls;
// cache the status of the type
@ -576,4 +578,11 @@ public class Component extends SimpleValue implements MetaAttributable {
return this.originalPropertyOrder = originalPropertyOrder;
}
public Class<? extends EmbeddableInstantiator> getCustomInstantiator() {
return customInstantiator;
}
public void setCustomInstantiator(Class<? extends EmbeddableInstantiator> customInstantiator) {
this.customInstantiator = customInstantiator;
}
}

View File

@ -12,7 +12,6 @@ import java.util.function.Supplier;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.mapping.Component;
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
/**
* Support for instantiating embeddables as dynamic-map representation
@ -21,7 +20,7 @@ import org.hibernate.metamodel.spi.EmbeddableInstantiator;
*/
public class EmbeddableInstantiatorDynamicMap
extends AbstractDynamicMapInstantiator
implements EmbeddableInstantiator {
implements StandardEmbeddableInstantiator {
private final Supplier<EmbeddableMappingType> runtimeDescriptorAccess;
public EmbeddableInstantiatorDynamicMap(

View File

@ -10,7 +10,6 @@ import java.util.function.Supplier;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
import org.hibernate.type.descriptor.java.JavaType;
import static org.hibernate.bytecode.spi.ReflectionOptimizer.InstantiationOptimizer;
@ -19,7 +18,7 @@ import static org.hibernate.bytecode.spi.ReflectionOptimizer.InstantiationOptimi
* Support for instantiating embeddables as POJO representation
* using bytecode optimizer
*/
public class EmbeddableInstantiatorPojoOptimized extends AbstractPojoInstantiator implements EmbeddableInstantiator {
public class EmbeddableInstantiatorPojoOptimized extends AbstractPojoInstantiator implements StandardEmbeddableInstantiator {
private final Supplier<EmbeddableMappingType> embeddableMappingAccess;
private final InstantiationOptimizer instantiationOptimizer;

View File

@ -16,13 +16,12 @@ import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
import org.hibernate.type.descriptor.java.JavaType;
/**
* Support for instantiating embeddables as POJO representation
*/
public class EmbeddableInstantiatorPojoStandard extends AbstractPojoInstantiator implements EmbeddableInstantiator {
public class EmbeddableInstantiatorPojoStandard extends AbstractPojoInstantiator implements StandardEmbeddableInstantiator {
private static final CoreMessageLogger LOG = CoreLogging.messageLogger( PojoInstantiatorImpl.class );
private final Supplier<EmbeddableMappingType> embeddableMappingAccess;

View File

@ -10,12 +10,11 @@ import java.util.function.Supplier;
import org.hibernate.bytecode.spi.BasicProxyFactory;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
/**
* EmbeddableInstantiator used for instantiating "proxies" of an embeddable.
*/
public class EmbeddableInstantiatorProxied implements EmbeddableInstantiator {
public class EmbeddableInstantiatorProxied implements StandardEmbeddableInstantiator {
private final Class<?> proxiedClass;
private final BasicProxyFactory factory;

View File

@ -31,9 +31,12 @@ public class EmbeddableRepresentationStrategyMap implements EmbeddableRepresenta
public EmbeddableRepresentationStrategyMap(
Component bootDescriptor,
Supplier<EmbeddableMappingType> runtimeDescriptorAccess,
EmbeddableInstantiator customInstantiator,
RuntimeModelCreationContext creationContext) {
this.mapJtd = creationContext.getTypeConfiguration().getJavaTypeDescriptorRegistry().getDescriptor( Map.class );
this.instantiator = new EmbeddableInstantiatorDynamicMap( bootDescriptor, runtimeDescriptorAccess );
this.instantiator = customInstantiator != null
? customInstantiator
: new EmbeddableInstantiatorDynamicMap( bootDescriptor, runtimeDescriptorAccess );
}
@Override

View File

@ -44,6 +44,7 @@ public class EmbeddableRepresentationStrategyPojo extends AbstractEmbeddableRepr
public EmbeddableRepresentationStrategyPojo(
Component bootDescriptor,
Supplier<EmbeddableMappingType> runtimeDescriptorAccess,
EmbeddableInstantiator customInstantiator,
RuntimeModelCreationContext creationContext) {
super(
bootDescriptor,
@ -69,7 +70,9 @@ public class EmbeddableRepresentationStrategyPojo extends AbstractEmbeddableRepr
false
);
this.instantiator = determineInstantiator( bootDescriptor, runtimeDescriptorAccess, creationContext );
this.instantiator = customInstantiator != null
? customInstantiator
: determineInstantiator( bootDescriptor, runtimeDescriptorAccess, creationContext );
}
private EmbeddableInstantiator determineInstantiator(

View File

@ -114,6 +114,8 @@ public class EntityRepresentationStrategyPojoStandard implements EntityRepresent
mapsIdRepresentationStrategy = new EmbeddableRepresentationStrategyPojo(
bootDescriptor.getIdentifierMapper(),
() -> ( ( CompositeTypeImplementor) bootDescriptor.getIdentifierMapper().getType() ).getMappingModelPart().getEmbeddableTypeDescriptor(),
// we currently do not support custom instantiators for identifiers
null,
creationContext
);
}
@ -121,6 +123,8 @@ public class EntityRepresentationStrategyPojoStandard implements EntityRepresent
mapsIdRepresentationStrategy = new EmbeddableRepresentationStrategyPojo(
(Component) bootDescriptorIdentifier,
() -> ( ( CompositeTypeImplementor) bootDescriptor.getIdentifierMapper().getType() ).getMappingModelPart().getEmbeddableTypeDescriptor(),
// we currently do not support custom instantiators for identifiers
null,
creationContext
);
}

View File

@ -13,11 +13,13 @@ import org.hibernate.mapping.Component;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.metamodel.RepresentationMode;
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
import org.hibernate.metamodel.spi.EmbeddableRepresentationStrategy;
import org.hibernate.metamodel.spi.EntityRepresentationStrategy;
import org.hibernate.metamodel.spi.ManagedTypeRepresentationResolver;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.resource.beans.spi.ManagedBeanRegistry;
/**
* @author Steve Ebersole
@ -74,8 +76,26 @@ public class ManagedTypeRepresentationResolverStandard implements ManagedTypeRep
}
}
final EmbeddableInstantiator customInstantiator;
if ( bootDescriptor.getCustomInstantiator() != null ) {
final Class<? extends EmbeddableInstantiator> customInstantiatorImpl = bootDescriptor.getCustomInstantiator();
customInstantiator = creationContext.getBootstrapContext()
.getServiceRegistry()
.getService( ManagedBeanRegistry.class )
.getBean( customInstantiatorImpl )
.getBeanInstance();
}
else {
customInstantiator = null;
}
if ( representation == RepresentationMode.MAP ) {
return new EmbeddableRepresentationStrategyMap( bootDescriptor, runtimeDescriptorAccess, creationContext );
return new EmbeddableRepresentationStrategyMap(
bootDescriptor,
runtimeDescriptorAccess,
customInstantiator,
creationContext
);
}
else {
// todo (6.0) : fix this
@ -84,7 +104,12 @@ public class ManagedTypeRepresentationResolverStandard implements ManagedTypeRep
//
// instead, resolve ReflectionOptimizer once - here - and pass along to
// StandardPojoRepresentationStrategy
return new EmbeddableRepresentationStrategyPojo( bootDescriptor, runtimeDescriptorAccess, creationContext );
return new EmbeddableRepresentationStrategyPojo(
bootDescriptor,
runtimeDescriptorAccess,
customInstantiator,
creationContext
);
}
}
}

View File

@ -0,0 +1,17 @@
/*
* 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.metamodel.internal;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
/**
* Marker interface for standard EmbeddableInstantiator implementations.
*
* This allows us to recognize custom instantiators
*/
public interface StandardEmbeddableInstantiator extends EmbeddableInstantiator {
}

View File

@ -37,6 +37,7 @@ import org.hibernate.mapping.Property;
import org.hibernate.mapping.Selectable;
import org.hibernate.mapping.Table;
import org.hibernate.metamodel.UnsupportedMappingException;
import org.hibernate.metamodel.internal.StandardEmbeddableInstantiator;
import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
@ -45,6 +46,7 @@ import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.ManagedMappingType;
import org.hibernate.metamodel.mapping.MappingModelCreationLogger;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.NonTransientException;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.SelectableConsumer;
import org.hibernate.metamodel.mapping.SelectableMapping;
@ -129,6 +131,9 @@ public class EmbeddableMappingTypeImpl extends AbstractEmbeddableMapping impleme
}
}
catch (Exception e) {
if ( e instanceof NonTransientException ) {
throw e;
}
MappingModelCreationLogger.LOGGER.debugf(
e,
"(DEBUG) Error finalizing EmbeddableMappingType(%s)",
@ -550,6 +555,7 @@ public class EmbeddableMappingTypeImpl extends AbstractEmbeddableMapping impleme
"EmbeddableMappingType(" + getEmbeddedValueMapping().getNavigableRole().getFullPath() + ")#initColumnMappings",
this::initColumnMappings
);
return true;
}

View File

@ -12,6 +12,7 @@ import org.hibernate.bytecode.spi.ReflectionOptimizer;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.mapping.Property;
import org.hibernate.metamodel.RepresentationMode;
import org.hibernate.metamodel.internal.StandardEmbeddableInstantiator;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
import org.hibernate.metamodel.spi.EmbeddableRepresentationStrategy;
@ -60,7 +61,7 @@ public class VirtualIdRepresentationStrategy implements EmbeddableRepresentation
);
}
private static class InstantiatorAdapter implements EmbeddableInstantiator {
private static class InstantiatorAdapter implements StandardEmbeddableInstantiator {
private final VirtualIdEmbeddable virtualIdEmbeddable;
private final EntityInstantiator entityInstantiator;

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.embeddable;
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,35 @@
/*
* 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.embeddable;
import org.hibernate.annotations.EmbeddableInstantiator;
//tag::embeddable-instantiator-class[]
@EmbeddableInstantiator( NameInstantiator.class )
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-class[]

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.embeddable;
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,75 @@
/*
* 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.embeddable; /**
* @author Steve Ebersole
*/
import java.util.HashSet;
import java.util.Set;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Embedded;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
@Entity(name = "Person")
@Table(name = "persons")
//tag::embeddable-instantiator-class[]
public class Person {
@Id
public Integer id;
@Embedded
public Name name;
@ElementCollection
@Embedded
public Set<Name> aliases;
//end::embeddable-instantiator-class[]
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-class[]
}
//end::embeddable-instantiator-class[]

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.embeddable;

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.embedded;
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.embedded;
//tag::embeddable-instantiator-property[]
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-property[]

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.embedded;
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,81 @@
/*
* 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.embedded; /**
* @author Steve Ebersole
*/
import java.util.HashSet;
import java.util.Set;
import org.hibernate.annotations.EmbeddableInstantiator;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Embedded;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
//tag::embeddable-instantiator-property[]
@Entity(name = "Person")
@Table(name = "persons")
public class Person {
@Id
public Integer id;
@Embedded
@EmbeddableInstantiator( NameInstantiator.class )
public Name name;
@ElementCollection
@Embedded
@EmbeddableInstantiator( NameInstantiator.class )
public Set<Name> aliases;
//end::embeddable-instantiator-property[]
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-property[]
}
//end::embeddable-instantiator-property[]

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.embedded;