diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/inheritance/embeddable-inheritance-create-table-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/inheritance/embeddable-inheritance-create-table-example.sql new file mode 100644 index 0000000000..6c4f9eb160 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/inheritance/embeddable-inheritance-create-table-example.sql @@ -0,0 +1,8 @@ +create table TestEntity ( + id bigint not null, + embeddable_type varchar(31) not null, + parentProp varchar(255), + childOneProp integer, + subChildOneProp float(53), + primary key (id) +) \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/inheritance.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/inheritance.adoc index 08b4d36956..af1bf410bc 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/inheritance.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/inheritance.adoc @@ -376,3 +376,57 @@ include::{example-dir-inheritance}/polymorphism/ExplicitPolymorphismTest.java[ta Therefore, only the `Book` was fetched since the `Blog` entity was marked with the `@Polymorphism(type = PolymorphismType.EXPLICIT)` annotation, which instructs Hibernate to skip it when executing a polymorphic query against a non-mapped base class. + +[[embeddable-inheritance]] +==== Embeddable inheritance + +Hibernate also supports discriminator-based inheritance for *embeddable types*. This works similarly to +<>: an `@Embeddable` class may be +extended by other `@Embeddable` classes, in which case the `@Embedded` properties using that type will +rely on an additional discriminator column to store information about the composite value's subtype. + +When retrieving the inherited property, Hibernate will read the discriminator value and instantiate the +correct `@Embeddable` subtype with its corresponding properties. + +By default, the discriminator column will be `STRING` typed and named like `_DTYPE`, +where `property_name` is the name of the `@Embedded` property in the respective entity mapping. +It's possible to customize the discriminator column mapping: + +* For the whole `@Embeddable` type, by using `@DiscriminatorColumn` or `@DiscriminatorFormula` on the *root* class of the inheritance hierarchy +(NOTE: if using the same inheritance-enabled embeddable type for two different properties in the same entity mapping, +this will cause a column name conflict); +* For a specific `@Embedded` property, by using the `@AttributeOverride` annotation with the special name `{discriminator}`. + +Finally, to specify custom discriminator values for each subtype one can annotate the inheritance hierarchy's +classes with `@DiscriminatorValue`. + +[IMPORTANT] +==== +Embeddable inheritance *IS* also supported for components used in an `@ElementCollection`. +Embeddable inheritance is *NOT* supported for `@EmbeddedId`, embeddable types used as `@IdClass` +and embedded properties using a custom `@CompositeType`. +==== + +[[embeddable-inheritance-example]] +.Example mapping of an embeddable inheritance hierarchy +[source,java] +---- +include::{example-dir-inheritance}/embeddable/ParentEmbeddable.java[tags=embeddable-inheritance-parent-example,indent=0] +---- +[source,java] +---- +include::{example-dir-inheritance}/embeddable/ChildOneEmbeddable.java[tags=embeddable-inheritance-child-one-example,indent=0] +---- +[source,java] +---- +include::{example-dir-inheritance}/embeddable/SubChildOneEmbeddable.java[tags=embeddable-inheritance-sub-child-one-example,indent=0] +---- +[source,java] +---- +include::{example-dir-inheritance}/embeddable/BasicEmbeddableInheritanceTest.java[tags=embeddable-inheritance-entity-example,indent=0] +---- +This is the resulting table structure: +[source,sql] +---- +include::{extrasdir}/embeddable-inheritance-create-table-example.sql[] +---- \ No newline at end of file diff --git a/hibernate-core/src/main/java/org/hibernate/action/internal/AbstractEntityInsertAction.java b/hibernate-core/src/main/java/org/hibernate/action/internal/AbstractEntityInsertAction.java index 68198c6b7b..c239d59c59 100644 --- a/hibernate-core/src/main/java/org/hibernate/action/internal/AbstractEntityInsertAction.java +++ b/hibernate-core/src/main/java/org/hibernate/action/internal/AbstractEntityInsertAction.java @@ -20,6 +20,7 @@ import org.hibernate.engine.spi.Status; import org.hibernate.event.spi.EventSource; import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.metamodel.mapping.AttributeMappingsList; +import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.mapping.NaturalIdMapping; import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping; @@ -176,22 +177,25 @@ public abstract class AbstractEntityInsertAction extends EntityAction { Object object, PersistenceContext persistenceContext) { if ( object != null ) { - final AttributeMappingsList attributeMappings = attributeMapping.getEmbeddableTypeDescriptor().getAttributeMappings(); + final EmbeddableMappingType descriptor = attributeMapping.getEmbeddableTypeDescriptor(); + final AttributeMappingsList attributeMappings = descriptor.getAttributeMappings(); for ( int i = 0; i < attributeMappings.size(); i++ ) { final AttributeMapping attribute = attributeMappings.get( i ); - if ( attribute.isPluralAttributeMapping() ) { - addCollectionKey( - attribute.asPluralAttributeMapping(), - attribute.getPropertyAccess().getGetter().get( object ), - persistenceContext - ); - } - else if ( attribute.isEmbeddedAttributeMapping() ) { - visitEmbeddedAttributeMapping( - attribute.asEmbeddedAttributeMapping(), - attribute.getPropertyAccess().getGetter().get( object ), - persistenceContext - ); + if ( descriptor.declaresAttribute( object.getClass().getName(), attributeMapping ) ) { + if ( attribute.isPluralAttributeMapping() ) { + addCollectionKey( + attribute.asPluralAttributeMapping(), + attribute.getPropertyAccess().getGetter().get( object ), + persistenceContext + ); + } + else if ( attribute.isEmbeddedAttributeMapping() ) { + visitEmbeddedAttributeMapping( + attribute.asEmbeddedAttributeMapping(), + attribute.getPropertyAccess().getGetter().get( object ), + persistenceContext + ); + } } } } 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 d27c1e2fa8..fb6fb6072c 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 @@ -135,6 +135,7 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector, private final Map entityBindingMap = new HashMap<>(); private final List composites = new ArrayList<>(); private final Map, Component> genericComponentsMap = new HashMap<>(); + private final Map> embeddableSubtypes = new HashMap<>(); private final Map collectionBindingMap = new HashMap<>(); private final Map filterDefinitionMap = new HashMap<>(); @@ -284,6 +285,17 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector, return genericComponentsMap.get( componentClass ); } + @Override + public void registerEmbeddableSubclass(XClass superclass, XClass subclass) { + embeddableSubtypes.computeIfAbsent( superclass, c -> new ArrayList<>() ).add( subclass ); + } + + @Override + public List getEmbeddableSubclasses(XClass superclass) { + final List subclasses = embeddableSubtypes.get( superclass ); + return subclasses != null ? subclasses : List.of(); + } + @Override public SessionFactoryBuilder getSessionFactoryBuilder() { throw new UnsupportedOperationException( diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AggregateComponentSecondPass.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AggregateComponentSecondPass.java index 158a8a33f3..f85646d6c3 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AggregateComponentSecondPass.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AggregateComponentSecondPass.java @@ -65,6 +65,9 @@ public class AggregateComponentSecondPass implements SecondPass { final Dialect dialect = database.getDialect(); final AggregateSupport aggregateSupport = dialect.getAggregateSupport(); + // Sort the component properties early to ensure the aggregated + // columns respect the same order as the component's properties + final int[] originalOrder = component.sortProperties(); // Compute aggregated columns since we have to replace them in the table with the aggregate column final List aggregatedColumns = component.getAggregatedColumns(); final AggregateColumn aggregateColumn = component.getAggregateColumn(); @@ -97,7 +100,7 @@ public class AggregateComponentSecondPass implements SecondPass { ); if ( registeredUdt == udt ) { addAuxiliaryObjects = true; - orderColumns( registeredUdt ); + orderColumns( registeredUdt, originalOrder ); } else { addAuxiliaryObjects = false; @@ -184,9 +187,8 @@ public class AggregateComponentSecondPass implements SecondPass { propertyHolder.getTable().getColumns().removeAll( aggregatedColumns ); } - private void orderColumns(UserDefinedObjectType userDefinedType) { + private void orderColumns(UserDefinedObjectType userDefinedType, int[] originalOrder) { final Class componentClass = component.getComponentClass(); - final int[] originalOrder = component.sortProperties(); final String[] structColumnNames = component.getStructColumnNames(); if ( structColumnNames == null || structColumnNames.length == 0 ) { final int[] propertyMappingIndex; @@ -211,23 +213,27 @@ public class AggregateComponentSecondPass implements SecondPass { else { propertyMappingIndex = null; } + final ArrayList orderedColumns = new ArrayList<>( userDefinedType.getColumnSpan() ); if ( propertyMappingIndex == null ) { // If there is default ordering possible, assume alphabetical ordering - final ArrayList orderedColumns = new ArrayList<>( userDefinedType.getColumnSpan() ); final List properties = component.getProperties(); for ( Property property : properties ) { addColumns( orderedColumns, property.getValue() ); } - userDefinedType.reorderColumns( orderedColumns ); + if ( component.isPolymorphic() ) { + addColumns( orderedColumns, component.getDiscriminator() ); + } } else { - final ArrayList orderedColumns = new ArrayList<>( userDefinedType.getColumnSpan() ); final List properties = component.getProperties(); for ( final int propertyIndex : propertyMappingIndex ) { addColumns( orderedColumns, properties.get( propertyIndex ).getValue() ); } - userDefinedType.reorderColumns( orderedColumns ); } + final List reorderedColumn = context.getBuildingOptions() + .getColumnOrderingStrategy() + .orderUserDefinedTypeColumns( userDefinedType, context.getMetadataCollector() ); + userDefinedType.reorderColumns( reorderedColumn != null ? reorderedColumn : orderedColumns ); } else { final ArrayList orderedColumns = new ArrayList<>( userDefinedType.getColumnSpan() ); @@ -281,6 +287,13 @@ public class AggregateComponentSecondPass implements SecondPass { } } } + if ( component.isPolymorphic() ) { + final Column column = component.getDiscriminator().getColumns().get( 0 ); + if ( structColumnName.equals( column.getName() ) ) { + orderedColumns.add( column ); + return true; + } + } return false; } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedDiscriminatorColumn.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedDiscriminatorColumn.java index 2725f4273e..69cd7260ce 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedDiscriminatorColumn.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedDiscriminatorColumn.java @@ -10,6 +10,8 @@ import org.hibernate.AssertionFailure; import org.hibernate.annotations.DiscriminatorFormula; import org.hibernate.boot.spi.MetadataBuildingContext; +import jakarta.persistence.AttributeOverride; +import jakarta.persistence.Column; import jakarta.persistence.DiscriminatorColumn; import jakarta.persistence.DiscriminatorType; @@ -25,10 +27,10 @@ public class AnnotatedDiscriminatorColumn extends AnnotatedColumn { private String discriminatorTypeName; - public AnnotatedDiscriminatorColumn() { + public AnnotatedDiscriminatorColumn(String defaultColumnName) { //discriminator default value super(); - setLogicalColumnName( DEFAULT_DISCRIMINATOR_COLUMN_NAME ); + setLogicalColumnName( defaultColumnName ); setNullable( false ); setDiscriminatorTypeName( DEFAULT_DISCRIMINATOR_TYPE ); setLength( DEFAULT_DISCRIMINATOR_LENGTH ); @@ -45,10 +47,12 @@ public class AnnotatedDiscriminatorColumn extends AnnotatedColumn { public static AnnotatedDiscriminatorColumn buildDiscriminatorColumn( DiscriminatorColumn discriminatorColumn, DiscriminatorFormula discriminatorFormula, + Column columnOverride, + String defaultColumnName, MetadataBuildingContext context) { final AnnotatedColumns parent = new AnnotatedColumns(); parent.setBuildingContext( context ); - final AnnotatedDiscriminatorColumn column = new AnnotatedDiscriminatorColumn(); + final AnnotatedDiscriminatorColumn column = new AnnotatedDiscriminatorColumn( defaultColumnName ); final DiscriminatorType discriminatorType; if ( discriminatorFormula != null ) { final DiscriminatorType type = discriminatorFormula.discriminatorType(); @@ -76,7 +80,13 @@ public class AnnotatedDiscriminatorColumn extends AnnotatedColumn { discriminatorType = DiscriminatorType.STRING; column.setImplicit( true ); } - setDiscriminatorType( discriminatorType, discriminatorColumn, column ); + if ( columnOverride != null ) { + column.setLogicalColumnName( columnOverride.name() ); + if ( !columnOverride.columnDefinition().isEmpty() ) { + column.setSqlType( columnOverride.columnDefinition() ); + } + } + setDiscriminatorType( discriminatorType, discriminatorColumn, columnOverride, column ); column.setParent( parent ); column.bind(); return column; @@ -85,6 +95,7 @@ public class AnnotatedDiscriminatorColumn extends AnnotatedColumn { private static void setDiscriminatorType( DiscriminatorType type, DiscriminatorColumn discriminatorColumn, + Column columnOverride, AnnotatedDiscriminatorColumn column) { if ( type == null ) { column.setDiscriminatorTypeName( "string" ); @@ -102,7 +113,10 @@ public class AnnotatedDiscriminatorColumn extends AnnotatedColumn { break; case STRING: column.setDiscriminatorTypeName( "string" ); - if ( discriminatorColumn != null ) { + if ( columnOverride != null ) { + column.setLength( (long) columnOverride.length() ); + } + else if ( discriminatorColumn != null ) { column.setLength( (long) discriminatorColumn.length() ); } break; diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotationBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotationBinder.java index 84b10e7c95..dfa3ca6a34 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotationBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotationBinder.java @@ -67,6 +67,7 @@ import jakarta.persistence.Table; import jakarta.persistence.TableGenerator; import jakarta.persistence.TableGenerators; +import static org.hibernate.boot.model.internal.AnnotatedClassType.EMBEDDABLE; import static org.hibernate.boot.model.internal.AnnotatedClassType.ENTITY; import static org.hibernate.boot.model.internal.FilterDefBinder.bindFilterDefs; import static org.hibernate.boot.model.internal.GeneratorBinder.buildGenerators; @@ -697,7 +698,15 @@ public final class AnnotationBinder { superclassState.setHasSiblings( true ); final InheritanceState superEntityState = getInheritanceStateOfSuperEntity( clazz, inheritanceStatePerClass ); - state.setHasParents( superEntityState != null ); + if ( superEntityState != null ) { + state.setHasParents( true ); + if ( buildingContext.getMetadataCollector().getClassType( clazz ) == EMBEDDABLE ) { + buildingContext.getMetadataCollector().registerEmbeddableSubclass( + superEntityState.getClazz(), + clazz + ); + } + } logMixedInheritance( clazz, superclassState, state ); if ( superclassState.getType() != null ) { state.setType( superclassState.getType() ); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ComponentPropertyHolder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ComponentPropertyHolder.java index 8917b17282..b493faf796 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ComponentPropertyHolder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ComponentPropertyHolder.java @@ -308,7 +308,7 @@ public class ComponentPropertyHolder extends AbstractPropertyHolder { @Override public void addProperty(Property prop, XClass declaringClass) { - component.addProperty( prop ); + component.addProperty( prop, declaringClass ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EmbeddableBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EmbeddableBinder.java index 0e82e2833a..1463ec488a 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EmbeddableBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EmbeddableBinder.java @@ -13,8 +13,10 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.TreeMap; import org.hibernate.AnnotationException; +import org.hibernate.annotations.DiscriminatorFormula; import org.hibernate.annotations.Instantiator; import org.hibernate.annotations.TypeBinderType; import org.hibernate.annotations.common.reflection.XAnnotatedElement; @@ -26,20 +28,25 @@ import org.hibernate.boot.spi.AccessType; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.PropertyData; import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.mapping.BasicValue; import org.hibernate.mapping.Component; import org.hibernate.mapping.Property; import org.hibernate.mapping.SimpleValue; import org.hibernate.mapping.SingleTableSubclass; +import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping; import org.hibernate.metamodel.spi.EmbeddableInstantiator; import org.hibernate.property.access.internal.PropertyAccessStrategyCompositeUserTypeImpl; import org.hibernate.property.access.internal.PropertyAccessStrategyMixedImpl; import org.hibernate.property.access.spi.PropertyAccessStrategy; import org.hibernate.resource.beans.internal.FallbackBeanInstanceProducer; import org.hibernate.resource.beans.spi.ManagedBeanRegistry; +import org.hibernate.type.BasicType; import org.hibernate.usertype.CompositeUserType; import jakarta.persistence.Column; import jakarta.persistence.Convert; +import jakarta.persistence.DiscriminatorColumn; +import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Embeddable; import jakarta.persistence.Embedded; import jakarta.persistence.EmbeddedId; @@ -51,6 +58,9 @@ import jakarta.persistence.MappedSuperclass; import jakarta.persistence.OneToMany; import jakarta.persistence.OneToOne; +import static org.hibernate.boot.model.internal.AnnotatedDiscriminatorColumn.DEFAULT_DISCRIMINATOR_COLUMN_NAME; +import static org.hibernate.boot.model.internal.AnnotatedDiscriminatorColumn.buildDiscriminatorColumn; +import static org.hibernate.boot.model.internal.BinderHelper.getOverridableAnnotation; import static org.hibernate.boot.model.internal.BinderHelper.getPath; import static org.hibernate.boot.model.internal.BinderHelper.getPropertyOverriddenByMapperOrMapsId; import static org.hibernate.boot.model.internal.BinderHelper.getRelativePath; @@ -64,6 +74,9 @@ import static org.hibernate.boot.model.internal.PropertyBinder.addElementsOfClas import static org.hibernate.boot.model.internal.PropertyBinder.processElementAnnotations; import static org.hibernate.boot.model.internal.PropertyHolderBuilder.buildPropertyHolder; import static org.hibernate.internal.CoreLogging.messageLogger; +import static org.hibernate.internal.util.StringHelper.isEmpty; +import static org.hibernate.internal.util.StringHelper.qualify; +import static org.hibernate.internal.util.StringHelper.unqualify; import static org.hibernate.mapping.SimpleValue.DEFAULT_ID_GEN_STRATEGY; /** @@ -362,6 +375,36 @@ public class EmbeddableBinder { final XClass annotatedClass = inferredData.getPropertyClass(); final List classElements = collectClassElements( propertyAccessor, context, returnedClassOrElement, annotatedClass, isIdClass ); + // Main entry point for binding embeddable inheritance + bindDiscriminator( + component, + returnedClassOrElement, + propertyHolder, + subholder, + inferredData, + inheritanceStatePerClass, + context + ); + if ( component.isPolymorphic() ) { + validateInheritanceIsSupported( subholder, compositeUserType ); + final BasicType discriminatorType = (BasicType) component.getDiscriminator().getType(); + // Discriminator values are used to construct the embeddable domain + // type hierarchy so order of processing is important + final Map discriminatorValues = new TreeMap<>(); + final Map subclassToSuperclass = new HashMap<>(); + collectDiscriminatorValue( returnedClassOrElement, discriminatorType, discriminatorValues ); + collectSubclassElements( + propertyAccessor, + context, + returnedClassOrElement, + classElements, + discriminatorType, + discriminatorValues, + subclassToSuperclass + ); + component.setDiscriminatorValues( discriminatorValues ); + component.setSubclassToSuperclass( subclassToSuperclass ); + } final List baseClassElements = collectBaseClassElements( baseInferredData, propertyAccessor, context, annotatedClass ); if ( baseClassElements != null @@ -403,6 +446,7 @@ public class EmbeddableBinder { if ( compositeUserType != null ) { processCompositeUserType( component, compositeUserType ); } + AggregateComponentBinder.processAggregate( component, propertyHolder, @@ -428,6 +472,125 @@ public class EmbeddableBinder { .getBeanInstance(); } + private static void bindDiscriminator( + Component component, + XClass componentClass, + PropertyHolder parentHolder, + PropertyHolder holder, + PropertyData propertyData, + Map inheritanceStatePerClass, + MetadataBuildingContext context) { + final InheritanceState inheritanceState = inheritanceStatePerClass.get( componentClass ); + if ( inheritanceState == null ) { + return; + } + + final AnnotatedDiscriminatorColumn discriminatorColumn = processEmbeddableDiscriminatorProperties( + componentClass, + propertyData, + parentHolder, + holder, + inheritanceState, + context + ); + if ( discriminatorColumn != null ) { + bindDiscriminatorColumnToComponent( component, discriminatorColumn, holder, context ); + } + } + + private static AnnotatedDiscriminatorColumn processEmbeddableDiscriminatorProperties( + XClass annotatedClass, + PropertyData propertyData, + PropertyHolder parentHolder, + PropertyHolder holder, + InheritanceState inheritanceState, + MetadataBuildingContext context) { + final DiscriminatorColumn discriminatorColumn = annotatedClass.getAnnotation( DiscriminatorColumn.class ); + final DiscriminatorFormula discriminatorFormula = getOverridableAnnotation( + annotatedClass, + DiscriminatorFormula.class, + context + ); + if ( !inheritanceState.hasParents() ) { + if ( inheritanceState.hasSiblings() ) { + final String path = qualify( holder.getPath(), EntityDiscriminatorMapping.DISCRIMINATOR_ROLE_NAME ); + final String columnPrefix; + final Column[] overrides; + if ( holder.isWithinElementCollection() ) { + columnPrefix = unqualify( parentHolder.getPath() ); + overrides = parentHolder.getOverriddenColumn( path ); + } + else { + columnPrefix = propertyData.getPropertyName(); + overrides = holder.getOverriddenColumn( path ); + } + return buildDiscriminatorColumn( + discriminatorColumn, + discriminatorFormula, + overrides == null ? null : overrides[0], + columnPrefix + DEFAULT_DISCRIMINATOR_COLUMN_NAME, + context + ); + } + } + else { + // not a root entity + if ( discriminatorColumn != null ) { + throw new AnnotationException( String.format( + "Embeddable class '%s' is annotated '@DiscriminatorColumn' but it is not the root of the inheritance hierarchy", + annotatedClass.getName() + ) ); + } + if ( discriminatorFormula != null ) { + throw new AnnotationException( String.format( + "Embeddable class '%s' is annotated '@DiscriminatorFormula' but it is not the root of the inheritance hierarchy", + annotatedClass.getName() + ) ); + } + } + return null; + } + + private static void bindDiscriminatorColumnToComponent( + Component component, + AnnotatedDiscriminatorColumn discriminatorColumn, + PropertyHolder holder, + MetadataBuildingContext context) { + assert component.getDiscriminator() == null; + LOG.tracev( "Setting discriminator for embeddable {0}", component.getComponentClassName() ); + final AnnotatedColumns columns = new AnnotatedColumns(); + columns.setPropertyHolder( holder ); + columns.setBuildingContext( context ); + discriminatorColumn.setParent( columns ); + final BasicValue discriminatorColumnBinding = new BasicValue( context, component.getTable() ); + component.setDiscriminator( discriminatorColumnBinding ); + discriminatorColumn.linkWithValue( discriminatorColumnBinding ); + discriminatorColumnBinding.setTypeName( discriminatorColumn.getDiscriminatorTypeName() ); + } + + private static void validateInheritanceIsSupported( + PropertyHolder holder, + CompositeUserType compositeUserType) { + if ( holder.isOrWithinEmbeddedId() ) { + throw new AnnotationException( String.format( + "Embeddable class '%s' defines an inheritance hierarchy and cannot be used in an '@EmbeddedId'", + holder.getClassName() + ) ); + } + else if ( holder.isInIdClass() ) { + throw new AnnotationException( String.format( + "Embeddable class '%s' defines an inheritance hierarchy and cannot be used in an '@IdClass'", + holder.getClassName() + ) ); + } + else if ( compositeUserType != null ) { + throw new AnnotationException( String.format( + "Embeddable class '%s' defines an inheritance hierarchy and cannot be used with a custom '@CompositeType'", + holder.getClassName() + ) ); + } + } + private static List collectClassElements( AccessType propertyAccessor, MetadataBuildingContext context, @@ -451,6 +614,75 @@ public class EmbeddableBinder { return classElements; } + private static void collectSubclassElements( + AccessType propertyAccessor, + MetadataBuildingContext context, + XClass superclass, + List classElements, + BasicType discriminatorType, + Map discriminatorValues, + Map subclassToSuperclass) { + for ( final XClass subclass : context.getMetadataCollector().getEmbeddableSubclasses( superclass ) ) { + // collect the discriminator value details + final String old = collectDiscriminatorValue( subclass, discriminatorType, discriminatorValues ); + if ( old != null ) { + throw new AnnotationException( String.format( + "Embeddable subclass '%s' defines the same discriminator value as '%s", + subclass.getName(), + old + ) ); + } + final String put = subclassToSuperclass.put( subclass.getName().intern(), superclass.getName().intern() ); + assert put == null; + // collect property of subclass + final PropertyContainer superContainer = new PropertyContainer( subclass, superclass, propertyAccessor ); + addElementsOfClass( classElements, superContainer, context ); + // recursively do that same for all subclasses + collectSubclassElements( + propertyAccessor, + context, + subclass, + classElements, + discriminatorType, + discriminatorValues, + subclassToSuperclass + ); + } + } + + private static String collectDiscriminatorValue( + XClass annotatedClass, + BasicType discriminatorType, + Map discriminatorValues) { + final String explicitValue = annotatedClass.isAnnotationPresent( DiscriminatorValue.class ) + ? annotatedClass.getAnnotation( DiscriminatorValue.class ).value() + : null; + final String discriminatorValue; + if ( isEmpty( explicitValue ) ) { + final String name = unqualify( annotatedClass.getName() ); + if ( "character".equals( discriminatorType.getName() ) ) { + throw new AnnotationException( String.format( + "Embeddable '%s' has a discriminator of character type and must specify its '@DiscriminatorValue'", + name + ) ); + } + else if ( "integer".equals( discriminatorType.getName() ) ) { + discriminatorValue = String.valueOf( name.hashCode() ); + } + else { + discriminatorValue = name; + } + } + else { + discriminatorValue = explicitValue; + } + return discriminatorValues.put( + discriminatorType.getJavaTypeDescriptor().fromString( discriminatorValue ), + annotatedClass.getName().intern() + ); + } + + private static boolean isValidSuperclass(XClass superClass, boolean isIdClass) { if ( superClass == null ) { return false; diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EntityBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EntityBinder.java index 4a11f4af16..d9b0ffac32 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EntityBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EntityBinder.java @@ -139,6 +139,7 @@ import static jakarta.persistence.InheritanceType.SINGLE_TABLE; import static org.hibernate.annotations.PolymorphismType.EXPLICIT; import static org.hibernate.annotations.PolymorphismType.IMPLICIT; import static org.hibernate.boot.model.internal.AnnotatedClassType.MAPPED_SUPERCLASS; +import static org.hibernate.boot.model.internal.AnnotatedDiscriminatorColumn.DEFAULT_DISCRIMINATOR_COLUMN_NAME; import static org.hibernate.boot.model.internal.AnnotatedDiscriminatorColumn.buildDiscriminatorColumn; import static org.hibernate.boot.model.internal.AnnotatedJoinColumn.buildInheritanceJoinColumn; import static org.hibernate.boot.model.internal.BinderHelper.getMappedSuperclassOrNull; @@ -976,7 +977,13 @@ public class EntityBinder { getOverridableAnnotation( annotatedClass, DiscriminatorFormula.class, context ); if ( !inheritanceState.hasParents() || annotatedClass.isAnnotationPresent( Inheritance.class ) ) { - return buildDiscriminatorColumn( discriminatorColumn, discriminatorFormula, context ); + return buildDiscriminatorColumn( + discriminatorColumn, + discriminatorFormula, + null, + DEFAULT_DISCRIMINATOR_COLUMN_NAME, + context + ); } else { // not a root entity @@ -1006,7 +1013,7 @@ public class EntityBinder { final DiscriminatorColumn discriminatorColumn = annotatedClass.getAnnotation( DiscriminatorColumn.class ); if ( !inheritanceState.hasParents() || annotatedClass.isAnnotationPresent( Inheritance.class ) ) { return useDiscriminatorColumnForJoined( discriminatorColumn ) - ? buildDiscriminatorColumn( discriminatorColumn, null, context ) + ? buildDiscriminatorColumn( discriminatorColumn, null, null, DEFAULT_DISCRIMINATOR_COLUMN_NAME, context ) : null; } else { 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 2162febe5d..8f0d81569f 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 @@ -7,6 +7,7 @@ package org.hibernate.boot.spi; import java.io.Serializable; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.function.Function; @@ -83,6 +84,10 @@ public interface InFlightMetadataCollector extends MetadataImplementor { void registerGenericComponent(Component component); + void registerEmbeddableSubclass(XClass superclass, XClass subclass); + + List getEmbeddableSubclasses(XClass superclass); + /** * Adds an import (for use in HQL). * diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/AbstractPostgreSQLStructJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/AbstractPostgreSQLStructJdbcType.java index 3c7c356c55..5d9e1eceab 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/AbstractPostgreSQLStructJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/AbstractPostgreSQLStructJdbcType.java @@ -24,12 +24,12 @@ import java.util.ArrayList; import java.util.TimeZone; import org.hibernate.internal.util.CharSequenceHelper; -import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.metamodel.mapping.BasicValuedMapping; import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.MappingType; import org.hibernate.metamodel.mapping.SelectableMapping; +import org.hibernate.metamodel.mapping.ValuedModelPart; import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping; import org.hibernate.sql.ast.spi.SqlAppender; import org.hibernate.sql.ast.spi.StringBuilderSqlAppender; @@ -45,6 +45,8 @@ import org.hibernate.type.descriptor.jdbc.BasicExtractor; import org.hibernate.type.descriptor.jdbc.StructJdbcType; import org.hibernate.type.spi.TypeConfiguration; +import static org.hibernate.dialect.StructHelper.getEmbeddedPart; +import static org.hibernate.dialect.StructHelper.instantiate; import static org.hibernate.type.descriptor.DateTimeUtils.appendAsDate; import static org.hibernate.type.descriptor.DateTimeUtils.appendAsLocalTime; import static org.hibernate.type.descriptor.DateTimeUtils.appendAsTime; @@ -184,17 +186,14 @@ public abstract class AbstractPostgreSQLStructJdbcType implements StructJdbcType array = values.toArray(); } else { - array = new Object[embeddableMappingType.getJdbcValueCount()]; + array = new Object[embeddableMappingType.getJdbcValueCount() + ( embeddableMappingType.isPolymorphic() ? 1 : 0 )]; end = deserializeStruct( string, 0, 0, array, returnEmbeddable, options ); } assert end == string.length(); if ( returnEmbeddable ) { - final Object[] attributeValues = getAttributeValues( embeddableMappingType, orderMapping, array, options ); + final StructAttributeValues attributeValues = getAttributeValues( embeddableMappingType, orderMapping, array, options ); //noinspection unchecked - return (X) embeddableMappingType.getRepresentationStrategy().getInstantiator().instantiate( - () -> attributeValues, - options.getSessionFactory() - ); + return (X) instantiate( embeddableMappingType, attributeValues, options.getSessionFactory() ); } else if ( inverseOrderMapping != null ) { StructHelper.orderJdbcValues( embeddableMappingType, inverseOrderMapping, array.clone(), array ); @@ -467,15 +466,13 @@ public abstract class AbstractPostgreSQLStructJdbcType implements StructJdbcType options ); if ( returnEmbeddable ) { - final Object[] attributeValues = structJdbcType.getAttributeValues( + final StructAttributeValues attributeValues = structJdbcType.getAttributeValues( structJdbcType.embeddableMappingType, structJdbcType.orderMapping, subValues, options ); - final Object subValue = structJdbcType.embeddableMappingType.getRepresentationStrategy() - .getInstantiator() - .instantiate( () -> attributeValues, options.getSessionFactory() ); + final Object subValue = instantiate( structJdbcType.embeddableMappingType, attributeValues, options.getSessionFactory() ); values[column] = subValue; } else { @@ -869,15 +866,13 @@ public abstract class AbstractPostgreSQLStructJdbcType implements StructJdbcType options ); if ( returnEmbeddable ) { - final Object[] attributeValues = structJdbcType.getAttributeValues( + final StructAttributeValues attributeValues = structJdbcType.getAttributeValues( structJdbcType.embeddableMappingType, structJdbcType.orderMapping, subValues, options ); - final Object subValue = structJdbcType.embeddableMappingType.getRepresentationStrategy() - .getInstantiator() - .instantiate( () -> attributeValues, options.getSessionFactory() ); + final Object subValue = instantiate( structJdbcType.embeddableMappingType, attributeValues, options.getSessionFactory() ); values.add( subValue ); } else { @@ -1010,10 +1005,11 @@ public abstract class AbstractPostgreSQLStructJdbcType implements StructJdbcType private SelectableMapping getJdbcValueSelectable(int jdbcValueSelectableIndex) { if ( orderMapping != null ) { final int numberOfAttributeMappings = embeddableMappingType.getNumberOfAttributeMappings(); + final int size = numberOfAttributeMappings + ( embeddableMappingType.isPolymorphic() ? 1 : 0 ); int count = 0; - for ( int i = 0; i < numberOfAttributeMappings; i++ ) { - final AttributeMapping attributeMapping = embeddableMappingType.getAttributeMapping( orderMapping[i] ); - final MappingType mappedType = attributeMapping.getMappedType(); + for ( int i = 0; i < size; i++ ) { + final ValuedModelPart modelPart = getEmbeddedPart( embeddableMappingType, numberOfAttributeMappings, orderMapping[i] ); + final MappingType mappedType = modelPart.getMappedType(); if ( mappedType instanceof EmbeddableMappingType ) { final EmbeddableMappingType embeddableMappingType = (EmbeddableMappingType) mappedType; final SelectableMapping aggregateMapping = embeddableMappingType.getAggregateMapping(); @@ -1033,9 +1029,9 @@ public abstract class AbstractPostgreSQLStructJdbcType implements StructJdbcType } else { if ( count == jdbcValueSelectableIndex ) { - return (SelectableMapping) attributeMapping; + return (SelectableMapping) modelPart; } - count += attributeMapping.getJdbcTypeCount(); + count += modelPart.getJdbcTypeCount(); } } return null; @@ -1192,8 +1188,7 @@ public abstract class AbstractPostgreSQLStructJdbcType implements StructJdbcType } private void serializeStructTo(PostgreSQLAppender appender, Object value, WrapperOptions options) { - final Object[] array = embeddableMappingType.getValues( value ); - serializeValuesTo( appender, options, embeddableMappingType, array, '(' ); + serializeValuesTo( appender, options, embeddableMappingType, value, '(' ); appender.append( ')' ); } @@ -1201,19 +1196,20 @@ public abstract class AbstractPostgreSQLStructJdbcType implements StructJdbcType PostgreSQLAppender appender, WrapperOptions options, EmbeddableMappingType embeddableMappingType, - Object[] array, + Object domainValue, char separator) { - final int end = embeddableMappingType.getNumberOfAttributeMappings(); - for ( int i = 0; i < end; i++ ) { - final AttributeMapping attributeMapping; + final Object[] array = embeddableMappingType.getValues( domainValue ); + final int numberOfAttributes = embeddableMappingType.getNumberOfAttributeMappings(); + for ( int i = 0; i < array.length; i++ ) { + final ValuedModelPart attributeMapping; final Object attributeValue; if ( orderMapping == null ) { - attributeMapping = embeddableMappingType.getAttributeMapping( i ); - attributeValue = array == null ? null : array[i]; + attributeMapping = getEmbeddedPart( embeddableMappingType, numberOfAttributes, i ); + attributeValue = array[i]; } else { - attributeMapping = embeddableMappingType.getAttributeMapping( orderMapping[i] ); - attributeValue = array == null ? null : array[orderMapping[i]]; + attributeMapping = getEmbeddedPart( embeddableMappingType, numberOfAttributes, orderMapping[i] ); + attributeValue = array[orderMapping[i]]; } if ( attributeMapping instanceof BasicValuedMapping ) { appender.append( separator ); @@ -1232,7 +1228,7 @@ public abstract class AbstractPostgreSQLStructJdbcType implements StructJdbcType appender, options, mappingType, - attributeValue == null ? null : mappingType.getValues( attributeValue ), + attributeValue, separator ); separator = ','; @@ -1398,9 +1394,8 @@ public abstract class AbstractPostgreSQLStructJdbcType implements StructJdbcType if ( subValue != null ) { final AbstractPostgreSQLStructJdbcType structJdbcType = (AbstractPostgreSQLStructJdbcType) jdbcMapping.getJdbcType(); final EmbeddableMappingType subEmbeddableMappingType = structJdbcType.getEmbeddableMappingType(); - final Object[] array = subEmbeddableMappingType.getValues( subValue ); appender.quoteStart(); - structJdbcType.serializeValuesTo( appender, options, subEmbeddableMappingType, array, '(' ); + structJdbcType.serializeValuesTo( appender, options, subEmbeddableMappingType, subValue, '(' ); appender.append( ')' ); appender.quoteEnd(); } @@ -1410,21 +1405,21 @@ public abstract class AbstractPostgreSQLStructJdbcType implements StructJdbcType } } - private Object[] getAttributeValues( + private StructAttributeValues getAttributeValues( EmbeddableMappingType embeddableMappingType, int[] orderMapping, Object[] rawJdbcValues, WrapperOptions options) throws SQLException { final int numberOfAttributeMappings = embeddableMappingType.getNumberOfAttributeMappings(); - final Object[] attributeValues; - if ( numberOfAttributeMappings != rawJdbcValues.length || orderMapping != null ) { - attributeValues = new Object[numberOfAttributeMappings]; - } - else { - attributeValues = rawJdbcValues; - } + final int size = numberOfAttributeMappings + ( embeddableMappingType.isPolymorphic() ? 1 : 0 ); + final StructAttributeValues attributeValues = new StructAttributeValues( + numberOfAttributeMappings, + orderMapping != null ? + null : + rawJdbcValues + ); int jdbcIndex = 0; - for ( int i = 0; i < numberOfAttributeMappings; i++ ) { + for ( int i = 0; i < size; i++ ) { final int attributeIndex; if ( orderMapping == null ) { attributeIndex = i; @@ -1432,9 +1427,9 @@ public abstract class AbstractPostgreSQLStructJdbcType implements StructJdbcType else { attributeIndex = orderMapping[i]; } - final AttributeMapping attributeMapping = embeddableMappingType.getAttributeMapping( attributeIndex ); + final ValuedModelPart modelPart = getEmbeddedPart( embeddableMappingType, numberOfAttributeMappings, attributeIndex ); jdbcIndex += injectAttributeValue( - attributeMapping, + modelPart, attributeValues, attributeIndex, rawJdbcValues, @@ -1446,45 +1441,46 @@ public abstract class AbstractPostgreSQLStructJdbcType implements StructJdbcType } private int injectAttributeValue( - AttributeMapping attributeMapping, - Object[] attributeValues, + ValuedModelPart modelPart, + StructAttributeValues attributeValues, int attributeIndex, Object[] rawJdbcValues, int jdbcIndex, WrapperOptions options) throws SQLException { - final MappingType mappedType = attributeMapping.getMappedType(); + final MappingType mappedType = modelPart.getMappedType(); final int jdbcValueCount; final Object rawJdbcValue = rawJdbcValues[jdbcIndex]; if ( mappedType instanceof EmbeddableMappingType ) { final EmbeddableMappingType embeddableMappingType = (EmbeddableMappingType) mappedType; if ( embeddableMappingType.getAggregateMapping() != null ) { jdbcValueCount = 1; - attributeValues[attributeIndex] = rawJdbcValue; + attributeValues.setAttributeValue( attributeIndex, rawJdbcValue ); } else { jdbcValueCount = embeddableMappingType.getJdbcValueCount(); final Object[] subJdbcValues = new Object[jdbcValueCount]; System.arraycopy( rawJdbcValues, jdbcIndex, subJdbcValues, 0, subJdbcValues.length ); - final Object[] subValues = getAttributeValues( embeddableMappingType, null, subJdbcValues, options ); - attributeValues[attributeIndex] = embeddableMappingType.getRepresentationStrategy() - .getInstantiator() - .instantiate( - () -> subValues, - embeddableMappingType.findContainingEntityMapping() - .getEntityPersister() - .getFactory() - ); + final StructAttributeValues subValues = getAttributeValues( + embeddableMappingType, + null, + subJdbcValues, + options + ); + attributeValues.setAttributeValue( + attributeIndex, + instantiate( embeddableMappingType, subValues, options.getSessionFactory() ) + ); } } else { - assert attributeMapping.getJdbcTypeCount() == 1; + assert modelPart.getJdbcTypeCount() == 1; jdbcValueCount = 1; - final JdbcMapping jdbcMapping = attributeMapping.getSingleJdbcMapping(); + final JdbcMapping jdbcMapping = modelPart.getSingleJdbcMapping(); final Object jdbcValue = jdbcMapping.getJdbcJavaType().wrap( rawJdbcValue, options ); - attributeValues[attributeIndex] = jdbcMapping.convertToDomainValue( jdbcValue ); + attributeValues.setAttributeValue( attributeIndex, jdbcMapping.convertToDomainValue( jdbcValue ) ); } return jdbcValueCount; } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/JsonHelper.java b/hibernate-core/src/main/java/org/hibernate/dialect/JsonHelper.java index 7371d60f62..840d309d8e 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/JsonHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/JsonHelper.java @@ -22,11 +22,11 @@ import java.util.Objects; import org.hibernate.Internal; import org.hibernate.internal.util.CharSequenceHelper; import org.hibernate.internal.util.collections.ArrayHelper; -import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.MappingType; import org.hibernate.metamodel.mapping.SelectableMapping; +import org.hibernate.metamodel.mapping.ValuedModelPart; import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping; import org.hibernate.sql.ast.spi.SqlAppender; import org.hibernate.type.BasicPluralType; @@ -41,6 +41,9 @@ import org.hibernate.type.descriptor.java.OffsetDateTimeJavaType; import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType; import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; +import static org.hibernate.dialect.StructHelper.getEmbeddedPart; +import static org.hibernate.dialect.StructHelper.instantiate; + /** * A Helper for serializing and deserializing JSON, based on an {@link org.hibernate.metamodel.mapping.EmbeddableMappingType}. */ @@ -57,8 +60,7 @@ public class JsonHelper { } private static void toString(EmbeddableMappingType embeddableMappingType, Object value, WrapperOptions options, JsonAppender appender) { - final Object[] values = embeddableMappingType.getValues( value ); - toString( embeddableMappingType, options, appender, values, '{' ); + toString( embeddableMappingType, options, appender, value, '{' ); appender.append( '}' ); } @@ -66,10 +68,12 @@ public class JsonHelper { EmbeddableMappingType embeddableMappingType, WrapperOptions options, JsonAppender appender, - Object[] values, + Object domainValue, char separator) { + final Object[] values = embeddableMappingType.getValues( domainValue ); + final int numberOfAttributes = embeddableMappingType.getNumberOfAttributeMappings(); for ( int i = 0; i < values.length; i++ ) { - final AttributeMapping attributeMapping = embeddableMappingType.getAttributeMapping( i ); + final ValuedModelPart attributeMapping = getEmbeddedPart( embeddableMappingType, numberOfAttributes, i ); if ( attributeMapping instanceof SelectableMapping ) { final String name = ( (SelectableMapping) attributeMapping ).getSelectableName(); appender.append( separator ); @@ -90,7 +94,7 @@ public class JsonHelper { mappingType, options, appender, - mappingType.getValues( values[i] ), + values[i], separator ); } @@ -295,20 +299,18 @@ public class JsonHelper { return null; } - final Object[] values = new Object[embeddableMappingType.getJdbcValueCount()]; + final int jdbcValueCount = embeddableMappingType.getJdbcValueCount(); + final Object[] values = new Object[jdbcValueCount + ( embeddableMappingType.isPolymorphic() ? 1 : 0 )]; final int end = fromString( embeddableMappingType, string, 0, string.length(), values, returnEmbeddable, options ); assert string.substring( end ).isBlank(); if ( returnEmbeddable ) { - final Object[] attributeValues = StructHelper.getAttributeValues( + final StructAttributeValues attributeValues = StructHelper.getAttributeValues( embeddableMappingType, values, options ); //noinspection unchecked - return (X) embeddableMappingType.getRepresentationStrategy().getInstantiator().instantiate( - () -> attributeValues, - options.getSessionFactory() - ); + return (X) instantiate( embeddableMappingType, attributeValues, options.getSessionFactory() ); } //noinspection unchecked return (X) values; @@ -440,17 +442,12 @@ public class JsonHelper { i = fromString( subMappingType, string, i, end, subValues, returnEmbeddable, options ) - 1; assert string.charAt( i ) == '}'; if ( returnEmbeddable ) { - final Object[] attributeValues = StructHelper.getAttributeValues( + final StructAttributeValues attributeValues = StructHelper.getAttributeValues( subMappingType, subValues, options ); - values[selectableIndex] = embeddableMappingType.getRepresentationStrategy() - .getInstantiator() - .instantiate( - () -> attributeValues, - options.getSessionFactory() - ); + values[selectableIndex] = instantiate( embeddableMappingType, attributeValues, options.getSessionFactory() ); } else { values[selectableIndex] = subValues; @@ -1113,13 +1110,13 @@ public class JsonHelper { options ); if ( returnEmbeddable ) { + final StructAttributeValues subAttributeValues = StructHelper.getAttributeValues( + aggregateJdbcType.getEmbeddableMappingType(), + subValues, + options + ); final EmbeddableMappingType embeddableMappingType = aggregateJdbcType.getEmbeddableMappingType(); - return embeddableMappingType.getRepresentationStrategy() - .getInstantiator() - .instantiate( - () -> subValues, - options.getSessionFactory() - ); + return instantiate( embeddableMappingType, subAttributeValues, options.getSessionFactory() ) ; } return subValues; } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/StructAttributeValues.java b/hibernate-core/src/main/java/org/hibernate/dialect/StructAttributeValues.java new file mode 100644 index 0000000000..3d98154ba1 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/StructAttributeValues.java @@ -0,0 +1,46 @@ +/* + * 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.dialect; + +import org.hibernate.metamodel.spi.ValueAccess; + +/** + * @author Marco Belladelli + */ +public class StructAttributeValues implements ValueAccess { + private final Object[] attributeValues; + private final int size; + private Object discriminator; + + public StructAttributeValues(int size, Object[] rawJdbcValues) { + this.size = size; + if ( rawJdbcValues == null || size != rawJdbcValues.length) { + attributeValues = new Object[size]; + } + else { + attributeValues = rawJdbcValues; + } + } + + @Override + public Object[] getValues() { + return attributeValues; + } + + public void setAttributeValue(int index, Object value) { + if ( index == size ) { + discriminator = value; + } + else { + attributeValues[index] = value; + } + } + + public Object getDiscriminator() { + return discriminator; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/StructHelper.java b/hibernate-core/src/main/java/org/hibernate/dialect/StructHelper.java index 00ab80177e..6407d72a08 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/StructHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/StructHelper.java @@ -13,10 +13,13 @@ import java.sql.NClob; import java.sql.SQLException; import org.hibernate.Internal; -import org.hibernate.metamodel.mapping.AttributeMapping; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.MappingType; +import org.hibernate.metamodel.mapping.ValuedModelPart; +import org.hibernate.metamodel.spi.EmbeddableInstantiator; +import org.hibernate.metamodel.spi.EmbeddableRepresentationStrategy; import org.hibernate.type.SqlTypes; import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.JavaType; @@ -27,67 +30,61 @@ import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; */ @Internal public class StructHelper { - - public static Object[] getAttributeValues( + public static StructAttributeValues getAttributeValues( EmbeddableMappingType embeddableMappingType, Object[] rawJdbcValues, WrapperOptions options) throws SQLException { final int numberOfAttributeMappings = embeddableMappingType.getNumberOfAttributeMappings(); - final Object[] attributeValues; - if ( numberOfAttributeMappings != rawJdbcValues.length ) { - attributeValues = new Object[numberOfAttributeMappings]; - } - else { - attributeValues = rawJdbcValues; - } + final int size = numberOfAttributeMappings + ( embeddableMappingType.isPolymorphic() ? 1 : 0 ); + final StructAttributeValues attributeValues = new StructAttributeValues( numberOfAttributeMappings, rawJdbcValues ); int jdbcIndex = 0; - for ( int i = 0; i < numberOfAttributeMappings; i++ ) { - final AttributeMapping attributeMapping = embeddableMappingType.getAttributeMapping( i ); - jdbcIndex += injectAttributeValue( attributeMapping, attributeValues, i, rawJdbcValues, jdbcIndex, options ); + for ( int i = 0; i < size; i++ ) { + final ValuedModelPart valuedModelPart = getEmbeddedPart( + embeddableMappingType, + numberOfAttributeMappings, + i + ); + jdbcIndex += injectAttributeValue( valuedModelPart, attributeValues, i, rawJdbcValues, jdbcIndex, options ); } return attributeValues; } private static int injectAttributeValue( - AttributeMapping attributeMapping, - Object[] attributeValues, + ValuedModelPart modelPart, + StructAttributeValues attributeValues, int attributeIndex, Object[] rawJdbcValues, int jdbcIndex, WrapperOptions options) throws SQLException { - final MappingType mappedType = attributeMapping.getMappedType(); + final MappingType mappedType = modelPart.getMappedType(); final int jdbcValueCount; final Object rawJdbcValue = rawJdbcValues[jdbcIndex]; if ( mappedType instanceof EmbeddableMappingType ) { final EmbeddableMappingType embeddableMappingType = (EmbeddableMappingType) mappedType; if ( embeddableMappingType.getAggregateMapping() != null ) { jdbcValueCount = 1; - attributeValues[attributeIndex] = rawJdbcValue; + attributeValues.setAttributeValue( attributeIndex, rawJdbcValue ); } else { jdbcValueCount = embeddableMappingType.getJdbcValueCount(); final Object[] subJdbcValues = new Object[jdbcValueCount]; System.arraycopy( rawJdbcValues, jdbcIndex, subJdbcValues, 0, subJdbcValues.length ); - final Object[] subValues = getAttributeValues( embeddableMappingType, subJdbcValues, options ); - attributeValues[attributeIndex] = embeddableMappingType.getRepresentationStrategy() - .getInstantiator() - .instantiate( - () -> subValues, - embeddableMappingType.findContainingEntityMapping() - .getEntityPersister() - .getFactory() - ); + final StructAttributeValues subValues = getAttributeValues( embeddableMappingType, subJdbcValues, options ); + attributeValues.setAttributeValue( + attributeIndex, + instantiate( embeddableMappingType, subValues, options.getSessionFactory() ) + ); } } else { - assert attributeMapping.getJdbcTypeCount() == 1; + assert modelPart.getJdbcTypeCount() == 1; jdbcValueCount = 1; - final JdbcMapping jdbcMapping = attributeMapping.getSingleJdbcMapping(); + final JdbcMapping jdbcMapping = modelPart.getSingleJdbcMapping(); final Object jdbcValue = jdbcMapping.getJdbcJavaType().wrap( rawJdbcValue, options ); - attributeValues[attributeIndex] = jdbcMapping.convertToDomainValue( jdbcValue ); + attributeValues.setAttributeValue( attributeIndex, jdbcMapping.convertToDomainValue( jdbcValue ) ); } return jdbcValueCount; } @@ -95,18 +92,20 @@ public class StructHelper { public static Object[] getJdbcValues( EmbeddableMappingType embeddableMappingType, int[] orderMapping, - Object[] attributeValues, + Object domainValue, WrapperOptions options) throws SQLException { final int jdbcValueCount = embeddableMappingType.getJdbcValueCount(); + final int valueCount = jdbcValueCount + ( embeddableMappingType.isPolymorphic() ? 1 : 0 ); + final Object[] values = embeddableMappingType.getValues( domainValue ); final Object[] jdbcValues; - if ( jdbcValueCount != attributeValues.length || orderMapping != null ) { - jdbcValues = new Object[jdbcValueCount]; + if ( valueCount != values.length || orderMapping != null ) { + jdbcValues = new Object[valueCount]; } else { - jdbcValues = attributeValues; + jdbcValues = values; } int jdbcIndex = 0; - for ( int i = 0; i < attributeValues.length; i++ ) { + for ( int i = 0; i < values.length; i++ ) { final int attributeIndex; if ( orderMapping == null ) { attributeIndex = i; @@ -115,20 +114,45 @@ public class StructHelper { attributeIndex = orderMapping[i]; } jdbcIndex += injectJdbcValue( - embeddableMappingType.getAttributeMapping( attributeIndex ), - attributeValues, + getEmbeddedPart( embeddableMappingType, jdbcValueCount, attributeIndex ), + values, attributeIndex, jdbcValues, jdbcIndex, options ); } - assert jdbcIndex == jdbcValueCount; + assert jdbcIndex == valueCount; return jdbcValues; } + public static Object instantiate( + EmbeddableMappingType embeddableMappingType, + StructAttributeValues attributeValues, + SessionFactoryImplementor sessionFactory) { + final EmbeddableRepresentationStrategy representationStrategy = embeddableMappingType.getRepresentationStrategy(); + final EmbeddableInstantiator instantiator; + if ( !embeddableMappingType.isPolymorphic() ) { + instantiator = representationStrategy.getInstantiator(); + } + else { + // the discriminator here is the composite class name because it gets converted to the domain type when extracted + instantiator = representationStrategy.getInstantiatorForClass( (String) attributeValues.getDiscriminator() ); + } + return instantiator.instantiate( attributeValues, sessionFactory ); + } + + public static ValuedModelPart getEmbeddedPart( + EmbeddableMappingType embeddableMappingType, + int numberOfAttributes, + int position) { + return position == numberOfAttributes ? + embeddableMappingType.getDiscriminatorMapping() : + embeddableMappingType.getAttributeMapping( position ); + } + private static int injectJdbcValue( - AttributeMapping attributeMapping, + ValuedModelPart attributeMapping, Object[] attributeValues, int attributeIndex, Object[] jdbcValues, @@ -149,13 +173,14 @@ public class StructHelper { ); } else { - jdbcValueCount = embeddableMappingType.getJdbcValueCount(); + jdbcValueCount = embeddableMappingType.getJdbcValueCount() + ( embeddableMappingType.isPolymorphic() ? 1 : 0 ); final int numberOfAttributeMappings = embeddableMappingType.getNumberOfAttributeMappings(); + final int numberOfValues = numberOfAttributeMappings + ( embeddableMappingType.isPolymorphic() ? 1 : 0 ); final Object[] subValues = embeddableMappingType.getValues( attributeValues[attributeIndex] ); int offset = 0; - for ( int i = 0; i < numberOfAttributeMappings; i++ ) { + for ( int i = 0; i < numberOfValues; i++ ) { offset += injectJdbcValue( - embeddableMappingType.getAttributeMapping( i ), + getEmbeddedPart( embeddableMappingType, numberOfAttributeMappings, i ), subValues, i, jdbcValues, @@ -226,10 +251,10 @@ public class StructHelper { int[] inverseMapping, Object[] sourceJdbcValues, Object[] targetJdbcValues) { - final int numberOfAttributeMappings = embeddableMappingType.getNumberOfAttributeMappings(); + final int numberOfAttributes = embeddableMappingType.getNumberOfAttributeMappings(); int targetJdbcOffset = 0; - for ( int i = 0; i < numberOfAttributeMappings; i++ ) { - final AttributeMapping attributeMapping = embeddableMappingType.getAttributeMapping( i ); + for ( int i = 0; i < numberOfAttributes + ( embeddableMappingType.isPolymorphic() ? 1 : 0 ); i++ ) { + final ValuedModelPart attributeMapping = getEmbeddedPart( embeddableMappingType, numberOfAttributes, i ); final MappingType mappedType = attributeMapping.getMappedType(); final int jdbcValueCount = getJdbcValueCount( mappedType ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/StructJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/StructJdbcType.java index 2127d3abec..14a46f50cb 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/StructJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/StructJdbcType.java @@ -11,14 +11,12 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Struct; -import java.util.ArrayList; import org.hibernate.boot.model.naming.Identifier; -import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.MappingType; -import org.hibernate.metamodel.spi.EmbeddableInstantiator; +import org.hibernate.metamodel.mapping.ValuedModelPart; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.type.BasicPluralType; import org.hibernate.type.BasicType; @@ -29,12 +27,14 @@ import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.java.spi.UnknownBasicJavaType; import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; -import org.hibernate.type.descriptor.jdbc.ArrayJdbcType; import org.hibernate.type.descriptor.jdbc.BasicBinder; import org.hibernate.type.descriptor.jdbc.BasicExtractor; import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.spi.TypeConfiguration; +import static org.hibernate.dialect.StructHelper.getEmbeddedPart; +import static org.hibernate.dialect.StructHelper.instantiate; + /** * @author Christian Beikov */ @@ -132,7 +132,7 @@ public class StructJdbcType implements org.hibernate.type.descriptor.jdbc.Struct final Object[] jdbcValues = StructHelper.getJdbcValues( embeddableMappingType, orderMapping, - embeddableMappingType.getValues( domainValue ), + domainValue, options ); return options.getSession() @@ -206,36 +206,33 @@ public class StructJdbcType implements org.hibernate.type.descriptor.jdbc.Struct return (X) values; } assert embeddableMappingType != null && embeddableMappingType.getJavaType() == getJavaType(); - final Object[] attributeValues = getAttributeValues( + final StructAttributeValues attributeValues = getAttributeValues( embeddableMappingType, orderMapping, values, options ); //noinspection unchecked - return (X) embeddableMappingType.getRepresentationStrategy().getInstantiator().instantiate( - () -> attributeValues, - options.getSessionFactory() - ); + return (X) instantiate( embeddableMappingType, attributeValues, options.getSessionFactory() ); } }; } - private Object[] getAttributeValues( + private StructAttributeValues getAttributeValues( EmbeddableMappingType embeddableMappingType, int[] orderMapping, Object[] rawJdbcValues, WrapperOptions options) throws SQLException { final int numberOfAttributeMappings = embeddableMappingType.getNumberOfAttributeMappings(); - final Object[] attributeValues; - if ( numberOfAttributeMappings != rawJdbcValues.length || orderMapping != null ) { - attributeValues = new Object[numberOfAttributeMappings]; - } - else { - attributeValues = rawJdbcValues; - } + final int size = numberOfAttributeMappings + ( embeddableMappingType.isPolymorphic() ? 1 : 0 ); + final StructAttributeValues attributeValues = new StructAttributeValues( + numberOfAttributeMappings, + orderMapping != null ? + null : + rawJdbcValues + ); int jdbcIndex = 0; - for ( int i = 0; i < numberOfAttributeMappings; i++ ) { + for ( int i = 0; i < size; i++ ) { final int attributeIndex; if ( orderMapping == null ) { attributeIndex = i; @@ -243,9 +240,9 @@ public class StructJdbcType implements org.hibernate.type.descriptor.jdbc.Struct else { attributeIndex = orderMapping[i]; } - final AttributeMapping attributeMapping = embeddableMappingType.getAttributeMapping( attributeIndex ); + final ValuedModelPart modelPart = getEmbeddedPart( embeddableMappingType, numberOfAttributeMappings, attributeIndex ); jdbcIndex += injectAttributeValue( - attributeMapping, + modelPart, attributeValues, attributeIndex, rawJdbcValues, @@ -257,13 +254,13 @@ public class StructJdbcType implements org.hibernate.type.descriptor.jdbc.Struct } private int injectAttributeValue( - AttributeMapping attributeMapping, - Object[] attributeValues, + ValuedModelPart modelPart, + StructAttributeValues attributeValues, int attributeIndex, Object[] rawJdbcValues, int jdbcIndex, WrapperOptions options) throws SQLException { - final MappingType mappedType = attributeMapping.getMappedType(); + final MappingType mappedType = modelPart.getMappedType(); final int jdbcValueCount; final Object rawJdbcValue = rawJdbcValues[jdbcIndex]; if ( mappedType instanceof EmbeddableMappingType ) { @@ -271,13 +268,13 @@ public class StructJdbcType implements org.hibernate.type.descriptor.jdbc.Struct if ( embeddableMappingType.getAggregateMapping() != null ) { jdbcValueCount = 1; if ( rawJdbcValue == null ) { - attributeValues[attributeIndex] = null; + attributeValues.setAttributeValue( attributeIndex, null ); } else { final AggregateJdbcType aggregateJdbcType = (AggregateJdbcType) embeddableMappingType.getAggregateMapping() .getJdbcMapping() .getJdbcType(); - final Object[] subValues; + final StructAttributeValues subValues; if ( aggregateJdbcType instanceof StructJdbcType ) { subValues = getAttributeValues( embeddableMappingType, @@ -287,37 +284,33 @@ public class StructJdbcType implements org.hibernate.type.descriptor.jdbc.Struct ); } else { - subValues = aggregateJdbcType.extractJdbcValues( rawJdbcValue, options ); + subValues = StructHelper.getAttributeValues( + embeddableMappingType, + aggregateJdbcType.extractJdbcValues( rawJdbcValue, options ), + options + ); } - attributeValues[attributeIndex] = embeddableMappingType.getRepresentationStrategy() - .getInstantiator() - .instantiate( - () -> subValues, - embeddableMappingType.findContainingEntityMapping() - .getEntityPersister() - .getFactory() - ); + attributeValues.setAttributeValue( + attributeIndex, + instantiate( embeddableMappingType, subValues, options.getSessionFactory() ) + ); } } else { jdbcValueCount = embeddableMappingType.getJdbcValueCount(); final Object[] jdbcValues = new Object[jdbcValueCount]; System.arraycopy( rawJdbcValues, jdbcIndex, jdbcValues, 0, jdbcValues.length ); - final Object[] subValues = getAttributeValues( embeddableMappingType, null, jdbcValues, options ); - attributeValues[attributeIndex] = embeddableMappingType.getRepresentationStrategy() - .getInstantiator() - .instantiate( - () -> subValues, - embeddableMappingType.findContainingEntityMapping() - .getEntityPersister() - .getFactory() - ); + final StructAttributeValues subValues = getAttributeValues( embeddableMappingType, null, jdbcValues, options ); + attributeValues.setAttributeValue( + attributeIndex, + instantiate( embeddableMappingType, subValues, options.getSessionFactory() ) + ); } } else { - assert attributeMapping.getJdbcTypeCount() == 1; + assert modelPart.getJdbcTypeCount() == 1; jdbcValueCount = 1; - final JdbcMapping jdbcMapping = attributeMapping.getSingleJdbcMapping(); + final JdbcMapping jdbcMapping = modelPart.getSingleJdbcMapping(); final Object jdbcValue; if ( rawJdbcValue == null ) { jdbcValue = null; @@ -358,10 +351,8 @@ public class StructJdbcType implements org.hibernate.type.descriptor.jdbc.Struct newArray = new Object[array.length]; final AggregateJdbcType aggregateJdbcType = (AggregateJdbcType) elementJdbcType; final EmbeddableMappingType subEmbeddableMappingType = aggregateJdbcType.getEmbeddableMappingType(); - final EmbeddableInstantiator instantiator = subEmbeddableMappingType.getRepresentationStrategy() - .getInstantiator(); for ( int j = 0; j < array.length; j++ ) { - final Object[] subValues = StructHelper.getAttributeValues( + final StructAttributeValues subValues = StructHelper.getAttributeValues( subEmbeddableMappingType, aggregateJdbcType.extractJdbcValues( array[j], @@ -369,10 +360,7 @@ public class StructJdbcType implements org.hibernate.type.descriptor.jdbc.Struct ), options ); - newArray[j] = instantiator.instantiate( - () -> subValues, - options.getSessionFactory() - ); + newArray[j] = instantiate( subEmbeddableMappingType, subValues, options.getSessionFactory() ); } jdbcValue = jdbcMapping.getJdbcJavaType().wrap( newArray, options ); break; @@ -386,7 +374,7 @@ public class StructJdbcType implements org.hibernate.type.descriptor.jdbc.Struct break; } } - attributeValues[attributeIndex] = jdbcMapping.convertToDomainValue( jdbcValue ); + attributeValues.setAttributeValue( attributeIndex, jdbcMapping.convertToDomainValue( jdbcValue ) ); } return jdbcValueCount; } @@ -406,13 +394,13 @@ public class StructJdbcType implements org.hibernate.type.descriptor.jdbc.Struct targetJdbcValues = jdbcValues.clone(); } final int numberOfAttributeMappings = embeddableMappingType.getNumberOfAttributeMappings(); - for ( int i = 0; i < numberOfAttributeMappings; i++ ) { - final AttributeMapping attributeMapping; + for ( int i = 0; i < numberOfAttributeMappings + ( embeddableMappingType.isPolymorphic() ? 1 : 0 ); i++ ) { + final ValuedModelPart attributeMapping; if ( orderMapping == null ) { - attributeMapping = embeddableMappingType.getAttributeMapping( i ); + attributeMapping = getEmbeddedPart( embeddableMappingType, numberOfAttributeMappings, i ); } else { - attributeMapping = embeddableMappingType.getAttributeMapping( orderMapping[i] ); + attributeMapping = getEmbeddedPart( embeddableMappingType, numberOfAttributeMappings, orderMapping[i] ); } final MappingType mappedType = attributeMapping.getMappedType(); @@ -472,10 +460,8 @@ public class StructJdbcType implements org.hibernate.type.descriptor.jdbc.Struct newArray = new Object[array.length]; final AggregateJdbcType aggregateJdbcType = (AggregateJdbcType) elementJdbcType; final EmbeddableMappingType subEmbeddableMappingType = aggregateJdbcType.getEmbeddableMappingType(); - final EmbeddableInstantiator instantiator = subEmbeddableMappingType.getRepresentationStrategy() - .getInstantiator(); for ( int j = 0; j < array.length; j++ ) { - final Object[] subValues = StructHelper.getAttributeValues( + final StructAttributeValues subValues = StructHelper.getAttributeValues( subEmbeddableMappingType, aggregateJdbcType.extractJdbcValues( array[j], @@ -483,10 +469,7 @@ public class StructJdbcType implements org.hibernate.type.descriptor.jdbc.Struct ), options ); - newArray[j] = instantiator.instantiate( - () -> subValues, - options.getSessionFactory() - ); + newArray[j] = instantiate( subEmbeddableMappingType, subValues, options.getSessionFactory() ); } targetJdbcValues[jdbcIndex] = jdbcMapping.getJdbcJavaType().wrap( newArray, options ); break; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/XmlHelper.java b/hibernate-core/src/main/java/org/hibernate/dialect/XmlHelper.java index cd68779d7e..2a148be6b0 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/XmlHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/XmlHelper.java @@ -18,10 +18,10 @@ import java.util.List; import org.hibernate.Internal; import org.hibernate.internal.util.CharSequenceHelper; -import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.SelectableMapping; +import org.hibernate.metamodel.mapping.ValuedModelPart; import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping; import org.hibernate.sql.ast.spi.SqlAppender; import org.hibernate.type.SqlTypes; @@ -34,6 +34,9 @@ import org.hibernate.type.descriptor.java.JdbcTimestampJavaType; import org.hibernate.type.descriptor.java.OffsetDateTimeJavaType; import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; +import static org.hibernate.dialect.StructHelper.getEmbeddedPart; +import static org.hibernate.dialect.StructHelper.instantiate; + /** * A Helper for serializing and deserializing XML, based on an {@link EmbeddableMappingType}. */ @@ -234,18 +237,15 @@ public class XmlHelper { array = values.toArray(); } else { - array = new Object[embeddableMappingType.getJdbcValueCount()]; + array = new Object[embeddableMappingType.getJdbcValueCount() + ( embeddableMappingType.isPolymorphic() ? 1 : 0 )]; end = fromString( embeddableMappingType, string, returnEmbeddable, options, array, START_TAG.length() ); } assert end + END_TAG.length() == string.length(); if ( returnEmbeddable ) { - final Object[] attributeValues = StructHelper.getAttributeValues( embeddableMappingType, array, options ); + final StructAttributeValues attributeValues = StructHelper.getAttributeValues( embeddableMappingType, array, options ); //noinspection unchecked - return (X) embeddableMappingType.getRepresentationStrategy().getInstantiator().instantiate( - () -> attributeValues, - options.getSessionFactory() - ); + return (X) instantiate( embeddableMappingType, attributeValues, options.getSessionFactory() ); } //noinspection unchecked return (X) array; @@ -416,15 +416,12 @@ public class XmlHelper { ); } if ( returnEmbeddable ) { - final Object[] attributeValues = StructHelper.getAttributeValues( + final StructAttributeValues attributeValues = StructHelper.getAttributeValues( subMappingType, subValues, options ); - final Object subValue = subMappingType.getRepresentationStrategy() - .getInstantiator() - .instantiate( () -> attributeValues, options.getSessionFactory() ); - values[selectableIndex] = subValue; + values[selectableIndex] = instantiate( subMappingType, attributeValues, options.getSessionFactory() ); } else { values[selectableIndex] = subValues; @@ -491,11 +488,12 @@ public class XmlHelper { WrapperOptions options, XMLAppender sb) { final Object[] array = embeddableMappingType.getValues( value ); + final int numberOfAttributes = embeddableMappingType.getNumberOfAttributeMappings(); for ( int i = 0; i < array.length; i++ ) { if ( array[i] == null ) { continue; } - final AttributeMapping attributeMapping = embeddableMappingType.getAttributeMapping( i ); + final ValuedModelPart attributeMapping = getEmbeddedPart( embeddableMappingType, numberOfAttributes, i ); if ( attributeMapping instanceof SelectableMapping ) { final SelectableMapping selectable = (SelectableMapping) attributeMapping; final String tagName = selectable.getSelectableName(); diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderSqlAstCreationState.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderSqlAstCreationState.java index e27c651047..1a82de69aa 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderSqlAstCreationState.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderSqlAstCreationState.java @@ -11,6 +11,8 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Function; + import jakarta.persistence.CacheRetrieveMode; import jakarta.persistence.CacheStoreMode; @@ -163,12 +165,12 @@ public class LoaderSqlAstCreationState } @Override - public ImmutableFetchList visitNestedFetches(FetchParent fetchParent) { + public R withNestedFetchParent(FetchParent fetchParent, Function action) { final FetchParent nestingFetchParent = processingState.getNestingFetchParent(); processingState.setNestingFetchParent( fetchParent ); - final ImmutableFetchList fetches = fetchProcessor.visitFetches( fetchParent, this ); + final R result = action.apply( fetchParent ); processingState.setNestingFetchParent( nestingFetchParent ); - return fetches; + return result; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Component.java b/hibernate-core/src/main/java/org/hibernate/mapping/Component.java index 25a192efed..409fb3b461 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Component.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Component.java @@ -8,6 +8,7 @@ package org.hibernate.mapping; import java.lang.reflect.Constructor; import java.util.ArrayList; +import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; @@ -20,6 +21,7 @@ import java.util.Set; import org.hibernate.Internal; import org.hibernate.MappingException; import org.hibernate.Remove; +import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.boot.model.relational.Database; import org.hibernate.boot.model.relational.ExportableProducer; import org.hibernate.boot.model.relational.SqlStringGenerationContext; @@ -73,8 +75,12 @@ public class Component extends SimpleValue implements MetaAttributable, Sortable private boolean isKey; private Boolean isGeneric; private String roleName; + private Value discriminator; + private Map discriminatorValues; + private Map subclassToSuperclass; private final ArrayList properties = new ArrayList<>(); + private Map propertyDeclaringClasses; private int[] originalPropertyOrder = ArrayHelper.EMPTY_INT_ARRAY; private Map metaAttributes; @@ -89,6 +95,7 @@ public class Component extends SimpleValue implements MetaAttributable, Sortable private AggregateColumn parentAggregateColumn; private String structName; private String[] structColumnNames; + private transient Class componentClass; // lazily computed based on 'properties' field: invalidate by setting to null when properties are modified private transient List cachedSelectables; // lazily computed based on 'properties' field: invalidate by setting to null when properties are modified @@ -123,7 +130,9 @@ public class Component extends SimpleValue implements MetaAttributable, Sortable super( original ); this.properties.addAll( original.properties ); this.originalPropertyOrder = original.originalPropertyOrder == null ? null : original.originalPropertyOrder.clone(); + this.propertyDeclaringClasses = original.propertyDeclaringClasses; this.componentClassName = original.componentClassName; + this.componentClass = original.componentClass; this.embedded = original.embedded; this.parentProperty = original.parentProperty; this.owner = original.owner; @@ -132,6 +141,9 @@ public class Component extends SimpleValue implements MetaAttributable, Sortable this.metaAttributes = original.metaAttributes == null ? null : new HashMap<>( original.metaAttributes ); this.isKey = original.isKey; this.roleName = original.roleName; + this.discriminator = original.discriminator; + this.discriminatorValues = original.discriminatorValues; + this.subclassToSuperclass = original.subclassToSuperclass; this.customInstantiator = original.customInstantiator; this.type = original.type; } @@ -154,11 +166,28 @@ public class Component extends SimpleValue implements MetaAttributable, Sortable return properties; } - public void addProperty(Property p) { + public void addProperty(Property p, XClass declaringClass) { properties.add( p ); + if ( isPolymorphic() && declaringClass != null ) { + if ( propertyDeclaringClasses == null ) { + propertyDeclaringClasses = new HashMap<>(); + } + propertyDeclaringClasses.put( p, declaringClass.getName() ); + } propertiesListModified(); } + public void addProperty(Property p) { + addProperty( p, null ); + } + + public String getPropertyDeclaringClass(Property p) { + if ( propertyDeclaringClasses != null ) { + return propertyDeclaringClasses.get( p ); + } + return null; + } + private void propertiesListModified() { this.cachedSelectables = null; this.cachedColumns = null; @@ -172,9 +201,13 @@ public class Component extends SimpleValue implements MetaAttributable, Sortable @Override public List getSelectables() { if ( cachedSelectables == null ) { - cachedSelectables = properties.stream() + final List selectables = properties.stream() .flatMap( p -> p.getSelectables().stream() ) .collect( toList() ); + if ( discriminator != null ) { + selectables.addAll( discriminator.getSelectables() ); + } + cachedSelectables = selectables; } return cachedSelectables; } @@ -185,9 +218,13 @@ public class Component extends SimpleValue implements MetaAttributable, Sortable return cachedColumns; } else { - this.cachedColumns = properties.stream() + final List columns = properties.stream() .flatMap( p -> p.getValue().getColumns().stream() ) .collect( toList() ); + if ( discriminator != null ) { + columns.addAll( discriminator.getColumns() ); + } + this.cachedColumns = Collections.unmodifiableList( columns ); return cachedColumns; } } @@ -233,6 +270,9 @@ public class Component extends SimpleValue implements MetaAttributable, Sortable aggregatedColumns.addAll( value.getColumns() ); } } + if ( component.isPolymorphic() ) { + aggregatedColumns.addAll( component.getDiscriminator().getColumns() ); + } } private void notifyPropertiesAboutAggregateColumn(AggregateColumn aggregateColumn, Component component) { @@ -254,6 +294,9 @@ public class Component extends SimpleValue implements MetaAttributable, Sortable } } } + if ( component.isPolymorphic() ) { + ( (BasicValue) component.getDiscriminator() ).setAggregateColumn( aggregateColumn ); + } } public AggregateColumn getParentAggregateColumn() { @@ -275,7 +318,27 @@ public class Component extends SimpleValue implements MetaAttributable, Sortable @Override public void checkColumnDuplication(Set distinctColumns, String owner) { if ( aggregateColumn == null ) { - checkPropertyColumnDuplication( distinctColumns, getProperties(), owner ); + if ( isPolymorphic() ) { + // We can allow different subtypes reusing the same columns + // since only one subtype can exist at one time + final Map> distinctColumnsByClass = new HashMap<>(); + for ( Property prop : properties ) { + if ( prop.isUpdateable() || prop.isInsertable() ) { + final String declaringClass = propertyDeclaringClasses.get( prop ); + final Set set = distinctColumnsByClass.computeIfAbsent( + declaringClass, + k -> new HashSet<>( distinctColumns ) + ); + prop.getValue().checkColumnDuplication( set, owner ); + } + } + for ( Set columns : distinctColumnsByClass.values() ) { + distinctColumns.addAll( columns ); + } + } + else { + checkPropertyColumnDuplication( distinctColumns, getProperties(), owner ); + } } else { checkPropertyColumnDuplication( new HashSet<>(), getProperties(), "component '" + getRoleName() + "'" ); @@ -288,21 +351,24 @@ public class Component extends SimpleValue implements MetaAttributable, Sortable } public Class getComponentClass() throws MappingException { - if ( componentClassName == null ) { - return null; - } - else { - final ClassLoaderService classLoaderService = getMetadata() - .getMetadataBuildingOptions() - .getServiceRegistry() - .requireService( ClassLoaderService.class ); - try { - return classLoaderService.classForName( componentClassName ); + Class result = componentClass; + if ( result == null ) { + if ( componentClassName == null ) { + return null; } - catch (ClassLoadingException e) { - throw new MappingException("component class not found: " + componentClassName, e); + else { + try { + result = componentClass = getMetadata() + .getMetadataBuildingOptions() + .getServiceRegistry() + .requireService( ClassLoaderService.class ).classForName( componentClassName ); + } + catch (ClassLoadingException e) { + throw new MappingException( "component class not found: " + componentClassName, e ); + } } } + return result; } public PersistentClass getOwner() { @@ -315,6 +381,7 @@ public class Component extends SimpleValue implements MetaAttributable, Sortable public void setComponentClassName(String componentClass) { this.componentClassName = componentClass; + this.componentClass = null; } public void setEmbedded(boolean embedded) { @@ -426,18 +493,25 @@ public class Component extends SimpleValue implements MetaAttributable, Sortable @Override public boolean[] getColumnInsertability() { - final boolean[] result = new boolean[ getColumnSpan() ]; + final boolean[] result = new boolean[getColumnSpan()]; int i = 0; for ( Property prop : getProperties() ) { - final boolean[] chunk = prop.getValue().getColumnInsertability(); - if ( prop.isInsertable() ) { - System.arraycopy( chunk, 0, result, i, chunk.length ); - } - i += chunk.length; + i += copyFlags( prop.getValue().getColumnInsertability(), result, i, prop.isInsertable() ); } + if ( isPolymorphic() ) { + i += copyFlags( getDiscriminator().getColumnInsertability(), result, i, true ); + } + assert i == getColumnSpan(); return result; } + private static int copyFlags(boolean[] chunk, boolean[] result, int i, boolean doCopy) { + if ( doCopy ) { + System.arraycopy( chunk, 0, result, i, chunk.length ); + } + return chunk.length; + } + @Override public boolean hasAnyInsertableColumns() { for ( Property property : properties ) { @@ -451,15 +525,15 @@ public class Component extends SimpleValue implements MetaAttributable, Sortable @Override public boolean[] getColumnUpdateability() { - boolean[] result = new boolean[ getColumnSpan() ]; - int i=0; + boolean[] result = new boolean[getColumnSpan()]; + int i = 0; for ( Property prop : getProperties() ) { - boolean[] chunk = prop.getValue().getColumnUpdateability(); - if ( prop.isUpdateable() ) { - System.arraycopy(chunk, 0, result, i, chunk.length); - } - i+=chunk.length; + i += copyFlags( prop.getValue().getColumnUpdateability(), result, i, prop.isUpdateable() ); } + if ( isPolymorphic() ) { + i += copyFlags( getDiscriminator().getColumnUpdateability(), result, i, true ); + } + assert i == getColumnSpan(); return result; } @@ -530,6 +604,34 @@ public class Component extends SimpleValue implements MetaAttributable, Sortable this.roleName = roleName; } + public Value getDiscriminator() { + return discriminator; + } + + public void setDiscriminator(Value discriminator) { + this.discriminator = discriminator; + } + + public boolean isPolymorphic() { + return discriminator != null; + } + + public Map getDiscriminatorValues() { + return discriminatorValues; + } + + public void setDiscriminatorValues(Map discriminatorValues) { + this.discriminatorValues = discriminatorValues; + } + + public String getSuperclass(String subclass) { + return subclassToSuperclass.get( subclass ); + } + + public void setSubclassToSuperclass(Map subclassToSuperclass) { + this.subclassToSuperclass = subclassToSuperclass; + } + @Override public String toString() { return getClass().getSimpleName() + '(' + componentClassName + ')'; diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/AbstractEmbeddableRepresentationStrategy.java b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/AbstractEmbeddableRepresentationStrategy.java deleted file mode 100644 index 66970b9f14..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/AbstractEmbeddableRepresentationStrategy.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * 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 java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import org.hibernate.mapping.Component; -import org.hibernate.mapping.Property; -import org.hibernate.metamodel.spi.EmbeddableRepresentationStrategy; -import org.hibernate.metamodel.spi.RuntimeModelCreationContext; -import org.hibernate.property.access.spi.PropertyAccess; -import org.hibernate.type.descriptor.java.JavaType; - -/** - * @author Steve Ebersole - */ -public abstract class AbstractEmbeddableRepresentationStrategy implements EmbeddableRepresentationStrategy { - private final JavaType embeddableJavaType; - - private final int propertySpan; - private final PropertyAccess[] propertyAccesses; - private final boolean hasCustomAccessors; - - private final Map attributeNameToPositionMap; - - public AbstractEmbeddableRepresentationStrategy( - Component bootDescriptor, - JavaType embeddableJavaType, - RuntimeModelCreationContext creationContext) { - this.propertySpan = bootDescriptor.getPropertySpan(); - this.embeddableJavaType = embeddableJavaType; - - this.propertyAccesses = new PropertyAccess[ propertySpan ]; - this.attributeNameToPositionMap = new ConcurrentHashMap<>( propertySpan ); - - boolean foundCustomAccessor = false; - int i = 0; - for ( Property property : bootDescriptor.getProperties() ) { - propertyAccesses[i] = buildPropertyAccess( property ); - attributeNameToPositionMap.put( property.getName(), i ); - - if ( !property.isBasicPropertyAccessor() ) { - foundCustomAccessor = true; - } - - i++; - } - - hasCustomAccessors = foundCustomAccessor; - } - - protected abstract PropertyAccess buildPropertyAccess(Property bootAttributeDescriptor); - - public JavaType getEmbeddableJavaType() { - return embeddableJavaType; - } - - @Override - public JavaType getMappedJavaType() { - return getEmbeddableJavaType(); - } - - public int getPropertySpan() { - return propertySpan; - } - - public PropertyAccess[] getPropertyAccesses() { - return propertyAccesses; - } - - public boolean hasCustomAccessors() { - return hasCustomAccessors; - } - - @Override - public PropertyAccess resolvePropertyAccess(Property bootAttributeDescriptor) { - return propertyAccesses[ attributeNameToPositionMap.get( bootAttributeDescriptor.getName() ) ]; - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/AttributeFactory.java b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/AttributeFactory.java index 5e94e1a26a..fb128535c0 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/AttributeFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/AttributeFactory.java @@ -10,10 +10,12 @@ import java.lang.reflect.Field; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; +import java.util.HashMap; import org.hibernate.AssertionFailure; import org.hibernate.PropertyNotFoundException; import org.hibernate.boot.model.convert.spi.ConverterDescriptor; +import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.internal.EntityManagerMessageLogger; import org.hibernate.internal.HEMLogging; import org.hibernate.mapping.AggregateColumn; @@ -255,7 +257,7 @@ public class AttributeFactory { final Class embeddableClass = (Class) component.getComponentClass(); if ( !component.isGeneric() ) { - final EmbeddableDomainType cached = context.locateEmbeddable( embeddableClass, component); + final EmbeddableDomainType cached = context.locateEmbeddable( embeddableClass, component ); if ( cached != null ) { return cached; } @@ -266,9 +268,30 @@ public class AttributeFactory { false, context.getJpaMetamodel() ); - context.registerEmbeddableType( embeddableType, component); + if ( component.isPolymorphic() ) { + final java.util.Collection embeddableSubclasses = component.getDiscriminatorValues().values(); + final java.util.Map> domainTypes = new HashMap<>(); + domainTypes.put( embeddableType.getTypeName(), embeddableType ); + final ClassLoaderService cls = context.getJpaMetamodel().getServiceRegistry().requireService( + ClassLoaderService.class + ); + for ( final String subclassName : embeddableSubclasses ) { + if ( domainTypes.containsKey( subclassName ) ) { + assert subclassName.equals( embeddableType.getTypeName() ); + continue; + } + final Class subclass = cls.classForName( subclassName ); + context.registerEmbeddableType( new EmbeddableTypeImpl<>( + context.getJavaTypeRegistry().resolveManagedTypeDescriptor( subclass ), + domainTypes.get( component.getSuperclass( subclassName ) ), + false, + context.getJpaMetamodel() + ), component ); + } + } + return embeddableType; } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/EmbeddableInstantiatorPojoOptimized.java b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/EmbeddableInstantiatorPojoOptimized.java index a1fdacfcbf..1e108b681e 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/EmbeddableInstantiatorPojoOptimized.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/EmbeddableInstantiatorPojoOptimized.java @@ -11,7 +11,6 @@ import java.util.function.Supplier; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.spi.ValueAccess; -import org.hibernate.type.descriptor.java.JavaType; import static org.hibernate.bytecode.spi.ReflectionOptimizer.InstantiationOptimizer; @@ -24,10 +23,10 @@ public class EmbeddableInstantiatorPojoOptimized extends AbstractPojoInstantiato private final InstantiationOptimizer instantiationOptimizer; public EmbeddableInstantiatorPojoOptimized( - JavaType javaType, + Class embeddableClass, Supplier embeddableMappingAccess, InstantiationOptimizer instantiationOptimizer) { - super( javaType.getJavaTypeClass() ); + super( embeddableClass ); this.embeddableMappingAccess = embeddableMappingAccess; this.instantiationOptimizer = instantiationOptimizer; } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/EmbeddableInstantiatorPojoStandard.java b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/EmbeddableInstantiatorPojoStandard.java index 8129af0189..6983cb5015 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/EmbeddableInstantiatorPojoStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/EmbeddableInstantiatorPojoStandard.java @@ -17,7 +17,6 @@ import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.ReflectHelper; import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.spi.ValueAccess; -import org.hibernate.type.descriptor.java.JavaType; /** * Support for instantiating embeddables as POJO representation @@ -28,11 +27,11 @@ public class EmbeddableInstantiatorPojoStandard extends AbstractPojoInstantiator private final Supplier embeddableMappingAccess; private final Constructor constructor; - public EmbeddableInstantiatorPojoStandard(JavaType javaType, Supplier embeddableMappingAccess) { - super( javaType.getJavaTypeClass() ); + public EmbeddableInstantiatorPojoStandard(Class embeddableClass, Supplier embeddableMappingAccess) { + super( embeddableClass ); this.embeddableMappingAccess = embeddableMappingAccess; - this.constructor = resolveConstructor( javaType.getJavaTypeClass() ); + this.constructor = resolveConstructor( embeddableClass ); } protected static Constructor resolveConstructor(Class mappedPojoClass) { diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/EmbeddableRepresentationStrategyPojo.java b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/EmbeddableRepresentationStrategyPojo.java index fd48857ede..c2203eafbc 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/EmbeddableRepresentationStrategyPojo.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/EmbeddableRepresentationStrategyPojo.java @@ -6,12 +6,16 @@ */ package org.hibernate.metamodel.internal; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Locale; import java.util.Map; import java.util.function.Supplier; import org.hibernate.HibernateException; +import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.registry.selector.spi.StrategySelector; import org.hibernate.bytecode.spi.BytecodeProvider; import org.hibernate.bytecode.spi.ProxyFactoryFactory; @@ -25,6 +29,7 @@ import org.hibernate.mapping.Property; 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.RuntimeModelCreationContext; import org.hibernate.property.access.internal.PropertyAccessStrategyBackRefImpl; import org.hibernate.property.access.internal.PropertyAccessStrategyIndexBackRefImpl; @@ -36,14 +41,21 @@ import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry; import org.hibernate.type.internal.CompositeUserTypeJavaTypeWrapper; import org.hibernate.usertype.CompositeUserType; +import static org.hibernate.internal.util.NullnessUtil.castNonNull; + /** * @author Steve Ebersole */ -public class EmbeddableRepresentationStrategyPojo extends AbstractEmbeddableRepresentationStrategy { - private final StrategySelector strategySelector; +public class EmbeddableRepresentationStrategyPojo implements EmbeddableRepresentationStrategy { + private final JavaType embeddableJavaType; + private final PropertyAccess[] propertyAccesses; + private final Map attributeNameToPositionMap; + private final StrategySelector strategySelector; private final ReflectionOptimizer reflectionOptimizer; private final EmbeddableInstantiator instantiator; + private final Map instantiatorsByDiscriminator; + private final Map instantiatorsByClass; public EmbeddableRepresentationStrategyPojo( Component bootDescriptor, @@ -51,22 +63,68 @@ public class EmbeddableRepresentationStrategyPojo extends AbstractEmbeddableRepr EmbeddableInstantiator customInstantiator, CompositeUserType compositeUserType, RuntimeModelCreationContext creationContext) { - super( + this.embeddableJavaType = resolveEmbeddableJavaType( bootDescriptor, compositeUserType, creationContext ); + + final int propertySpan = bootDescriptor.getPropertySpan(); + this.propertyAccesses = new PropertyAccess[propertySpan]; + this.attributeNameToPositionMap = new HashMap<>( propertySpan ); + + // We need access to the Class objects, used only during initialization + final Map> subclassesByName = getSubclassesByName( bootDescriptor, creationContext ); + boolean foundCustomAccessor = false; + for ( int i = 0; i < bootDescriptor.getProperties().size(); i++ ) { + final Property property = bootDescriptor.getProperty( i ); + final Class embeddableClass = bootDescriptor.isPolymorphic() ? + castNonNull( subclassesByName ).get( bootDescriptor.getPropertyDeclaringClass( property ) ) : + getEmbeddableJavaType().getJavaTypeClass(); + propertyAccesses[i] = buildPropertyAccess( property, embeddableClass, customInstantiator == null ); + attributeNameToPositionMap.put( property.getName(), i ); + + if ( !property.isBasicPropertyAccessor() ) { + foundCustomAccessor = true; + } + } + + boolean hasCustomAccessors = foundCustomAccessor; + this.strategySelector = creationContext.getServiceRegistry().getService( StrategySelector.class ); + this.reflectionOptimizer = buildReflectionOptimizer( bootDescriptor, - resolveEmbeddableJavaType( bootDescriptor, compositeUserType, creationContext ), + hasCustomAccessors, + propertyAccesses, creationContext ); - - assert bootDescriptor.getComponentClass() != null; - - this.strategySelector = creationContext.getServiceRegistry().getService( StrategySelector.class ); - - this.reflectionOptimizer = buildReflectionOptimizer( bootDescriptor, creationContext ); - - this.instantiator = customInstantiator != null - ? customInstantiator - : determineInstantiator( bootDescriptor, runtimeDescriptorAccess, creationContext ); + if ( bootDescriptor.isPolymorphic() ) { + final int size = bootDescriptor.getDiscriminatorValues().size(); + this.instantiatorsByDiscriminator = new HashMap<>( size ); + this.instantiatorsByClass = new HashMap<>( size ); + for ( Map.Entry discriminator : bootDescriptor.getDiscriminatorValues().entrySet() ) { + final String className = discriminator.getValue(); + final EmbeddableInstantiator instantiator = determineInstantiator( + bootDescriptor, + castNonNull( subclassesByName ).get( className ), + reflectionOptimizer, + runtimeDescriptorAccess, + creationContext + ); + instantiatorsByDiscriminator.put( discriminator.getKey(), instantiator ); + instantiatorsByClass.put( className, instantiator ); + } + this.instantiator = null; + } + else { + this.instantiator = customInstantiator != null ? + customInstantiator : + determineInstantiator( + bootDescriptor, + bootDescriptor.getComponentClass(), + reflectionOptimizer, + runtimeDescriptorAccess, + creationContext + ); + this.instantiatorsByDiscriminator = null; + this.instantiatorsByClass = null; + } } private static JavaType resolveEmbeddableJavaType( @@ -83,40 +141,39 @@ public class EmbeddableRepresentationStrategyPojo extends AbstractEmbeddableRepr ); } - private EmbeddableInstantiator determineInstantiator( + private static EmbeddableInstantiator determineInstantiator( Component bootDescriptor, + Class embeddableClass, + ReflectionOptimizer reflectionOptimizer, Supplier runtimeDescriptorAccess, RuntimeModelCreationContext creationContext) { if ( reflectionOptimizer != null && reflectionOptimizer.getInstantiationOptimizer() != null ) { final ReflectionOptimizer.InstantiationOptimizer instantiationOptimizer = reflectionOptimizer.getInstantiationOptimizer(); return new EmbeddableInstantiatorPojoOptimized( - getEmbeddableJavaType(), + embeddableClass, runtimeDescriptorAccess, instantiationOptimizer ); } - if ( bootDescriptor.isEmbedded() && ReflectHelper.isAbstractClass( bootDescriptor.getComponentClass() ) ) { + if ( bootDescriptor.isEmbedded() && ReflectHelper.isAbstractClass( embeddableClass ) ) { return new EmbeddableInstantiatorProxied( - bootDescriptor.getComponentClass(), + embeddableClass, runtimeDescriptorAccess, creationContext.getServiceRegistry() .requireService( ProxyFactoryFactory.class ) - .buildBasicProxyFactory( bootDescriptor.getComponentClass() ) + .buildBasicProxyFactory( embeddableClass ) ); } - return new EmbeddableInstantiatorPojoStandard( getEmbeddableJavaType(), runtimeDescriptorAccess ); + return new EmbeddableInstantiatorPojoStandard( embeddableClass, runtimeDescriptorAccess ); } - @Override - public ReflectionOptimizer getReflectionOptimizer() { - return reflectionOptimizer; - } - - @Override - protected PropertyAccess buildPropertyAccess(Property bootAttributeDescriptor) { - PropertyAccessStrategy strategy = bootAttributeDescriptor.getPropertyAccessStrategy( getEmbeddableJavaType().getJavaTypeClass() ); + private PropertyAccess buildPropertyAccess( + Property bootAttributeDescriptor, + Class embeddableClass, + boolean requireSetters) { + PropertyAccessStrategy strategy = bootAttributeDescriptor.getPropertyAccessStrategy( embeddableClass ); if ( strategy == null ) { final String propertyAccessorName = bootAttributeDescriptor.getPropertyAccessorName(); @@ -160,17 +217,18 @@ public class EmbeddableRepresentationStrategyPojo extends AbstractEmbeddableRepr } return strategy.buildPropertyAccess( - getEmbeddableJavaType().getJavaTypeClass(), + embeddableClass, bootAttributeDescriptor.getName(), - instantiator instanceof StandardEmbeddableInstantiator + requireSetters ); } - private ReflectionOptimizer buildReflectionOptimizer( + private static ReflectionOptimizer buildReflectionOptimizer( Component bootDescriptor, + boolean hasCustomAccessors, + PropertyAccess[] propertyAccesses, RuntimeModelCreationContext creationContext) { - - if ( hasCustomAccessors() || bootDescriptor.getCustomInstantiator() != null || bootDescriptor.getInstantiator() != null ) { + if ( hasCustomAccessors || bootDescriptor.getCustomInstantiator() != null || bootDescriptor.getInstantiator() != null || bootDescriptor.isPolymorphic() ) { return null; } @@ -178,7 +236,7 @@ public class EmbeddableRepresentationStrategyPojo extends AbstractEmbeddableRepr int i = 0; for ( Property property : bootDescriptor.getProperties() ) { - propertyAccessMap.put( property.getName(), getPropertyAccesses()[i] ); + propertyAccessMap.put( property.getName(), propertyAccesses[i] ); i++; } @@ -187,6 +245,52 @@ public class EmbeddableRepresentationStrategyPojo extends AbstractEmbeddableRepr .getReflectionOptimizer( bootDescriptor.getComponentClass(), propertyAccessMap ); } + private static Map> getSubclassesByName( + Component bootDescriptor, + RuntimeModelCreationContext creationContext) { + if ( bootDescriptor.isPolymorphic() ) { + final Collection subclassNames = bootDescriptor.getDiscriminatorValues().values(); + final Map> result = new HashMap<>( subclassNames.size() ); + final ClassLoaderService classLoaderService = creationContext.getMetadata() + .getMetadataBuildingOptions() + .getServiceRegistry() + .requireService( ClassLoaderService.class ); + for ( final String subclassName : subclassNames ) { + final Class embeddableClass; + if ( subclassName.equals( bootDescriptor.getComponentClassName() ) ) { + embeddableClass = bootDescriptor.getComponentClass(); + } + else { + embeddableClass = classLoaderService.classForName( subclassName ); + } + result.put( subclassName, embeddableClass ); + } + return result; + } + else { + return null; + } + } + + public JavaType getEmbeddableJavaType() { + return embeddableJavaType; + } + + @Override + public JavaType getMappedJavaType() { + return getEmbeddableJavaType(); + } + + @Override + public ReflectionOptimizer getReflectionOptimizer() { + return reflectionOptimizer; + } + + @Override + public PropertyAccess resolvePropertyAccess(Property bootAttributeDescriptor) { + return propertyAccesses[ attributeNameToPositionMap.get( bootAttributeDescriptor.getName() ) ]; + } + @Override public RepresentationMode getMode() { return RepresentationMode.POJO; @@ -194,6 +298,27 @@ public class EmbeddableRepresentationStrategyPojo extends AbstractEmbeddableRepr @Override public EmbeddableInstantiator getInstantiator() { + assert instantiator != null && instantiatorsByDiscriminator == null && instantiatorsByClass == null; return instantiator; } + + @Override + public EmbeddableInstantiator getInstantiatorForDiscriminator(Object discriminatorValue) { + if ( instantiator != null ) { + assert instantiatorsByDiscriminator == null; + return instantiator; + } + assert instantiatorsByDiscriminator != null; + return instantiatorsByDiscriminator.get( discriminatorValue ); + } + + @Override + public EmbeddableInstantiator getInstantiatorForClass(String className) { + if ( instantiator != null ) { + assert instantiatorsByClass == null; + return instantiator; + } + assert instantiatorsByClass != null; + return instantiatorsByClass.get( className ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java index 48f6243e70..fe7223502a 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java @@ -407,6 +407,9 @@ public class MetadataContext { for ( EmbeddableDomainType embeddable : processingEmbeddables ) { final Component component = componentByEmbeddable.get( embeddable ); for ( Property property : component.getProperties() ) { + if ( component.isPolymorphic() && !component.getPropertyDeclaringClass( property ).equals( embeddable.getTypeName() ) ) { + continue; + } final PersistentAttribute attribute = attributeFactory.buildAttribute( (ManagedDomainType) embeddable, property ); if ( attribute != null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/DiscriminatorMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/DiscriminatorMapping.java index 5833a60325..18d11f4805 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/DiscriminatorMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/DiscriminatorMapping.java @@ -6,11 +6,16 @@ */ package org.hibernate.metamodel.mapping; +import org.hibernate.HibernateException; +import org.hibernate.engine.FetchTiming; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.ast.spi.SqlAstCreationState; import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.from.TableGroup; +import org.hibernate.sql.results.graph.DomainResultCreationState; +import org.hibernate.sql.results.graph.FetchParent; import org.hibernate.sql.results.graph.Fetchable; +import org.hibernate.sql.results.graph.basic.BasicFetch; import org.hibernate.type.descriptor.java.JavaType; /** @@ -30,6 +35,15 @@ public interface DiscriminatorMapping extends VirtualModelPart, BasicValuedModel */ DiscriminatorConverter getValueConverter(); + /** + * Retrieve the {@linkplain DiscriminatorValueDetails details} for a particular discriminator value. + * + * @throws HibernateException if there is value matching the provided one + */ + default DiscriminatorValueDetails resolveDiscriminatorValue(Object discriminatorValue) { + return getValueConverter().getDetailsForDiscriminatorValue( discriminatorValue ); + } + JdbcMapping getUnderlyingJdbcMapping(); /** @@ -59,4 +73,13 @@ public interface DiscriminatorMapping extends VirtualModelPart, BasicValuedModel JdbcMapping jdbcMappingToUse, TableGroup tableGroup, SqlAstCreationState creationState); + + @Override + BasicFetch generateFetch( + FetchParent fetchParent, + NavigablePath fetchablePath, + FetchTiming fetchTiming, + boolean selected, + String resultVariable, + DomainResultCreationState creationState); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EmbeddableDiscriminatorConverter.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EmbeddableDiscriminatorConverter.java new file mode 100644 index 0000000000..b9f5bc254d --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EmbeddableDiscriminatorConverter.java @@ -0,0 +1,129 @@ +/* + * 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.mapping; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Function; + +import org.hibernate.AssertionFailure; +import org.hibernate.HibernateException; +import org.hibernate.metamodel.mapping.internal.EmbeddableDiscriminatorValueDetailsImpl; +import org.hibernate.metamodel.model.domain.NavigableRole; +import org.hibernate.type.BasicType; +import org.hibernate.type.descriptor.java.JavaType; + +/** + * Handles conversion of discriminator values for embeddable subtype classes + * to their domain typed form. + * + * @author Marco Belladelli + * @see EmbeddableDiscriminatorMapping + */ +public class EmbeddableDiscriminatorConverter extends DiscriminatorConverter { + public static EmbeddableDiscriminatorConverter fromValueMappings( + NavigableRole role, + JavaType domainJavaType, + BasicType underlyingJdbcMapping, + Map valueMappings) { + final List valueDetailsList = new ArrayList<>( valueMappings.size() ); + valueMappings.forEach( (value, embeddableClassName) -> valueDetailsList.add( new EmbeddableDiscriminatorValueDetailsImpl( + value, + embeddableClassName + ) ) ); + return new EmbeddableDiscriminatorConverter<>( + role, + domainJavaType, + underlyingJdbcMapping.getJavaTypeDescriptor(), + valueDetailsList + ); + } + + private final Map discriminatorValueToDetailsMap; + private final Map embeddableClassNameToDetailsMap; + + public EmbeddableDiscriminatorConverter( + NavigableRole discriminatorRole, + JavaType domainJavaType, + JavaType relationalJavaType, + List valueMappings) { + super( discriminatorRole, domainJavaType, relationalJavaType ); + + this.discriminatorValueToDetailsMap = new HashMap<>( valueMappings.size() ); + this.embeddableClassNameToDetailsMap = new HashMap<>( valueMappings.size() ); + valueMappings.forEach( valueDetails -> { + discriminatorValueToDetailsMap.put( valueDetails.getValue(), valueDetails ); + embeddableClassNameToDetailsMap.put( valueDetails.getIndicatedEntityName(), valueDetails ); + } ); + } + + @Override + public O toDomainValue(R relationalForm) { + assert relationalForm == null || getRelationalJavaType().isInstance( relationalForm ); + + final DiscriminatorValueDetails matchingValueDetails = getDetailsForDiscriminatorValue( relationalForm ); + if ( matchingValueDetails == null ) { + throw new IllegalStateException( "Could not resolve discriminator value" ); + } + + //noinspection unchecked + return (O) matchingValueDetails.getIndicatedEntityName(); + } + + @Override + public R toRelationalValue(O domainForm) { + assert domainForm == null || domainForm instanceof String; + + if ( domainForm == null ) { + return null; + } + + final String embeddableClassName = (String) domainForm; + + //noinspection unchecked + return (R) getDetailsForEntityName( embeddableClassName ).getValue(); + } + + @Override + public DiscriminatorValueDetails getDetailsForDiscriminatorValue(Object value) { + final DiscriminatorValueDetails valueMatch = discriminatorValueToDetailsMap.get( value ); + if ( valueMatch != null ) { + return valueMatch; + } + + throw new HibernateException( "Unrecognized discriminator value: " + value ); + } + + @Override + public DiscriminatorValueDetails getDetailsForEntityName(String embeddableClassName) { + final DiscriminatorValueDetails valueDetails = embeddableClassNameToDetailsMap.get( embeddableClassName ); + if ( valueDetails != null ) { + return valueDetails; + } + + throw new AssertionFailure( "Unrecognized embeddable class: " + embeddableClassName ); + } + + @Override + public void forEachValueDetail(Consumer consumer) { + discriminatorValueToDetailsMap.forEach( (value, detail) -> consumer.accept( detail ) ); + } + + @Override + public X fromValueDetails(Function handler) { + for ( DiscriminatorValueDetails detail : discriminatorValueToDetailsMap.values() ) { + final X result = handler.apply( detail ); + if ( result != null ) { + return result; + } + } + return null; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EmbeddableDiscriminatorMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EmbeddableDiscriminatorMapping.java new file mode 100644 index 0000000000..39b8b13e18 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EmbeddableDiscriminatorMapping.java @@ -0,0 +1,27 @@ +/* + * 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.mapping; + +import org.hibernate.HibernateException; +import org.hibernate.sql.results.graph.FetchOptions; + +/** + * Details about the discriminator for an embeddable hierarchy. + * + * @author Marco Belladelli + * @see EmbeddableMappingType#getDiscriminatorMapping() + */ +public interface EmbeddableDiscriminatorMapping extends DiscriminatorMapping, FetchOptions { + /** + * Retrieve the relational discriminator value corresponding to the provided embeddable class name. + * + * @throws HibernateException if the embeddable class name is not handled by this discriminator + */ + default Object getDiscriminatorValue(String embeddableClassName) { + return getValueConverter().getDetailsForEntityName( embeddableClassName ).getValue(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EmbeddableMappingType.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EmbeddableMappingType.java index 3279de9dda..7bc534ab91 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EmbeddableMappingType.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EmbeddableMappingType.java @@ -36,6 +36,34 @@ public interface EmbeddableMappingType extends ManagedMappingType, SelectableMap boolean isCreateEmptyCompositesEnabled(); + /** + * Returns the {@linkplain EmbeddableDiscriminatorMapping discriminator mapping} + * if this discriminator type is polymorphic, {@code null} otherwise. + */ + default EmbeddableDiscriminatorMapping getDiscriminatorMapping() { + return null; + } + + /** + * Returns {@code true} if this embeddable mapping type defines a + * discriminator-based inheritance hierarchy, {@code false} otherwise. + */ + default boolean isPolymorphic() { + return getDiscriminatorMapping() != null; + } + + /** + * Returns {@code true} if the provided embeddable class contains the + * specified attribute mapping, {@code false} otherwise. + * @implNote This method always returns {@code true} for non-polymorphic embeddable types + * + * @param embeddableClassName the embeddable subclass in which the attribute must be declared + * @param attributeMapping the attribute to check + */ + default boolean declaresAttribute(String embeddableClassName, AttributeMapping attributeMapping) { + return true; + } + default SelectableMapping getAggregateMapping() { return null; } @@ -127,6 +155,9 @@ public interface EmbeddableMappingType extends ManagedMappingType, SelectableMap count += attributeMapping.getJdbcTypeCount(); } } + if ( isPolymorphic() && columnIndex == count ) { + return getDiscriminatorMapping(); + } return null; } @@ -175,6 +206,9 @@ public interface EmbeddableMappingType extends ManagedMappingType, SelectableMap offset += jdbcTypeCount; } } + if ( isPolymorphic() && getDiscriminatorMapping().getSelectableName().equals( selectableName ) ) { + return offset; + } return -1; } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EmbeddableValuedModelPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EmbeddableValuedModelPart.java index e54f93f7fb..858e154b17 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EmbeddableValuedModelPart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EmbeddableValuedModelPart.java @@ -107,7 +107,7 @@ public interface EmbeddableValuedModelPart extends ValuedModelPart, Fetchable, F @Override default int getNumberOfFetchables() { - return getEmbeddableTypeDescriptor().getNumberOfAttributeMappings(); + return getEmbeddableTypeDescriptor().getNumberOfFetchables(); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityDiscriminatorMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityDiscriminatorMapping.java index 900398d453..17cd1496df 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityDiscriminatorMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityDiscriminatorMapping.java @@ -58,34 +58,6 @@ public interface EntityDiscriminatorMapping extends DiscriminatorMapping, FetchO return -2; } - /** - * Retrieve the details for a particular discriminator value. - * - * Returns {@code null} if there is no match. - */ - DiscriminatorValueDetails resolveDiscriminatorValue(Object value); - - /** - * Create the appropriate SQL expression for this discriminator - * - * @param jdbcMappingToUse The JDBC mapping to use. This allows opting between - * the "domain result type" (aka Class) and the "underlying type" (Integer, String, etc) - */ - Expression resolveSqlExpression( - NavigablePath navigablePath, - JdbcMapping jdbcMappingToUse, - TableGroup tableGroup, - SqlAstCreationState creationState); - - @Override - BasicFetch generateFetch( - FetchParent fetchParent, - NavigablePath fetchablePath, - FetchTiming fetchTiming, - boolean selected, - String resultVariable, - DomainResultCreationState creationState); - @Override default FetchOptions getMappedFetchOptions() { return this; diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractDiscriminatorMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractDiscriminatorMapping.java index 39bed53e05..7fc0678423 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractDiscriminatorMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractDiscriminatorMapping.java @@ -17,6 +17,7 @@ import org.hibernate.metamodel.mapping.DiscriminatorValueDetails; import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.JdbcMapping; +import org.hibernate.metamodel.mapping.ManagedMappingType; import org.hibernate.metamodel.mapping.MappingType; import org.hibernate.metamodel.mapping.SelectableConsumer; import org.hibernate.metamodel.model.domain.NavigableRole; @@ -44,22 +45,22 @@ public abstract class AbstractDiscriminatorMapping implements EntityDiscriminato private final BasicType underlyingJdbcMapping; private final DiscriminatorType discriminatorType; - private final EntityMappingType entityDescriptor; + private final ManagedMappingType mappingType; public AbstractDiscriminatorMapping( - EntityMappingType entityDescriptor, + ManagedMappingType mappingType, DiscriminatorType discriminatorType, BasicType underlyingJdbcMapping) { this.underlyingJdbcMapping = underlyingJdbcMapping; - this.entityDescriptor = entityDescriptor; + this.mappingType = mappingType; - this.role = entityDescriptor.getNavigableRole().append( EntityDiscriminatorMapping.DISCRIMINATOR_ROLE_NAME ); + this.role = mappingType.getNavigableRole().append( EntityDiscriminatorMapping.DISCRIMINATOR_ROLE_NAME ); this.discriminatorType = discriminatorType; } public EntityMappingType getEntityDescriptor() { - return entityDescriptor; + return mappingType.asEntityMappingType(); } @Override @@ -86,14 +87,9 @@ public abstract class AbstractDiscriminatorMapping implements EntityDiscriminato return discriminatorType; } - @Override - public DiscriminatorValueDetails resolveDiscriminatorValue(Object value) { - return discriminatorType.getValueConverter().getDetailsForDiscriminatorValue( value ); - } - @Override public EntityMappingType findContainingEntityMapping() { - return entityDescriptor; + return mappingType.findContainingEntityMapping(); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractEmbeddableMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractEmbeddableMapping.java index 5b4d046b80..4b97876989 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractEmbeddableMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractEmbeddableMapping.java @@ -33,6 +33,7 @@ import org.hibernate.mapping.Value; import org.hibernate.metamodel.UnsupportedMappingException; import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.metamodel.mapping.AttributeMappingsList; +import org.hibernate.metamodel.mapping.EmbeddableDiscriminatorMapping; import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; import org.hibernate.metamodel.mapping.EntityMappingType; @@ -91,6 +92,10 @@ public abstract class AbstractEmbeddableMapping implements EmbeddableMappingType return optimizer.getAccessOptimizer().getPropertyValues( compositeInstance ); } + return getAttributeValues( compositeInstance ); + } + + protected Object[] getAttributeValues(Object compositeInstance) { final Object[] results = new Object[getNumberOfAttributeMappings()]; for ( int i = 0; i < results.length; i++ ) { final Getter getter = getAttributeMapping( i ).getAttributeMetadata() @@ -108,9 +113,13 @@ public abstract class AbstractEmbeddableMapping implements EmbeddableMappingType optimizer.getAccessOptimizer().setPropertyValues( component, values ); } else { - for ( int i = 0; i < values.length; i++ ) { - getAttributeMapping( i ).getPropertyAccess().getSetter().set( component, values[i] ); - } + setAttributeValues( component, values ); + } + } + + protected void setAttributeValues(Object component, Object[] values) { + for ( int i = 0; i < values.length; i++ ) { + getAttributeMapping( i ).getPropertyAccess().getSetter().set( component, values[i] ); } } @@ -641,6 +650,13 @@ public abstract class AbstractEmbeddableMapping implements EmbeddableMappingType attributeMapping.addToCacheKey( cacheKey, attributeMapping.getValue( value ), session ); } } + if ( isPolymorphic() ) { + final EmbeddableDiscriminatorMapping discriminatorMapping = getDiscriminatorMapping(); + final Object discriminatorValue = value != null ? + discriminatorMapping.getDiscriminatorValue( value.getClass().getName() ) + : null; + discriminatorMapping.addToCacheKey( cacheKey, discriminatorValue, session ); + } } @Override @@ -727,6 +743,10 @@ public abstract class AbstractEmbeddableMapping implements EmbeddableMappingType ) ); + if ( getDiscriminatorMapping() != null ) { + getDiscriminatorMapping().forEachSelectable( (index, selection) -> selectableMappings.add( selection ) ); + } + this.selectableMappings = new SelectableMappingsImpl( selectableMappings.toArray( new SelectableMapping[0] ) ); return true; diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AnyDiscriminatorPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AnyDiscriminatorPart.java index dd071a15cd..3da235f4b5 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AnyDiscriminatorPart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AnyDiscriminatorPart.java @@ -295,7 +295,7 @@ public class AnyDiscriminatorPart implements DiscriminatorMapping, FetchOptions } @Override - public Fetch generateFetch( + public BasicFetch generateFetch( FetchParent fetchParent, NavigablePath fetchablePath, FetchTiming fetchTiming, diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CaseStatementDiscriminatorMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CaseStatementDiscriminatorMappingImpl.java index 633cfed119..08b66db39c 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CaseStatementDiscriminatorMappingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CaseStatementDiscriminatorMappingImpl.java @@ -51,8 +51,7 @@ public class CaseStatementDiscriminatorMappingImpl extends AbstractDiscriminator String[] notNullColumnNames, String[] discriminatorValues, boolean[] discriminatorAbstract, - DiscriminatorType incomingDiscriminatorType, - MappingModelCreationProcess creationProcess) { + DiscriminatorType incomingDiscriminatorType) { //noinspection unchecked super( entityDescriptor, (DiscriminatorType) incomingDiscriminatorType, (BasicType) incomingDiscriminatorType.getUnderlyingJdbcMapping() ); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddableDiscriminatorValueDetailsImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddableDiscriminatorValueDetailsImpl.java new file mode 100644 index 0000000000..5f19e2cfb8 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddableDiscriminatorValueDetailsImpl.java @@ -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.metamodel.mapping.internal; + +import org.hibernate.metamodel.mapping.DiscriminatorValueDetails; +import org.hibernate.metamodel.mapping.EmbeddableDiscriminatorConverter; +import org.hibernate.metamodel.mapping.EmbeddableDiscriminatorMapping; +import org.hibernate.metamodel.mapping.EntityMappingType; + +/** + * Implementation of {@link DiscriminatorValueDetails} used for embeddable inheritance. + * + * @author Marco Belladelli + * @see EmbeddableDiscriminatorConverter + * @see EmbeddableDiscriminatorMapping + */ +public class EmbeddableDiscriminatorValueDetailsImpl implements DiscriminatorValueDetails { + final Object value; + final String embeddableClassName; + + public EmbeddableDiscriminatorValueDetailsImpl(Object value, String embeddableClassName) { + this.value = value; + this.embeddableClassName = embeddableClassName; + } + + @Override + public Object getValue() { + return value; + } + + @Override + public String getIndicatedEntityName() { + return embeddableClassName; + } + + @Override + public EntityMappingType getIndicatedEntity() { + throw new UnsupportedOperationException(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddableMappingTypeImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddableMappingTypeImpl.java index 343f9f9b36..452182f7b7 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddableMappingTypeImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddableMappingTypeImpl.java @@ -7,11 +7,16 @@ package org.hibernate.metamodel.mapping.internal; import java.io.Serializable; +import java.util.HashMap; +import java.util.HashSet; import java.util.Locale; +import java.util.Map; +import java.util.Set; import java.util.function.Function; import org.hibernate.MappingException; import org.hibernate.SharedSessionContract; +import org.hibernate.WrongClassException; import org.hibernate.cfg.Environment; import org.hibernate.dialect.Dialect; import org.hibernate.dialect.aggregate.AggregateSupport; @@ -28,15 +33,20 @@ import org.hibernate.mapping.BasicValue; import org.hibernate.mapping.Column; import org.hibernate.mapping.Component; import org.hibernate.mapping.DependantValue; +import org.hibernate.mapping.Formula; import org.hibernate.mapping.Property; import org.hibernate.mapping.Selectable; import org.hibernate.mapping.Value; import org.hibernate.metamodel.mapping.AttributeMapping; +import org.hibernate.metamodel.mapping.DiscriminatorConverter; +import org.hibernate.metamodel.mapping.DiscriminatorType; +import org.hibernate.metamodel.mapping.EmbeddableDiscriminatorConverter; +import org.hibernate.metamodel.mapping.EmbeddableDiscriminatorMapping; import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; +import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.JdbcMapping; -import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.mapping.SelectableConsumer; import org.hibernate.metamodel.mapping.SelectableMapping; import org.hibernate.metamodel.mapping.SelectableMappings; @@ -44,7 +54,9 @@ import org.hibernate.metamodel.mapping.SelectablePath; import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.metamodel.spi.EmbeddableRepresentationStrategy; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; +import org.hibernate.persister.entity.DiscriminatorHelper; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.property.access.spi.Getter; import org.hibernate.property.access.spi.PropertyAccess; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.ast.tree.from.TableGroup; @@ -64,12 +76,15 @@ import org.hibernate.type.descriptor.java.BasicPluralJavaType; import org.hibernate.type.descriptor.java.ImmutableMutabilityPlan; import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.java.MutabilityPlan; +import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry; import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; import org.hibernate.type.descriptor.jdbc.JdbcTypeConstructor; import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry; import org.hibernate.type.spi.CompositeTypeImplementor; import org.hibernate.type.spi.TypeConfiguration; +import static org.hibernate.persister.entity.DiscriminatorHelper.getDiscriminatorType; + import static org.hibernate.type.SqlTypes.JSON; import static org.hibernate.type.SqlTypes.JSON_ARRAY; import static org.hibernate.type.SqlTypes.SQLXML; @@ -155,6 +170,8 @@ public class EmbeddableMappingTypeImpl extends AbstractEmbeddableMapping impleme private final EmbeddableRepresentationStrategy representationStrategy; private final EmbeddableValuedModelPart valueMapping; + private final EmbeddableDiscriminatorMapping discriminatorMapping; + private final Map> declaredAttributesBySubclass; private final boolean createEmptyCompositesEnabled; private final SelectableMapping aggregateMapping; @@ -175,6 +192,8 @@ public class EmbeddableMappingTypeImpl extends AbstractEmbeddableMapping impleme this.embeddableJtd = representationStrategy.getMappedJavaType(); this.valueMapping = embeddedPartBuilder.apply( this ); + this.discriminatorMapping = generateDiscriminatorMapping( bootDescriptor, creationContext ); + this.declaredAttributesBySubclass = bootDescriptor.isPolymorphic() ? new HashMap<>() : null; this.createEmptyCompositesEnabled = ConfigurationHelper.getBoolean( Environment.CREATE_EMPTY_COMPOSITES_ENABLED, @@ -317,6 +336,8 @@ public class EmbeddableMappingTypeImpl extends AbstractEmbeddableMapping impleme this.embeddableJtd = inverseMappingType.getJavaType(); this.representationStrategy = inverseMappingType.getRepresentationStrategy(); this.valueMapping = valueMapping; + this.discriminatorMapping = null; + this.declaredAttributesBySubclass = null; this.createEmptyCompositesEnabled = inverseMappingType.isCreateEmptyCompositesEnabled(); this.aggregateMapping = null; this.aggregateMappingRequiresColumnWriter = false; @@ -400,7 +421,7 @@ public class EmbeddableMappingTypeImpl extends AbstractEmbeddableMapping impleme // Reset the attribute mappings that were added in previous attempts attributeMappings.clear(); - for ( Property bootPropertyDescriptor : bootDescriptor.getProperties() ) { + for ( final Property bootPropertyDescriptor : bootDescriptor.getProperties() ) { final AttributeMapping attributeMapping; final Type subtype = subtypes[attributeIndex]; @@ -594,7 +615,7 @@ public class EmbeddableMappingTypeImpl extends AbstractEmbeddableMapping impleme this, entityPersister, (EntityType) subtype, - getRepresentationStrategy().resolvePropertyAccess( bootPropertyDescriptor ), + representationStrategy.resolvePropertyAccess( bootPropertyDescriptor ), compositeType.getCascadeStyle( attributeIndex ), creationProcess ); @@ -611,6 +632,18 @@ public class EmbeddableMappingTypeImpl extends AbstractEmbeddableMapping impleme ); } + if ( isPolymorphic() ) { + final String declaringClass = bootDescriptor.getPropertyDeclaringClass( bootPropertyDescriptor ); + for ( final String subclass : bootDescriptor.getDiscriminatorValues().values() ) { + if ( isDefinedInClassOrSuperclass( bootDescriptor, declaringClass, subclass ) ) { + declaredAttributesBySubclass.computeIfAbsent( + subclass, + k -> new HashSet<>() + ).add( attributeMapping ); + } + } + } + addAttribute( attributeMapping ); attributeIndex++; @@ -618,13 +651,23 @@ public class EmbeddableMappingTypeImpl extends AbstractEmbeddableMapping impleme // We need the attribute mapping types to finish initialization first before we can build the column mappings creationProcess.registerInitializationCallback( - "EmbeddableMappingType(" + getEmbeddedValueMapping().getNavigableRole().getFullPath() + ")#initColumnMappings", + "EmbeddableMappingType(" + valueMapping.getNavigableRole().getFullPath() + ")#initColumnMappings", this::initColumnMappings ); return true; } + private boolean isDefinedInClassOrSuperclass(Component bootDescriptor, String declaringClass, String subclass) { + while ( subclass != null ) { + if ( declaringClass.equals( subclass ) ) { + return true; + } + subclass = bootDescriptor.getSuperclass( subclass ); + } + return false; + } + private static MutabilityPlan getMutabilityPlan(boolean updateable) { if ( updateable ) { return new MutabilityPlan<>() { @@ -654,12 +697,91 @@ public class EmbeddableMappingTypeImpl extends AbstractEmbeddableMapping impleme } } + private EmbeddableDiscriminatorMapping generateDiscriminatorMapping( + Component bootDescriptor, + RuntimeModelCreationContext creationContext) { + final Value discriminator = bootDescriptor.getDiscriminator(); + if ( discriminator == null ) { + return null; + } + final Selectable selectable = discriminator.getSelectables().get( 0 ); + final String discriminatorColumnExpression; + final String columnDefinition; + final String name; + final Long length; + final Integer precision; + final Integer scale; + final boolean isFormula = discriminator.hasFormula(); + if ( isFormula ) { + final Formula formula = (Formula) selectable; + discriminatorColumnExpression = name = formula.getTemplate( + creationContext.getDialect(), + creationContext.getTypeConfiguration(), + creationContext.getFunctionRegistry() + ); + columnDefinition = null; + length = null; + precision = null; + scale = null; + } + else { + final Column column = discriminator.getColumns().get( 0 ); + assert column != null : "Embeddable discriminators require a column"; + discriminatorColumnExpression = column.getReadExpr( creationContext.getDialect() ); + columnDefinition = column.getSqlType(); + name = column.getName(); + length = column.getLength(); + precision = column.getPrecision(); + scale = column.getScale(); + } + + final DiscriminatorType discriminatorType = buildDiscriminatorType( + bootDescriptor, + creationContext + ); + + return new ExplicitColumnDiscriminatorMappingImpl( + this, + name, + bootDescriptor.getTable().getName(), + discriminatorColumnExpression, + isFormula, + true, + true, + columnDefinition, + selectable.getCustomReadExpression(), + length, + precision, + scale, + discriminatorType + ); + } + + private DiscriminatorType buildDiscriminatorType( + Component bootDescriptor, + RuntimeModelCreationContext creationContext) { + final JavaTypeRegistry javaTypeRegistry = creationContext.getSessionFactory().getTypeConfiguration().getJavaTypeRegistry(); + final JavaType domainJavaType = javaTypeRegistry.resolveDescriptor( String.class ); + final BasicType discriminatorType = getDiscriminatorType( bootDescriptor ); + final DiscriminatorConverter converter = EmbeddableDiscriminatorConverter.fromValueMappings( + getNavigableRole().append( EntityDiscriminatorMapping.DISCRIMINATOR_ROLE_NAME ), + domainJavaType, + discriminatorType, + bootDescriptor.getDiscriminatorValues() + ); + return new DiscriminatorTypeImpl<>( discriminatorType, converter ); + } public EmbeddableValuedModelPart getEmbeddedValueMapping() { return valueMapping; } + @Override + public EmbeddableDiscriminatorMapping getDiscriminatorMapping() { + return discriminatorMapping; + } + @Override public JavaType getMappedJavaType() { return embeddableJtd; @@ -693,6 +815,73 @@ public class EmbeddableMappingTypeImpl extends AbstractEmbeddableMapping impleme ); } + @Override + public boolean declaresAttribute(String embeddableClassName, AttributeMapping attributeMapping) { + if ( declaredAttributesBySubclass == null ) { + return true; + } + final Set declaredAttributes = declaredAttributesBySubclass.get( embeddableClassName ); + return declaredAttributes != null && declaredAttributes.contains( attributeMapping ); + } + + @Override + public Object getValue(Object instance, int position) { + final AttributeMapping attributeMapping = getAttributeMapping( position ); + if ( declaresAttribute( instance.getClass().getName(), attributeMapping ) ) { + return attributeMapping.getValue( instance ); + } + return null; + } + + @Override + protected Object[] getAttributeValues(Object compositeInstance) { + if ( !isPolymorphic() ) { + return super.getAttributeValues( compositeInstance ); + } + else { + final int numberOfAttributes = getNumberOfAttributeMappings(); + final Object[] results = new Object[numberOfAttributes + 1]; + final String compositeClassName = compositeInstance.getClass().getName(); + int i = 0; + for ( ; i < numberOfAttributes; i++ ) { + final AttributeMapping attributeMapping = getAttributeMapping( i ); + if ( declaresAttribute( compositeClassName, attributeMapping ) ) { + final Getter getter = attributeMapping.getAttributeMetadata() + .getPropertyAccess() + .getGetter(); + results[i] = getter.get( compositeInstance ); + } + else { + results[i] = null; + } + } + results[i] = compositeInstance.getClass().getName(); + return results; + } + } + + @Override + protected void setAttributeValues(Object component, Object[] values) { + if ( !isPolymorphic() ) { + super.setAttributeValues( component, values ); + } + else { + final String compositeClassName = component.getClass().getName(); + for ( int i = 0; i < getNumberOfAttributeMappings(); i++ ) { + final AttributeMapping attributeMapping = getAttributeMapping( i ); + if ( declaresAttribute( compositeClassName, attributeMapping ) ) { + attributeMapping.getPropertyAccess().getSetter().set( component, values[i] ); + } + else if ( values[i] != null ) { + throw new IllegalArgumentException( String.format( + "Unexpected non-null value for embeddable subtype '%s'", + compositeClassName + ) ); + } + } + } + } + @Override public int breakDownJdbcValues( Object domainValue, @@ -705,9 +894,9 @@ public class EmbeddableMappingTypeImpl extends AbstractEmbeddableMapping impleme int span = 0; if ( domainValue instanceof Object[] ) { final Object[] values = (Object[]) domainValue; - assert values.length == size; - - for ( int i = 0; i < size; i++ ) { + assert values.length == size + ( isPolymorphic() ? 1 : 0 ); + int i = 0; + for ( ; i < size; i++ ) { final AttributeMapping attributeMapping = attributeMappings.get( i ); if ( !attributeMapping.isPluralAttributeMapping() ) { final Object attributeValue = values[i]; @@ -721,12 +910,16 @@ public class EmbeddableMappingTypeImpl extends AbstractEmbeddableMapping impleme ); } } + if ( isPolymorphic() ) { + span += discriminatorMapping.breakDownJdbcValues( values[i], offset + span, x, y, valueConsumer, session ); + } } else { + final String compositeClassName = domainValue == null ? null : domainValue.getClass().getName(); for ( int i = 0; i < size; i++ ) { final AttributeMapping attributeMapping = attributeMappings.get( i ); if ( !attributeMapping.isPluralAttributeMapping() ) { - final Object attributeValue = domainValue == null + final Object attributeValue = domainValue == null || !declaresAttribute( compositeClassName, attributeMapping ) ? null : attributeMapping.getPropertyAccess().getGetter().get( domainValue ); span += attributeMapping.breakDownJdbcValues( @@ -739,6 +932,10 @@ public class EmbeddableMappingTypeImpl extends AbstractEmbeddableMapping impleme ); } } + if ( isPolymorphic() ) { + final Object d = domainValue == null ? null : discriminatorMapping.getDiscriminatorValue( compositeClassName ); + span += discriminatorMapping.breakDownJdbcValues( d, offset + span, x, y, valueConsumer, session ); + } } return span; } @@ -755,27 +952,36 @@ public class EmbeddableMappingTypeImpl extends AbstractEmbeddableMapping impleme valueConsumer.consume( offset, x, y, domainValue, aggregateMapping ); return 1; } + final int size = attributeMappings.size(); int span = 0; if ( domainValue instanceof Object[] ) { final Object[] values = (Object[]) domainValue; - assert values.length == attributeMappings.size(); - - for ( int i = 0; i < attributeMappings.size(); i++ ) { + assert values.length == size + ( isPolymorphic() ? 1 : 0 ); + int i = 0; + for ( ; i < size; i++ ) { final AttributeMapping attributeMapping = attributeMappings.get( i ); final Object attributeValue = values[ i ]; span += attributeMapping.decompose( attributeValue, offset + span, x, y, valueConsumer, session ); } + if ( isPolymorphic() ) { + span += discriminatorMapping.decompose( values[i], offset + span, x, y, valueConsumer, session ); + } } else { - for ( int i = 0; i < attributeMappings.size(); i++ ) { + final String compositeClassName = domainValue == null ? null : domainValue.getClass().getName(); + for ( int i = 0; i < size; i++ ) { final AttributeMapping attributeMapping = attributeMappings.get( i ); - if ( !(attributeMapping instanceof PluralAttributeMapping )) { - final Object attributeValue = domainValue == null + if ( !attributeMapping.isPluralAttributeMapping() ) { + final Object attributeValue = domainValue == null || !declaresAttribute( compositeClassName, attributeMapping ) ? null : attributeMapping.getPropertyAccess().getGetter().get( domainValue ); span += attributeMapping.decompose( attributeValue, offset + span, x, y, valueConsumer, session ); } } + if ( isPolymorphic() ) { + final Object d = domainValue == null ? null : discriminatorMapping.getDiscriminatorValue( compositeClassName ); + span += discriminatorMapping.decompose( d, offset + span, x, y, valueConsumer, session ); + } } return span; } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ExplicitColumnDiscriminatorMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ExplicitColumnDiscriminatorMappingImpl.java index 5023c48bdb..a8c13a8f8a 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ExplicitColumnDiscriminatorMappingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ExplicitColumnDiscriminatorMappingImpl.java @@ -7,10 +7,10 @@ package org.hibernate.metamodel.mapping.internal; import org.hibernate.metamodel.mapping.DiscriminatorConverter; -import org.hibernate.metamodel.mapping.EntityMappingType; -import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.DiscriminatorType; -import org.hibernate.metamodel.mapping.MappingType; +import org.hibernate.metamodel.mapping.EmbeddableDiscriminatorMapping; +import org.hibernate.metamodel.mapping.JdbcMapping; +import org.hibernate.metamodel.mapping.ManagedMappingType; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.ast.spi.SqlAstCreationState; import org.hibernate.sql.ast.spi.SqlExpressionResolver; @@ -25,43 +25,53 @@ import static org.hibernate.sql.ast.spi.SqlExpressionResolver.createColumnRefere /** * @author Steve Ebersole */ -public class ExplicitColumnDiscriminatorMappingImpl extends AbstractDiscriminatorMapping { +public class ExplicitColumnDiscriminatorMappingImpl extends AbstractDiscriminatorMapping + implements EmbeddableDiscriminatorMapping { + private final String name; private final String tableExpression; private final String columnName; private final String columnFormula; private final boolean isPhysical; + private final boolean isUpdateable; private final String columnDefinition; + private final String customReadExpression; private final Long length; private final Integer precision; private final Integer scale; public ExplicitColumnDiscriminatorMappingImpl( - EntityMappingType entityDescriptor, + ManagedMappingType mappingType, + String name, String tableExpression, String columnExpression, boolean isFormula, boolean isPhysical, + boolean isUpdateable, String columnDefinition, + String customReadExpression, Long length, Integer precision, Integer scale, - DiscriminatorType discriminatorType, - MappingModelCreationProcess creationProcess) { + DiscriminatorType discriminatorType) { //noinspection unchecked - super( entityDescriptor, (DiscriminatorType) discriminatorType, (BasicType) discriminatorType.getUnderlyingJdbcMapping() ); + super( mappingType, (DiscriminatorType) discriminatorType, (BasicType) discriminatorType.getUnderlyingJdbcMapping() ); + this.name = name; this.tableExpression = tableExpression; this.isPhysical = isPhysical; this.columnDefinition = columnDefinition; + this.customReadExpression = customReadExpression; this.length = length; this.precision = precision; this.scale = scale; if ( isFormula ) { columnName = null; columnFormula = columnExpression; + this.isUpdateable = false; } else { columnName = columnExpression; columnFormula = null; + this.isUpdateable = isUpdateable; } } @@ -99,6 +109,11 @@ public class ExplicitColumnDiscriminatorMappingImpl extends AbstractDiscriminato return tableExpression; } + @Override + public String getSelectableName() { + return name; + } + @Override public String getSelectionExpression() { return columnName == null ? columnFormula : columnName; @@ -106,7 +121,7 @@ public class ExplicitColumnDiscriminatorMappingImpl extends AbstractDiscriminato @Override public String getCustomReadExpression() { - return null; + return customReadExpression; } @Override @@ -156,7 +171,7 @@ public class ExplicitColumnDiscriminatorMappingImpl extends AbstractDiscriminato @Override public boolean isUpdateable() { - return false; + return isUpdateable; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/IdClassRepresentationStrategy.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/IdClassRepresentationStrategy.java index 5d99c50f59..eabe57936f 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/IdClassRepresentationStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/IdClassRepresentationStrategy.java @@ -33,7 +33,7 @@ public class IdClassRepresentationStrategy implements EmbeddableRepresentationSt this.idClassType = idClassEmbeddable.getMappedJavaType(); this.instantiator = isRecord( idClassType.getJavaTypeClass() ) ? new EmbeddableInstantiatorRecordStandard( idClassType.getJavaTypeClass() ) : - new EmbeddableInstantiatorPojoStandard( idClassType, () -> idClassEmbeddable ); + new EmbeddableInstantiatorPojoStandard( idClassType.getJavaTypeClass(), () -> idClassEmbeddable ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EmbeddableTypeImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EmbeddableTypeImpl.java index 21b746cce4..2a286dd395 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EmbeddableTypeImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EmbeddableTypeImpl.java @@ -11,6 +11,7 @@ import java.io.Serializable; import org.hibernate.metamodel.model.domain.AbstractManagedType; import org.hibernate.metamodel.model.domain.DomainType; import org.hibernate.metamodel.model.domain.EmbeddableDomainType; +import org.hibernate.metamodel.model.domain.ManagedDomainType; import org.hibernate.metamodel.model.domain.spi.JpaMetamodelImplementor; import org.hibernate.type.descriptor.java.JavaType; @@ -25,14 +26,21 @@ import jakarta.persistence.metamodel.SingularAttribute; public class EmbeddableTypeImpl extends AbstractManagedType implements EmbeddableDomainType, Serializable { - private final boolean isDynamic; public EmbeddableTypeImpl( JavaType javaType, boolean isDynamic, JpaMetamodelImplementor domainMetamodel) { - super( javaType.getTypeName(), javaType, null, domainMetamodel ); + this( javaType, null, isDynamic, domainMetamodel ); + } + + public EmbeddableTypeImpl( + JavaType javaType, + ManagedDomainType superType, + boolean isDynamic, + JpaMetamodelImplementor domainMetamodel) { + super( javaType.getTypeName(), javaType, superType, domainMetamodel ); this.isDynamic = isDynamic; } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EmbeddedSqmPathSource.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EmbeddedSqmPathSource.java index b0f79f2494..5414ca92ae 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EmbeddedSqmPathSource.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EmbeddedSqmPathSource.java @@ -7,6 +7,8 @@ package org.hibernate.metamodel.model.domain.internal; import org.hibernate.metamodel.model.domain.EmbeddableDomainType; +import org.hibernate.metamodel.model.domain.PersistentAttribute; +import org.hibernate.metamodel.model.domain.spi.JpaMetamodelImplementor; import org.hibernate.query.sqm.SqmJoinable; import org.hibernate.query.sqm.SqmPathSource; import org.hibernate.query.sqm.tree.domain.SqmEmbeddedValuedSimplePath; @@ -35,6 +37,16 @@ public class EmbeddedSqmPathSource return (EmbeddableDomainType) super.getSqmPathType(); } + @Override + public SqmPathSource findSubPathSource(String name, JpaMetamodelImplementor metamodel) { + final PersistentAttribute attribute = getSqmPathType().findAttribute( name ); + if ( attribute != null ) { + return (SqmPathSource) attribute; + } + + return (SqmPathSource) getSqmPathType().findSubTypesAttribute( name ); + } + @Override public SqmPathSource findSubPathSource(String name) { return (SqmPathSource) getSqmPathType().findAttribute( name ); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/spi/EmbeddableRepresentationStrategy.java b/hibernate-core/src/main/java/org/hibernate/metamodel/spi/EmbeddableRepresentationStrategy.java index 880751feac..ad7d23d827 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/spi/EmbeddableRepresentationStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/spi/EmbeddableRepresentationStrategy.java @@ -21,6 +21,14 @@ public interface EmbeddableRepresentationStrategy extends ManagedTypeRepresentat */ EmbeddableInstantiator getInstantiator(); + default EmbeddableInstantiator getInstantiatorForDiscriminator(Object discriminatorValue) { + return getInstantiator(); + } + + default EmbeddableInstantiator getInstantiatorForClass(String className) { + return getInstantiator(); + } + /** * The reflection optimizer to use for this embeddable. * diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index 1a31a29778..96f452142c 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -5202,7 +5202,7 @@ public abstract class AbstractEntityPersister ); } - discriminatorMapping = generateDiscriminatorMapping( bootEntityDescriptor, creationProcess ); + discriminatorMapping = generateDiscriminatorMapping( bootEntityDescriptor ); softDeleteMapping = resolveSoftDeleteMapping( this, bootEntityDescriptor, getIdentifierTableName(), creationProcess ); if ( softDeleteMapping != null ) { @@ -5329,9 +5329,7 @@ public abstract class AbstractEntityPersister return getDiscriminatorFormulaTemplate() == null; } - protected EntityDiscriminatorMapping generateDiscriminatorMapping( - PersistentClass bootEntityDescriptor, - MappingModelCreationProcess modelCreationProcess) { + protected EntityDiscriminatorMapping generateDiscriminatorMapping(PersistentClass bootEntityDescriptor) { if ( getDiscriminatorType() == null ) { return null; } @@ -5368,13 +5366,18 @@ public abstract class AbstractEntityPersister } return new ExplicitColumnDiscriminatorMappingImpl( this, + discriminatorColumnExpression, getTableName(), discriminatorColumnExpression, getDiscriminatorFormulaTemplate() != null, isPhysicalDiscriminator(), - columnDefinition, length, precision, scale, - (DiscriminatorType) getTypeDiscriminatorMetadata().getResolutionType(), - modelCreationProcess + false, + columnDefinition, + null, + length, + precision, + scale, + (DiscriminatorType) getTypeDiscriminatorMetadata().getResolutionType() ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/DiscriminatorHelper.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/DiscriminatorHelper.java index 8b1ee625c9..bb0470f286 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/DiscriminatorHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/DiscriminatorHelper.java @@ -10,6 +10,7 @@ import org.hibernate.Internal; import org.hibernate.MappingException; import org.hibernate.dialect.Dialect; import org.hibernate.internal.util.MarkerObject; +import org.hibernate.mapping.Component; import org.hibernate.mapping.PersistentClass; import org.hibernate.sql.InFragment; import org.hibernate.type.BasicType; @@ -41,6 +42,16 @@ public class DiscriminatorHelper { } } + public static BasicType getDiscriminatorType(Component component) { + Type discriminatorType = component.getDiscriminator().getType(); + if ( discriminatorType instanceof BasicType ) { + return (BasicType) discriminatorType; + } + else { + throw new MappingException( "Illegal discriminator type: " + discriminatorType.getName() ); + } + } + static String getDiscriminatorSQLValue(PersistentClass persistentClass, Dialect dialect) { if ( persistentClass.isDiscriminatorValueNull() ) { return InFragment.NULL; diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java index 5f305c4cf2..d7e88fe0c5 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java @@ -1205,9 +1205,7 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister { } @Override - protected EntityDiscriminatorMapping generateDiscriminatorMapping( - PersistentClass bootEntityDescriptor, - MappingModelCreationProcess modelCreationProcess) { + protected EntityDiscriminatorMapping generateDiscriminatorMapping(PersistentClass bootEntityDescriptor) { final EntityMappingType superMappingType = getSuperMappingType(); if ( superMappingType != null ) { return superMappingType.getDiscriminatorMapping(); @@ -1218,7 +1216,7 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister { // even though this is a JOINED hierarchy the user has defined an // explicit discriminator column - so we can use the normal // discriminator mapping - return super.generateDiscriminatorMapping( bootEntityDescriptor, modelCreationProcess ); + return super.generateDiscriminatorMapping( bootEntityDescriptor ); } else { // otherwise, we need to use the case approach @@ -1229,8 +1227,7 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister { notNullColumnNames, discriminatorValues, discriminatorAbstract, - resolveDiscriminatorType(), - modelCreationProcess + resolveDiscriminatorType() ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/UnionSubclassEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/UnionSubclassEntityPersister.java index 01420cab10..b45a3fa9bd 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/UnionSubclassEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/UnionSubclassEntityPersister.java @@ -438,10 +438,8 @@ public class UnionSubclassEntityPersister extends AbstractEntityPersister { } @Override - protected EntityDiscriminatorMapping generateDiscriminatorMapping( - PersistentClass bootEntityDescriptor, - MappingModelCreationProcess modelCreationProcess) { - return hasSubclasses() ? super.generateDiscriminatorMapping( bootEntityDescriptor, modelCreationProcess ) : null; + protected EntityDiscriminatorMapping generateDiscriminatorMapping(PersistentClass bootEntityDescriptor) { + return hasSubclasses() ? super.generateDiscriminatorMapping( bootEntityDescriptor ) : null; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/DomainResultCreationStateImpl.java b/hibernate-core/src/main/java/org/hibernate/query/results/DomainResultCreationStateImpl.java index 59e5dae4f2..fb2da99f59 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/DomainResultCreationStateImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/DomainResultCreationStateImpl.java @@ -362,12 +362,12 @@ public class DomainResultCreationStateImpl } @Override - public ImmutableFetchList visitNestedFetches(FetchParent fetchParent) { + public R withNestedFetchParent(FetchParent fetchParent, Function action) { final FetchParent oldNestingFetchParent = this.nestingFetchParent; this.nestingFetchParent = fetchParent; - final ImmutableFetchList fetches = visitFetches( fetchParent ); + final R result = action.apply( fetchParent ); this.nestingFetchParent = oldNestingFetchParent; - return fetches; + return result; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java index 21168b9d8a..5cb94f22bd 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java @@ -28,7 +28,6 @@ import org.hibernate.id.BulkInsertionCapableIdentifierGenerator; import org.hibernate.id.CompositeNestedGeneratedValueGenerator; import org.hibernate.id.OptimizableGenerator; import org.hibernate.id.enhanced.Optimizer; -import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.internal.util.collections.Stack; import org.hibernate.internal.util.collections.StandardStack; import org.hibernate.loader.MultipleBagFetchException; @@ -60,7 +59,6 @@ import org.hibernate.metamodel.mapping.MappingType; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.ModelPartContainer; import org.hibernate.metamodel.mapping.PluralAttributeMapping; -import org.hibernate.metamodel.mapping.Restrictable; import org.hibernate.metamodel.mapping.SelectableMapping; import org.hibernate.metamodel.mapping.SelectableMappings; import org.hibernate.metamodel.mapping.SoftDeleteMapping; @@ -356,7 +354,6 @@ import org.hibernate.sql.ast.tree.predicate.LikePredicate; import org.hibernate.sql.ast.tree.predicate.NegatedPredicate; import org.hibernate.sql.ast.tree.predicate.NullnessPredicate; import org.hibernate.sql.ast.tree.predicate.Predicate; -import org.hibernate.sql.ast.tree.predicate.PredicateCollector; import org.hibernate.sql.ast.tree.predicate.SelfRenderingPredicate; import org.hibernate.sql.ast.tree.predicate.ThruthnessPredicate; import org.hibernate.sql.ast.tree.select.QueryGroup; @@ -8350,13 +8347,13 @@ public abstract class BaseSqmToSqlAstConverter extends Base } @Override - public ImmutableFetchList visitNestedFetches(FetchParent fetchParent) { + public R withNestedFetchParent(FetchParent fetchParent, Function action) { final SqlAstQueryPartProcessingStateImpl processingState = (SqlAstQueryPartProcessingStateImpl) getCurrentProcessingState(); final FetchParent nestingFetchParent = processingState.getNestingFetchParent(); processingState.setNestingFetchParent( fetchParent ); - final ImmutableFetchList fetches = visitFetches( fetchParent ); + final R result = action.apply( fetchParent ); processingState.setNestingFetchParent( nestingFetchParent ); - return fetches; + return result; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/DomainResultCreationState.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/DomainResultCreationState.java index 625bc22b8a..193e377c4a 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/DomainResultCreationState.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/DomainResultCreationState.java @@ -6,9 +6,14 @@ */ package org.hibernate.sql.results.graph; +import java.util.function.Function; +import java.util.function.Supplier; + import org.hibernate.Incubating; import org.hibernate.engine.FetchTiming; import org.hibernate.metamodel.mapping.AssociationKey; +import org.hibernate.metamodel.mapping.EmbeddableDiscriminatorMapping; +import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping; import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.EntityMappingType; @@ -18,7 +23,9 @@ import org.hibernate.spi.EntityIdentifierNavigablePath; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.ast.spi.SqlAliasBaseManager; import org.hibernate.sql.ast.spi.SqlAstCreationState; +import org.hibernate.sql.ast.spi.SqlAstQueryPartProcessingState; import org.hibernate.sql.results.graph.basic.BasicFetch; +import org.hibernate.sql.results.graph.embeddable.EmbeddableResultGraphNode; import org.hibernate.sql.results.graph.entity.EntityResultGraphNode; import org.hibernate.sql.results.graph.internal.ImmutableFetchList; @@ -117,6 +124,30 @@ public interface DomainResultCreationState { } } + default BasicFetch visitEmbeddableDiscriminatorFetch(EmbeddableResultGraphNode fetchParent, boolean nested) { + final EmbeddableMappingType embeddableType = fetchParent.getReferencedMappingType(); + final EmbeddableDiscriminatorMapping discriminatorMapping = embeddableType.getDiscriminatorMapping(); + if ( discriminatorMapping != null ) { + final Function> fetchSupplier = fp -> discriminatorMapping.generateFetch( + fp, + fp.getNavigablePath().append( EntityDiscriminatorMapping.DISCRIMINATOR_ROLE_NAME ), + FetchTiming.IMMEDIATE, + true, + null, + this + ); + if ( nested ) { + return withNestedFetchParent( fetchParent, fetchSupplier ); + } + else { + return fetchSupplier.apply( fetchParent ); + } + } + else { + return null; + } + } + /** * Visit fetches for the given parent. * @@ -150,7 +181,11 @@ public interface DomainResultCreationState { */ ImmutableFetchList visitFetches(FetchParent fetchParent); - ImmutableFetchList visitNestedFetches(FetchParent fetchParent); + default ImmutableFetchList visitNestedFetches(FetchParent fetchParent) { + return withNestedFetchParent( fetchParent, this::visitFetches ); + } + + R withNestedFetchParent(FetchParent fetchParent, Function action); boolean isResolvingCircularFetch(); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/FetchParentAccess.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/FetchParentAccess.java index 89d5fc5881..85974959bb 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/FetchParentAccess.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/FetchParentAccess.java @@ -8,11 +8,14 @@ package org.hibernate.sql.results.graph; import java.util.function.Consumer; +import org.hibernate.metamodel.mapping.AttributeMapping; +import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.results.graph.collection.internal.AbstractImmediateCollectionInitializer; +import org.hibernate.sql.results.graph.embeddable.EmbeddableInitializer; import org.hibernate.sql.results.graph.entity.EntityInitializer; import org.hibernate.sql.results.jdbc.spi.RowProcessingState; @@ -60,7 +63,7 @@ public interface FetchParentAccess extends Initializer { if ( parentAccess == null || parentAccess.isEntityInitializer() || parentAccess.isCollectionInitializer() - || parentAccess.isEmbeddableInitializer() && parentAccess.isResultInitializer() ) { + || parentAccess.isEmbeddableInitializer() ) { return parentAccess; } return parentAccess.getOwningParent(); @@ -72,8 +75,10 @@ public interface FetchParentAccess extends Initializer { ModelPart modelPart, @Nullable FetchParentAccess parentAccess, @Nullable FetchParentAccess owningParent) { - final EntityInitializer entityInitializer; - if ( owningParent == null || ( entityInitializer = owningParent.asEntityInitializer() ) == null ) { + final EntityInitializer entityInitializer = owningParent != null ? + owningParent.findFirstEntityInitializer() : + null; + if ( entityInitializer == null ) { return null; } @@ -103,14 +108,34 @@ public interface FetchParentAccess extends Initializer { // skipping only depends on whether the collection key is resolvable or not return collectionInitializer.resolveCollectionKey( rowProcessingState ) == null; } - final EntityInitializer entityInitializer = owningParent.asEntityInitializer(); + EntityInitializer entityInitializer = owningParent.asEntityInitializer(); if ( entityInitializer == null ) { - // We can never skip an initializer if it is part of an embeddable domain result, - // because that embeddable always has to be materialized with its full state - assert owningParent.isEmbeddableInitializer() && owningParent.isResultInitializer(); - return false; + final EmbeddableInitializer embeddableInitializer = owningParent.asEmbeddableInitializer(); + assert embeddableInitializer != null; + final EmbeddableMappingType descriptor = embeddableInitializer.getInitializedPart() + .getEmbeddableTypeDescriptor(); + if ( descriptor.isPolymorphic() ) { + // The embeddable is polymorphic, check if the current subtype defines the initialized attribute + final AttributeMapping attribute = getInitializedPart().asAttributeMapping(); + if ( attribute != null ) { + embeddableInitializer.resolveKey( rowProcessingState ); + final String embeddableClassName = descriptor.getDiscriminatorMapping() + .resolveDiscriminatorValue( embeddableInitializer.getDiscriminatorValue() ) + .getIndicatedEntityName(); + if ( !descriptor.declaresAttribute( embeddableClassName, attribute ) ) { + return true; + } + } + } + if ( embeddableInitializer.isResultInitializer() ) { + // We can never skip an initializer if it is part of an embeddable domain result, + // because that embeddable always has to be materialized with its full state + return false; + } + else if ( ( entityInitializer = embeddableInitializer.findFirstEntityInitializer() ) == null ) { + return false; + } } - // We must resolve the key of the parent in order to determine the concrete descriptor entityInitializer.resolveKey( rowProcessingState ); final EntityPersister concreteDescriptor = entityInitializer.getConcreteDescriptor(); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/AbstractEmbeddableInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/AbstractEmbeddableInitializer.java index f4c99ed771..69e83ebf0a 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/AbstractEmbeddableInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/AbstractEmbeddableInitializer.java @@ -9,6 +9,7 @@ package org.hibernate.sql.results.graph.embeddable; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.metamodel.mapping.CollectionPart; +import org.hibernate.metamodel.mapping.EmbeddableDiscriminatorMapping; import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; import org.hibernate.metamodel.mapping.EntityMappingType; @@ -26,6 +27,8 @@ import org.hibernate.sql.results.graph.Fetch; import org.hibernate.sql.results.graph.FetchParentAccess; import org.hibernate.sql.results.graph.Fetchable; import org.hibernate.sql.results.graph.Initializer; +import org.hibernate.sql.results.graph.basic.BasicFetch; +import org.hibernate.sql.results.graph.basic.BasicResultAssembler; import org.hibernate.sql.results.graph.collection.CollectionInitializer; import org.hibernate.sql.results.graph.entity.EntityInitializer; import org.hibernate.sql.results.internal.NullValueAssembler; @@ -51,16 +54,19 @@ public abstract class AbstractEmbeddableInitializer extends AbstractFetchParentA private final SessionFactoryImplementor sessionFactory; protected final DomainResultAssembler[] assemblers; + private final BasicResultAssembler discriminatorAssembler; // per-row state private final Object[] rowState; private State state = State.INITIAL; protected Object compositeInstance; + private Object discriminatorValue; private RowProcessingState wrappedProcessingState; public AbstractEmbeddableInitializer( EmbeddableResultGraphNode resultDescriptor, FetchParentAccess parentAccess, + BasicFetch discriminatorFetch, AssemblerCreationState creationState) { this.navigablePath = resultDescriptor.getNavigablePath(); this.embedded = resultDescriptor.getReferencedMappingContainer(); @@ -77,6 +83,9 @@ public abstract class AbstractEmbeddableInitializer extends AbstractFetchParentA this.createEmptyCompositesEnabled = !isPartOfKey && embeddableTypeDescriptor.isCreateEmptyCompositesEnabled(); this.sessionFactory = creationState.getSqlAstCreationContext().getSessionFactory(); this.assemblers = createAssemblers( resultDescriptor, creationState, embeddableTypeDescriptor ); + discriminatorAssembler = discriminatorFetch != null ? + (BasicResultAssembler) discriminatorFetch.createAssembler( parentAccess, creationState ) : + null; } protected DomainResultAssembler[] createAssemblers( @@ -151,7 +160,16 @@ public abstract class AbstractEmbeddableInitializer extends AbstractFetchParentA @Override public void resolveKey(RowProcessingState processingState) { - // nothing to do + // We need to possibly wrap the processing state if the embeddable is within an aggregate + if ( wrappedProcessingState == null ) { + wrappedProcessingState = wrapProcessingState( processingState ); + } + if ( discriminatorAssembler != null ) { + final EmbeddableDiscriminatorMapping discriminatorMapping = embedded.getEmbeddableTypeDescriptor() + .getDiscriminatorMapping(); + assert discriminatorMapping != null; + discriminatorValue = discriminatorAssembler.extractRawValue( wrappedProcessingState ); + } } @Override @@ -191,10 +209,6 @@ public abstract class AbstractEmbeddableInitializer extends AbstractFetchParentA return; } - // We need to possibly wrap the processing state if the embeddable is within an aggregate - if ( wrappedProcessingState == null ) { - wrappedProcessingState = wrapProcessingState( processingState ); - } extractRowState( wrappedProcessingState ); prepareCompositeInstance( wrappedProcessingState ); if ( state == State.NULL ) { @@ -275,6 +289,7 @@ public abstract class AbstractEmbeddableInitializer extends AbstractFetchParentA if ( compositeInstance == null ) { compositeInstance = createCompositeInstance( + discriminatorValue, navigablePath, sessionFactory ); @@ -334,7 +349,10 @@ public abstract class AbstractEmbeddableInitializer extends AbstractFetchParentA } } - private Object createCompositeInstance(NavigablePath navigablePath, SessionFactoryImplementor sessionFactory) { + private Object createCompositeInstance( + Object discriminatorValue, + NavigablePath navigablePath, + SessionFactoryImplementor sessionFactory) { if ( state == State.NULL ) { // todo (6.0) : should we initialize the composite instance if it has a parent attribute? // if ( !createEmptyCompositesEnabled && embedded.getParentInjectionAttributePropertyAccess() == null ) { @@ -345,7 +363,7 @@ public abstract class AbstractEmbeddableInitializer extends AbstractFetchParentA final Object instance = embedded.getEmbeddableTypeDescriptor() .getRepresentationStrategy() - .getInstantiator() + .getInstantiatorForDiscriminator( discriminatorValue ) .instantiate( this, sessionFactory ); state = State.EXTRACTED; @@ -369,6 +387,11 @@ public abstract class AbstractEmbeddableInitializer extends AbstractFetchParentA return fetchParentAccess.getInitializedInstance(); } + @Override + public Object getDiscriminatorValue() { + return discriminatorValue; + } + private void handleParentInjection() { final PropertyAccess parentInjectionAccess = embedded.getParentInjectionAttributePropertyAccess(); if ( parentInjectionAccess == null ) { @@ -467,6 +490,7 @@ public abstract class AbstractEmbeddableInitializer extends AbstractFetchParentA @Override public void finishUpRow(RowProcessingState rowProcessingState) { compositeInstance = null; + discriminatorValue = null; state = State.INITIAL; wrappedProcessingState = null; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/EmbeddableInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/EmbeddableInitializer.java index 9b533e3ae1..b2a83b6a06 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/EmbeddableInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/EmbeddableInitializer.java @@ -50,4 +50,8 @@ public interface EmbeddableInitializer extends FetchParentAccess { } void resolveState(RowProcessingState rowProcessingState); + + default Object getDiscriminatorValue() { + return null; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/AggregateEmbeddableFetchImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/AggregateEmbeddableFetchImpl.java index 4b91059385..67c9de6bc6 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/AggregateEmbeddableFetchImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/AggregateEmbeddableFetchImpl.java @@ -29,6 +29,7 @@ import org.hibernate.sql.results.graph.FetchParent; import org.hibernate.sql.results.graph.FetchParentAccess; import org.hibernate.sql.results.graph.Fetchable; import org.hibernate.sql.results.graph.InitializerProducer; +import org.hibernate.sql.results.graph.basic.BasicFetch; import org.hibernate.sql.results.graph.embeddable.EmbeddableInitializer; import org.hibernate.sql.results.graph.embeddable.EmbeddableResultGraphNode; import org.hibernate.sql.results.graph.embeddable.EmbeddableValuedFetchable; @@ -49,6 +50,7 @@ public class AggregateEmbeddableFetchImpl extends AbstractFetchParent private final boolean hasTableGroup; private final SqlSelection aggregateSelection; private final EmbeddableMappingType fetchContainer; + private final BasicFetch discriminatorFetch; public AggregateEmbeddableFetchImpl( NavigablePath navigablePath, @@ -99,6 +101,7 @@ public class AggregateEmbeddableFetchImpl extends AbstractFetchParent fetchParent, typeConfiguration ); + this.discriminatorFetch = creationState.visitEmbeddableDiscriminatorFetch( this, true ); resetFetches( creationState.visitNestedFetches( this ) ); } @@ -173,6 +176,7 @@ public class AggregateEmbeddableFetchImpl extends AbstractFetchParent return new AggregateEmbeddableFetchInitializer( parentAccess, this, + discriminatorFetch, creationState, aggregateSelection ); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/AggregateEmbeddableFetchInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/AggregateEmbeddableFetchInitializer.java index e3e2543a52..bc85995afa 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/AggregateEmbeddableFetchInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/AggregateEmbeddableFetchInitializer.java @@ -11,6 +11,7 @@ import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.results.graph.AssemblerCreationState; import org.hibernate.sql.results.graph.DomainResultAssembler; import org.hibernate.sql.results.graph.FetchParentAccess; +import org.hibernate.sql.results.graph.basic.BasicFetch; import org.hibernate.sql.results.graph.embeddable.AbstractEmbeddableInitializer; import org.hibernate.sql.results.graph.embeddable.EmbeddableResultGraphNode; import org.hibernate.sql.results.jdbc.spi.RowProcessingState; @@ -28,8 +29,10 @@ public class AggregateEmbeddableFetchInitializer extends AbstractEmbeddableIniti public AggregateEmbeddableFetchInitializer( FetchParentAccess fetchParentAccess, EmbeddableResultGraphNode resultDescriptor, - AssemblerCreationState creationState, SqlSelection structSelection) { - super( resultDescriptor, fetchParentAccess, creationState ); + BasicFetch discriminatorFetch, + AssemblerCreationState creationState, + SqlSelection structSelection) { + super( resultDescriptor, fetchParentAccess, discriminatorFetch, creationState ); this.aggregateValuesArrayPositions = AggregateEmbeddableInitializer.determineAggregateValuesArrayPositions( fetchParentAccess, structSelection diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/AggregateEmbeddableResultImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/AggregateEmbeddableResultImpl.java index 3ed1103387..1159cc4237 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/AggregateEmbeddableResultImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/AggregateEmbeddableResultImpl.java @@ -30,6 +30,7 @@ import org.hibernate.sql.results.graph.FetchParent; import org.hibernate.sql.results.graph.FetchParentAccess; import org.hibernate.sql.results.graph.Initializer; import org.hibernate.sql.results.graph.InitializerProducer; +import org.hibernate.sql.results.graph.basic.BasicFetch; import org.hibernate.sql.results.graph.embeddable.EmbeddableResult; import org.hibernate.sql.results.graph.embeddable.EmbeddableResultGraphNode; import org.hibernate.sql.results.graph.internal.ImmutableFetchList; @@ -51,6 +52,7 @@ public class AggregateEmbeddableResultImpl extends AbstractFetchParent implem private final boolean containsAnyNonScalars; private final SqlSelection aggregateSelection; private final EmbeddableMappingType fetchContainer; + private final BasicFetch discriminatorFetch; public AggregateEmbeddableResultImpl( NavigablePath navigablePath, @@ -104,6 +106,7 @@ public class AggregateEmbeddableResultImpl extends AbstractFetchParent implem null, typeConfiguration ); + this.discriminatorFetch = creationState.visitEmbeddableDiscriminatorFetch( this, true ); resetFetches( creationState.visitNestedFetches( this ) ); this.containsAnyNonScalars = determineIfContainedAnyScalars( getFetches() ); } @@ -169,6 +172,7 @@ public class AggregateEmbeddableResultImpl extends AbstractFetchParent implem return new AggregateEmbeddableResultInitializer( parentAccess, this, + discriminatorFetch, creationState, aggregateSelection ); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/AggregateEmbeddableResultInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/AggregateEmbeddableResultInitializer.java index 00bbacd295..4366d7de40 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/AggregateEmbeddableResultInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/AggregateEmbeddableResultInitializer.java @@ -11,6 +11,7 @@ import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.results.graph.AssemblerCreationState; import org.hibernate.sql.results.graph.DomainResultAssembler; import org.hibernate.sql.results.graph.FetchParentAccess; +import org.hibernate.sql.results.graph.basic.BasicFetch; import org.hibernate.sql.results.graph.embeddable.AbstractEmbeddableInitializer; import org.hibernate.sql.results.graph.embeddable.EmbeddableResultGraphNode; import org.hibernate.sql.results.jdbc.spi.RowProcessingState; @@ -28,8 +29,10 @@ public class AggregateEmbeddableResultInitializer extends AbstractEmbeddableInit public AggregateEmbeddableResultInitializer( FetchParentAccess fetchParentAccess, EmbeddableResultGraphNode resultDescriptor, - AssemblerCreationState creationState, SqlSelection structSelection) { - super( resultDescriptor, fetchParentAccess, creationState ); + BasicFetch discriminatorFetch, + AssemblerCreationState creationState, + SqlSelection structSelection) { + super( resultDescriptor, fetchParentAccess, discriminatorFetch, creationState ); this.aggregateValuesArrayPositions = AggregateEmbeddableInitializer.determineAggregateValuesArrayPositions( fetchParentAccess, structSelection diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableExpressionResultImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableExpressionResultImpl.java index cf2877ecb8..cdf3793a52 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableExpressionResultImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableExpressionResultImpl.java @@ -140,6 +140,6 @@ public class EmbeddableExpressionResultImpl extends AbstractFetchParent imple @Override public Initializer createInitializer(FetchParentAccess parentAccess, AssemblerCreationState creationState) { - return new EmbeddableResultInitializer( this, parentAccess, creationState ); + return new EmbeddableResultInitializer( this, parentAccess, null, creationState ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableFetchImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableFetchImpl.java index ab1436b290..d089ebef47 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableFetchImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableFetchImpl.java @@ -8,6 +8,7 @@ package org.hibernate.sql.results.graph.embeddable.internal; import org.hibernate.engine.FetchTiming; import org.hibernate.graph.spi.GraphImplementor; +import org.hibernate.metamodel.mapping.DiscriminatorMapping; import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.model.domain.JpaMetamodel; @@ -26,6 +27,7 @@ import org.hibernate.sql.results.graph.FetchParentAccess; import org.hibernate.sql.results.graph.Fetchable; import org.hibernate.sql.results.graph.Initializer; import org.hibernate.sql.results.graph.InitializerProducer; +import org.hibernate.sql.results.graph.basic.BasicFetch; import org.hibernate.sql.results.graph.embeddable.EmbeddableInitializer; import org.hibernate.sql.results.graph.embeddable.EmbeddableResultGraphNode; import org.hibernate.sql.results.graph.embeddable.EmbeddableValuedFetchable; @@ -41,6 +43,7 @@ public class EmbeddableFetchImpl extends AbstractFetchParent private final TableGroup tableGroup; private final boolean hasTableGroup; private final EmbeddableMappingType fetchContainer; + private final BasicFetch discriminatorFetch; public EmbeddableFetchImpl( NavigablePath navigablePath, @@ -77,6 +80,8 @@ public class EmbeddableFetchImpl extends AbstractFetchParent } ); + this.discriminatorFetch = creationState.visitEmbeddableDiscriminatorFetch( this, false ); + afterInitialize( this, creationState ); } @@ -90,6 +95,7 @@ public class EmbeddableFetchImpl extends AbstractFetchParent fetchTiming = original.fetchTiming; tableGroup = original.tableGroup; hasTableGroup = original.hasTableGroup; + discriminatorFetch = original.discriminatorFetch; } @Override @@ -160,7 +166,7 @@ public class EmbeddableFetchImpl extends AbstractFetchParent @Override public EmbeddableInitializer createInitializer(FetchParentAccess parentAccess, AssemblerCreationState creationState) { - return new EmbeddableFetchInitializer( parentAccess, this, creationState ); + return new EmbeddableFetchInitializer( parentAccess, this, discriminatorFetch, creationState ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableFetchInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableFetchInitializer.java index 6e3da86138..8f74f3387d 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableFetchInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableFetchInitializer.java @@ -8,6 +8,7 @@ package org.hibernate.sql.results.graph.embeddable.internal; import org.hibernate.sql.results.graph.AssemblerCreationState; import org.hibernate.sql.results.graph.FetchParentAccess; +import org.hibernate.sql.results.graph.basic.BasicFetch; import org.hibernate.sql.results.graph.embeddable.AbstractEmbeddableInitializer; import org.hibernate.sql.results.graph.embeddable.EmbeddableResultGraphNode; @@ -19,8 +20,9 @@ public class EmbeddableFetchInitializer public EmbeddableFetchInitializer( FetchParentAccess fetchParentAccess, EmbeddableResultGraphNode resultDescriptor, + BasicFetch discriminatorFetch, AssemblerCreationState creationState) { - super( resultDescriptor, fetchParentAccess, creationState ); + super( resultDescriptor, fetchParentAccess, discriminatorFetch, creationState ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableForeignKeyResultImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableForeignKeyResultImpl.java index 4354551cca..d97a3ba59f 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableForeignKeyResultImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableForeignKeyResultImpl.java @@ -116,7 +116,7 @@ public class EmbeddableForeignKeyResultImpl public EmbeddableInitializer createInitializer(FetchParentAccess parentAccess, AssemblerCreationState creationState) { return getReferencedModePart() instanceof NonAggregatedIdentifierMapping ? new NonAggregatedIdentifierMappingResultInitializer( this, null, creationState ) - : new EmbeddableResultInitializer( this, null, creationState ); + : new EmbeddableResultInitializer( this, null, null, creationState ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableResultImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableResultImpl.java index 424aa5eab6..20badca3d0 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableResultImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableResultImpl.java @@ -23,6 +23,7 @@ import org.hibernate.sql.results.graph.Fetch; import org.hibernate.sql.results.graph.FetchParentAccess; import org.hibernate.sql.results.graph.Initializer; import org.hibernate.sql.results.graph.InitializerProducer; +import org.hibernate.sql.results.graph.basic.BasicFetch; import org.hibernate.sql.results.graph.embeddable.EmbeddableResult; import org.hibernate.sql.results.graph.embeddable.EmbeddableResultGraphNode; import org.hibernate.sql.results.graph.internal.ImmutableFetchList; @@ -38,6 +39,7 @@ public class EmbeddableResultImpl extends AbstractFetchParent implements Embe private final String resultVariable; private final boolean containsAnyNonScalars; private final EmbeddableMappingType fetchContainer; + private final BasicFetch discriminatorFetch; public EmbeddableResultImpl( NavigablePath navigablePath, @@ -75,6 +77,8 @@ public class EmbeddableResultImpl extends AbstractFetchParent implements Embe } ); + this.discriminatorFetch = creationState.visitEmbeddableDiscriminatorFetch( this, false ); + afterInitialize( this, creationState ); // after-after-initialize :D @@ -139,6 +143,6 @@ public class EmbeddableResultImpl extends AbstractFetchParent implements Embe @Override public Initializer createInitializer(FetchParentAccess parentAccess, AssemblerCreationState creationState) { - return new EmbeddableResultInitializer( this, parentAccess, creationState ); + return new EmbeddableResultInitializer( this, parentAccess, discriminatorFetch, creationState ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableResultInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableResultInitializer.java index 3652c06f97..dabc9300ca 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableResultInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableResultInitializer.java @@ -8,6 +8,7 @@ package org.hibernate.sql.results.graph.embeddable.internal; import org.hibernate.sql.results.graph.AssemblerCreationState; import org.hibernate.sql.results.graph.FetchParentAccess; +import org.hibernate.sql.results.graph.basic.BasicFetch; import org.hibernate.sql.results.graph.embeddable.AbstractEmbeddableInitializer; import org.hibernate.sql.results.graph.embeddable.EmbeddableResultGraphNode; @@ -18,8 +19,9 @@ public class EmbeddableResultInitializer extends AbstractEmbeddableInitializer { public EmbeddableResultInitializer( EmbeddableResultGraphNode resultDescriptor, FetchParentAccess parentAccess, + BasicFetch discriminatorFetch, AssemblerCreationState creationState) { - super( resultDescriptor, parentAccess, creationState ); + super( resultDescriptor, parentAccess, discriminatorFetch, creationState ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/type/ComponentType.java b/hibernate-core/src/main/java/org/hibernate/type/ComponentType.java index 37ac9dd9c9..9a7924db70 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/ComponentType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/ComponentType.java @@ -22,6 +22,7 @@ import org.hibernate.Remove; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; import org.hibernate.engine.spi.CascadeStyle; +import org.hibernate.engine.spi.CascadeStyles; import org.hibernate.engine.spi.Mapping; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; @@ -30,11 +31,14 @@ import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.mapping.Component; import org.hibernate.mapping.Property; +import org.hibernate.mapping.Value; +import org.hibernate.metamodel.mapping.EmbeddableDiscriminatorMapping; import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; import org.hibernate.metamodel.mapping.SelectableMapping; import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; import org.hibernate.metamodel.spi.EmbeddableInstantiator; +import org.hibernate.metamodel.spi.EmbeddableRepresentationStrategy; import org.hibernate.property.access.spi.PropertyAccess; import org.hibernate.query.sqm.SqmExpressible; import org.hibernate.type.descriptor.ValueExtractor; @@ -42,6 +46,7 @@ import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.spi.CompositeTypeImplementor; import static org.hibernate.internal.util.ReflectHelper.isRecord; +import static org.hibernate.metamodel.mapping.EntityDiscriminatorMapping.DISCRIMINATOR_ROLE_NAME; /** * Handles {@linkplain jakarta.persistence.Embedded embedded} mappings. @@ -59,6 +64,7 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen protected final int propertySpan; private final CascadeStyle[] cascade; private final FetchMode[] joinedFetch; + private final int discriminatorColumnSpan; private final boolean isAggregate; private final boolean isKey; @@ -85,11 +91,12 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen this.isKey = component.isKey(); this.propertySpan = component.getPropertySpan(); this.originalPropertyOrder = originalPropertyOrder; - this.propertyNames = new String[propertySpan]; - this.propertyTypes = new Type[propertySpan]; - this.propertyNullability = new boolean[propertySpan]; - this.cascade = new CascadeStyle[propertySpan]; - this.joinedFetch = new FetchMode[propertySpan]; + final Value discriminator = component.getDiscriminator(); + this.propertyNames = new String[propertySpan + ( component.isPolymorphic() ? 1 : 0 )]; + this.propertyTypes = new Type[propertySpan + ( component.isPolymorphic() ? 1 : 0 )]; + this.propertyNullability = new boolean[propertySpan + ( component.isPolymorphic() ? 1 : 0 )]; + this.cascade = new CascadeStyle[propertySpan + ( component.isPolymorphic() ? 1 : 0 )]; + this.joinedFetch = new FetchMode[propertySpan + ( component.isPolymorphic() ? 1 : 0 )]; int i = 0; for ( Property property : component.getProperties() ) { @@ -106,6 +113,17 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen } i++; } + if ( discriminator != null ) { + this.discriminatorColumnSpan = discriminator.getColumnSpan(); + this.propertyNames[i] = DISCRIMINATOR_ROLE_NAME; + this.propertyTypes[i] = discriminator.getType(); + this.propertyNullability[i] = false; + this.cascade[i] = CascadeStyles.NONE; + this.joinedFetch[i] = FetchMode.SELECT; + } + else { + this.discriminatorColumnSpan = 0; + } } private boolean isAggregate() { @@ -122,6 +140,7 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen for ( int i = 0; i < propertySpan; i++ ) { span += propertyTypes[i].getColumnSpan( mapping ); } + span += discriminatorColumnSpan; return span; } @@ -480,7 +499,7 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen values[i] = propertyTypes[i].deepCopy( values[i], factory ); } - final Object result = instantiator().instantiate( () -> values, factory ); + final Object result = instantiator( component ).instantiate( () -> values, factory ); //not absolutely necessary, but helps for some //equals()/hashCode() implementations @@ -516,7 +535,7 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen ); if ( target == null || !isMutable() ) { - return instantiator().instantiate( () -> replacedValues, session.getSessionFactory() ); + return instantiator( original ).instantiate( () -> replacedValues, session.getSessionFactory() ); } else { setPropertyValues( target, replacedValues ); @@ -549,7 +568,7 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen ); if ( target == null || !isMutable() ) { - return instantiator().instantiate( () -> replacedValues, session.getSessionFactory() ); + return instantiator( original ).instantiate( () -> replacedValues, session.getSessionFactory() ); } else { setPropertyValues( target, replacedValues ); @@ -576,9 +595,17 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen return null; } else { - final Object[] values = getPropertyValues( value ); - for ( int i = 0; i < propertyTypes.length; i++ ) { - values[i] = propertyTypes[i].disassemble( values[i], session, owner ); + final boolean polymorphic = embeddableTypeDescriptor().isPolymorphic(); + final Object[] values = new Object[propertySpan + (polymorphic ? 1 : 0)]; + final Object[] propertyValues = getPropertyValues( value ); + int i = 0; + for ( ; i < propertySpan; i++ ) { + values[i] = propertyTypes[i].disassemble( propertyValues[i], session, owner ); + } + if ( polymorphic ) { + final EmbeddableDiscriminatorMapping discriminatorMapping = embeddableTypeDescriptor().getDiscriminatorMapping(); + final Object discriminatorValue = discriminatorMapping.getDiscriminatorValue( value.getClass().getName() ); + values[i] = discriminatorMapping.disassemble( discriminatorValue, session ); } return values; } @@ -591,9 +618,17 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen return null; } else { - final Object[] values = getPropertyValues( value ); - for ( int i = 0; i < propertyTypes.length; i++ ) { - values[i] = propertyTypes[i].disassemble( values[i], sessionFactory ); + final boolean polymorphic = embeddableTypeDescriptor().isPolymorphic(); + final Object[] values = new Object[propertySpan + (polymorphic ? 1 : 0)]; + final Object[] propertyValues = getPropertyValues( value ); + int i = 0; + for ( ; i < propertyTypes.length; i++ ) { + values[i] = propertyTypes[i].disassemble( propertyValues[i], sessionFactory ); + } + if ( polymorphic ) { + final EmbeddableDiscriminatorMapping discriminatorMapping = embeddableTypeDescriptor().getDiscriminatorMapping(); + final Object discriminatorValue = discriminatorMapping.getDiscriminatorValue( value.getClass().getName() ); + values[i] = discriminatorMapping.disassemble( discriminatorValue, null ); } return values; } @@ -602,18 +637,23 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen @Override public Object assemble(Serializable object, SharedSessionContractImplementor session, Object owner) throws HibernateException { - if ( object == null ) { return null; } else { final Object[] values = (Object[]) object; - final Object[] assembled = new Object[values.length]; - for ( int i = 0; i < propertyTypes.length; i++ ) { + final boolean polymorphic = embeddableTypeDescriptor().isPolymorphic(); + final Object[] assembled = new Object[values.length - ( polymorphic ? 1 : 0 )]; + int i = 0; + for ( ; i < assembled.length; i++ ) { assembled[i] = propertyTypes[i].assemble( (Serializable) values[i], session, owner ); } - final Object instance = instantiator().instantiate( () -> assembled, session.getFactory() ); + final EmbeddableRepresentationStrategy representationStrategy = embeddableTypeDescriptor().getRepresentationStrategy(); + final EmbeddableInstantiator instantiator = polymorphic ? + representationStrategy.getInstantiatorForDiscriminator( values[i] ) : + representationStrategy.getInstantiator(); + final Object instance = instantiator.instantiate( () -> assembled, session.getFactory() ); final PropertyAccess parentInjectionAccess = mappingModelPart.getParentInjectionAttributePropertyAccess(); if ( parentInjectionAccess != null ) { @@ -708,7 +748,8 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen values = (Object[]) jdbcValueExtractor().extract( statement, startIndex, session ); } else { - values = new Object[propertySpan]; + final boolean polymorphic = embeddableTypeDescriptor().isPolymorphic(); + values = new Object[propertySpan + ( polymorphic ? 1 : 0 )]; int currentIndex = startIndex; boolean notNull = false; for ( int i = 0; i < propertySpan; i++ ) { @@ -729,6 +770,17 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen currentIndex += propertyType.getColumnSpan( session.getFactory() ); } + if ( polymorphic ) { + values[currentIndex] = embeddableTypeDescriptor().getDiscriminatorMapping() + .getJdbcMapping() + .getJdbcValueExtractor() + .extract( + statement, + currentIndex, + session + ); + } + if ( !notNull ) { values = null; } @@ -745,7 +797,16 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen } private Object resolve(Object[] value, SharedSessionContractImplementor session) throws HibernateException { - return instantiator().instantiate( () -> value, session.getFactory() ); + final EmbeddableRepresentationStrategy representationStrategy = embeddableTypeDescriptor().getRepresentationStrategy(); + final EmbeddableInstantiator instantiator; + if ( embeddableTypeDescriptor().isPolymorphic() ) { + // the discriminator here is the composite class name because it gets converted to the domain type when extracted + instantiator = representationStrategy.getInstantiatorForClass( (String) value[value.length - 1] ); + } + else { + instantiator = representationStrategy.getInstantiator(); + } + return instantiator.instantiate( () -> value, session.getFactory() ); } private EmbeddableMappingType embeddableTypeDescriptor() { @@ -756,8 +817,17 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen return embeddableTypeDescriptor().getAggregateMapping().getJdbcMapping().getJdbcValueExtractor(); } - protected final EmbeddableInstantiator instantiator() { - return embeddableTypeDescriptor().getRepresentationStrategy().getInstantiator(); + protected final EmbeddableInstantiator instantiator(Object compositeInstance) { + final EmbeddableRepresentationStrategy representationStrategy = embeddableTypeDescriptor().getRepresentationStrategy(); + if ( embeddableTypeDescriptor().isPolymorphic() ) { + final String compositeClassName = compositeInstance != null ? + compositeInstance.getClass().getName() : + componentClass.getName(); + return representationStrategy.getInstantiatorForClass( compositeClassName ); + } + else { + return representationStrategy.getInstantiator(); + } } @Override @@ -801,7 +871,7 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen public Object replacePropertyValues(Object component, Object[] values, SharedSessionContractImplementor session) throws HibernateException { if ( !isMutable() ) { - return instantiator().instantiate( () -> values, session.getSessionFactory() ); + return instantiator( component ).instantiate( () -> values, session.getSessionFactory() ); } return CompositeTypeImplementor.super.replacePropertyValues( component, values, session ); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/UserComponentType.java b/hibernate-core/src/main/java/org/hibernate/type/UserComponentType.java index 73363ed7ed..a6b2f6e418 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/UserComponentType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/UserComponentType.java @@ -91,6 +91,6 @@ public class UserComponentType extends ComponentType { @Override public Object replacePropertyValues(Object component, Object[] values, SharedSessionContractImplementor session) throws HibernateException { - return instantiator().instantiate( () -> values, session.getSessionFactory() ); + return instantiator( component ).instantiate( () -> values, session.getSessionFactory() ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/ArrayJdbcType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/ArrayJdbcType.java index 70a62239b6..63ccde770a 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/ArrayJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/ArrayJdbcType.java @@ -15,6 +15,7 @@ import java.sql.SQLException; import java.sql.Types; import org.hibernate.HibernateException; +import org.hibernate.dialect.StructAttributeValues; import org.hibernate.dialect.StructHelper; import org.hibernate.engine.jdbc.Size; import org.hibernate.engine.spi.SharedSessionContractImplementor; @@ -34,6 +35,8 @@ import org.hibernate.type.internal.BasicTypeImpl; import org.hibernate.type.internal.ParameterizedTypeImpl; import org.hibernate.type.spi.TypeConfiguration; +import static org.hibernate.dialect.StructHelper.instantiate; + /** * Descriptor for {@link Types#ARRAY ARRAY} handling. * @@ -151,18 +154,16 @@ public class ArrayJdbcType implements JdbcType { if ( array != null && getElementJdbcType() instanceof AggregateJdbcType ) { final AggregateJdbcType aggregateJdbcType = (AggregateJdbcType) getElementJdbcType(); final EmbeddableMappingType embeddableMappingType = aggregateJdbcType.getEmbeddableMappingType(); - final EmbeddableInstantiator instantiator = embeddableMappingType.getRepresentationStrategy() - .getInstantiator(); final Object rawArray = array.getArray(); final Object[] domainObjects = new Object[Array.getLength( rawArray )]; for ( int i = 0; i < domainObjects.length; i++ ) { final Object[] aggregateRawValues = aggregateJdbcType.extractJdbcValues( Array.get( rawArray, i ), options ); - final Object[] attributeValues = StructHelper.getAttributeValues( + final StructAttributeValues attributeValues = StructHelper.getAttributeValues( embeddableMappingType, aggregateRawValues, options ); - domainObjects[i] = instantiator.instantiate( () -> attributeValues, options.getSessionFactory() ); + domainObjects[i] = instantiate( embeddableMappingType, attributeValues, options.getSessionFactory() ); } return extractor.getJavaType().wrap( domainObjects, options ); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/embeddable/BasicEmbeddableInheritanceTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/embeddable/BasicEmbeddableInheritanceTest.java new file mode 100644 index 0000000000..cd1c033d08 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/embeddable/BasicEmbeddableInheritanceTest.java @@ -0,0 +1,161 @@ +/* + * 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.inheritance.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.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Marco Belladelli + */ +@DomainModel( annotatedClasses = { + BasicEmbeddableInheritanceTest.TestEntity.class, + ParentEmbeddable.class, + ChildOneEmbeddable.class, + SubChildOneEmbeddable.class, + ChildTwoEmbeddable.class, +} ) +@SessionFactory +public class BasicEmbeddableInheritanceTest { + @Test + public void testFind(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final TestEntity result = session.find( TestEntity.class, 1L ); + assertThat( result.getEmbeddable().getParentProp() ).isEqualTo( "embeddable_1" ); + assertThat( result.getEmbeddable() ).isExactlyInstanceOf( ChildOneEmbeddable.class ); + assertThat( ( (ChildOneEmbeddable) result.getEmbeddable() ).getChildOneProp() ).isEqualTo( 1 ); + } ); + scope.inTransaction( session -> { + final TestEntity result = session.find( TestEntity.class, 3L ); + assertThat( result.getEmbeddable().getParentProp() ).isEqualTo( "embeddable_3" ); + assertThat( result.getEmbeddable() ).isExactlyInstanceOf( ParentEmbeddable.class ); + } ); + } + + @Test + public void testQueryEntity(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final TestEntity result = session.createQuery( + "from TestEntity where id = 2", + TestEntity.class + ).getSingleResult(); + assertThat( result.getEmbeddable().getParentProp() ).isEqualTo( "embeddable_2" ); + assertThat( result.getEmbeddable() ).isExactlyInstanceOf( ChildTwoEmbeddable.class ); + assertThat( ( (ChildTwoEmbeddable) result.getEmbeddable() ).getChildTwoProp() ).isEqualTo( 2L ); + } ); + } + + @Test + public void testQueryEmbeddable(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final ParentEmbeddable result = session.createQuery( + "select embeddable from TestEntity where id = 4", + ParentEmbeddable.class + ).getSingleResult(); + assertThat( result.getParentProp() ).isEqualTo( "embeddable_4" ); + assertThat( result ).isExactlyInstanceOf( SubChildOneEmbeddable.class ); + assertThat( ( (SubChildOneEmbeddable) result ).getChildOneProp() ).isEqualTo( 4 ); + assertThat( ( (SubChildOneEmbeddable) result ).getSubChildOneProp() ).isEqualTo( 4.0 ); + } ); + } + + @Test + public void testQueryJoinedEmbeddable(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final ParentEmbeddable result = session.createQuery( + "select e from TestEntity t join t.embeddable e where t.id = 2", + ParentEmbeddable.class + ).getSingleResult(); + assertThat( result.getParentProp() ).isEqualTo( "embeddable_2" ); + assertThat( result ).isExactlyInstanceOf( ChildTwoEmbeddable.class ); + assertThat( ( (ChildTwoEmbeddable) result ).getChildTwoProp() ).isEqualTo( 2L ); + } ); + } + + @Test + public void testUpdate(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final TestEntity result = session.find( TestEntity.class, 5L ); + assertThat( result.getEmbeddable().getParentProp() ).isEqualTo( "embeddable_5" ); + assertThat( result.getEmbeddable() ).isExactlyInstanceOf( ChildOneEmbeddable.class ); + assertThat( ( (ChildOneEmbeddable) result.getEmbeddable() ).getChildOneProp() ).isEqualTo( 5 ); + // update values + result.getEmbeddable().setParentProp( "embeddable_5_new" ); + ( (ChildOneEmbeddable) result.getEmbeddable() ).setChildOneProp( 55 ); + } ); + scope.inTransaction( session -> { + final TestEntity result = session.find( TestEntity.class, 5L ); + assertThat( result.getEmbeddable().getParentProp() ).isEqualTo( "embeddable_5_new" ); + assertThat( ( (ChildOneEmbeddable) result.getEmbeddable() ).getChildOneProp() ).isEqualTo( 55 ); + result.setEmbeddable( new SubChildOneEmbeddable( "embeddable_6", 6, 6.0 ) ); + } ); + scope.inTransaction( session -> { + final TestEntity result = session.find( TestEntity.class, 5L ); + assertThat( result.getEmbeddable().getParentProp() ).isEqualTo( "embeddable_6" ); + assertThat( result.getEmbeddable() ).isExactlyInstanceOf( SubChildOneEmbeddable.class ); + assertThat( ( (SubChildOneEmbeddable) result.getEmbeddable() ).getChildOneProp() ).isEqualTo( 6 ); + assertThat( ( (SubChildOneEmbeddable) result.getEmbeddable() ).getSubChildOneProp() ).isEqualTo( 6.0 ); + } ); + } + + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + session.persist( new TestEntity( 1L, new ChildOneEmbeddable( "embeddable_1", 1 ) ) ); + session.persist( new TestEntity( 2L, new ChildTwoEmbeddable( "embeddable_2", 2L ) ) ); + session.persist( new TestEntity( 3L, new ParentEmbeddable( "embeddable_3" ) ) ); + session.persist( new TestEntity( 4L, new SubChildOneEmbeddable( "embeddable_4", 4, 4.0 ) ) ); + session.persist( new TestEntity( 5L, new ChildOneEmbeddable( "embeddable_5", 5 ) ) ); + } ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> session.createMutationQuery( "delete from TestEntity" ).executeUpdate() ); + } + + //tag::embeddable-inheritance-entity-example[] + @Entity( name = "TestEntity" ) + static class TestEntity { + @Id + private Long id; + + @Embedded + private ParentEmbeddable embeddable; + + // ... + //end::embeddable-inheritance-entity-example[] + + public TestEntity() { + } + + public TestEntity(Long id, ParentEmbeddable embeddable) { + this.id = id; + this.embeddable = embeddable; + } + + public ParentEmbeddable getEmbeddable() { + return embeddable; + } + + public void setEmbeddable(ParentEmbeddable embeddable) { + this.embeddable = embeddable; + } + //tag::embeddable-inheritance-entity-example[] + } + //end::embeddable-inheritance-entity-example[] +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/embeddable/ChildOneEmbeddable.java b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/embeddable/ChildOneEmbeddable.java new file mode 100644 index 0000000000..b1f686abdd --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/embeddable/ChildOneEmbeddable.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.orm.test.inheritance.embeddable; + +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Embeddable; + +/** + * @author Marco Belladelli + */ +//tag::embeddable-inheritance-child-one-example[] +@Embeddable +@DiscriminatorValue( "child_one" ) +class ChildOneEmbeddable extends ParentEmbeddable { + private Integer childOneProp; + + // ... +//end::embeddable-inheritance-child-one-example[] + + public ChildOneEmbeddable() { + } + + public ChildOneEmbeddable(String parentProp, Integer childOneProp) { + super( parentProp ); + this.childOneProp = childOneProp; + } + + public Integer getChildOneProp() { + return childOneProp; + } + + public void setChildOneProp(Integer childOneProp) { + this.childOneProp = childOneProp; + } +//tag::embeddable-inheritance-child-one-example[] +} +//end::embeddable-inheritance-child-one-example[] diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/embeddable/ChildTwoEmbeddable.java b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/embeddable/ChildTwoEmbeddable.java new file mode 100644 index 0000000000..2f5ed95ea3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/embeddable/ChildTwoEmbeddable.java @@ -0,0 +1,33 @@ +/* + * 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.inheritance.embeddable; + +import jakarta.persistence.Embeddable; + +/** + * @author Marco Belladelli + */ +@Embeddable +class ChildTwoEmbeddable extends ParentEmbeddable { + private Long childTwoProp; + + public ChildTwoEmbeddable() { + } + + public ChildTwoEmbeddable(String parentProp, Long childTwoProp) { + super( parentProp ); + this.childTwoProp = childTwoProp; + } + + public Long getChildTwoProp() { + return childTwoProp; + } + + public void setChildTwoProp(Long childTwoProp) { + this.childTwoProp = childTwoProp; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/embeddable/ElementCollectionEmbeddableInheritanceTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/embeddable/ElementCollectionEmbeddableInheritanceTest.java new file mode 100644 index 0000000000..c9b84657c2 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/embeddable/ElementCollectionEmbeddableInheritanceTest.java @@ -0,0 +1,150 @@ +/* + * 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.inheritance.embeddable; + +import java.util.ArrayList; +import java.util.List; + +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.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.ElementCollection; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Marco Belladelli + */ +@DomainModel( annotatedClasses = { + ElementCollectionEmbeddableInheritanceTest.TestEntity.class, + ParentEmbeddable.class, + ChildOneEmbeddable.class, + SubChildOneEmbeddable.class, + ChildTwoEmbeddable.class, +} ) +@SessionFactory +public class ElementCollectionEmbeddableInheritanceTest { + @Test + public void testFind(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final TestEntity result = session.find( TestEntity.class, 1L ); + assertThat( result.getEmbeddables() ).hasSize( 2 ); + result.getEmbeddables().forEach( embeddable -> { + if ( embeddable instanceof ChildOneEmbeddable ) { + assertThat( embeddable.getParentProp() ).isEqualTo( "embeddable_1" ); + assertThat( embeddable ).isExactlyInstanceOf( ChildOneEmbeddable.class ); + assertThat( ( (ChildOneEmbeddable) embeddable ).getChildOneProp() ).isEqualTo( 1 ); + } + else { + assertThat( embeddable.getParentProp() ).isEqualTo( "embeddable_3" ); + assertThat( embeddable ).isExactlyInstanceOf( ParentEmbeddable.class ); + } + } ); + } ); + } + + @Test + public void testQueryEntity(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final TestEntity result = session.createQuery( + "from TestEntity where id = 2", + TestEntity.class + ).getSingleResult(); + assertThat( result.getEmbeddables() ).hasSize( 1 ); + final ParentEmbeddable embeddable = result.getEmbeddables().get( 0 ); + assertThat( embeddable.getParentProp() ).isEqualTo( "embeddable_2" ); + assertThat( embeddable ).isExactlyInstanceOf( SubChildOneEmbeddable.class ); + assertThat( ( (SubChildOneEmbeddable) embeddable ).getChildOneProp() ).isEqualTo( 2 ); + assertThat( ( (SubChildOneEmbeddable) embeddable ).getSubChildOneProp() ).isEqualTo( 2.0 ); + } ); + } + + @Test + public void testUpdate(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final TestEntity result = session.find( TestEntity.class, 3L ); + assertThat( result.getEmbeddables() ).hasSize( 2 ); + result.getEmbeddables().forEach( embeddable -> { + if ( embeddable instanceof ChildTwoEmbeddable ) { + assertThat( embeddable ).isExactlyInstanceOf( ChildTwoEmbeddable.class ); + assertThat( ( (ChildTwoEmbeddable) embeddable ).getChildTwoProp() ).isEqualTo( 4L ); + } + else { + assertThat( embeddable ).isExactlyInstanceOf( ChildOneEmbeddable.class ); + assertThat( ( (ChildOneEmbeddable) embeddable ).getChildOneProp() ).isEqualTo( 5 ); + // update values + embeddable.setParentProp( "embeddable_5_new" ); + ( (ChildOneEmbeddable) embeddable ).setChildOneProp( 55 ); + } + } ); + result.getEmbeddables().add( new SubChildOneEmbeddable( "embeddable_6", 6, 6.0 ) ); + } ); + scope.inTransaction( session -> { + final TestEntity result = session.find( TestEntity.class, 3L ); + result.getEmbeddables().forEach( embeddable -> { + if ( embeddable instanceof SubChildOneEmbeddable ) { + assertThat( embeddable ).isExactlyInstanceOf( SubChildOneEmbeddable.class ); + assertThat( embeddable.getParentProp() ).isEqualTo( "embeddable_6" ); + assertThat( ( (SubChildOneEmbeddable) embeddable ).getSubChildOneProp() ).isEqualTo( 6.0 ); + } + else if ( embeddable instanceof ChildOneEmbeddable ) { + assertThat( embeddable ).isExactlyInstanceOf( ChildOneEmbeddable.class ); + assertThat( embeddable.getParentProp() ).isEqualTo( "embeddable_5_new" ); + assertThat( ( (ChildOneEmbeddable) embeddable ).getChildOneProp() ).isEqualTo( 55 ); + } + } ); + } ); + } + + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final TestEntity testEntity1 = new TestEntity( 1L ); + testEntity1.getEmbeddables().add( new ChildOneEmbeddable( "embeddable_1", 1 ) ); + testEntity1.getEmbeddables().add( new ParentEmbeddable( "embeddable_3" ) ); + session.persist( testEntity1 ); + final TestEntity testEntity2 = new TestEntity( 2L ); + testEntity2.getEmbeddables().add( new SubChildOneEmbeddable( "embeddable_2", 2, 2.0 ) ); + session.persist( testEntity2 ); + final TestEntity testEntity3 = new TestEntity( 3L ); + testEntity3.getEmbeddables().add( new ChildTwoEmbeddable( "embeddable_4", 4L ) ); + testEntity3.getEmbeddables().add( new ChildOneEmbeddable( "embeddable_5", 5 ) ); + session.persist( testEntity3 ); + } ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> session.createMutationQuery( "delete from TestEntity" ).executeUpdate() ); + } + + @Entity( name = "TestEntity" ) + static class TestEntity { + @Id + private Long id; + + @ElementCollection + private List embeddables = new ArrayList<>(); + + public TestEntity() { + } + + public TestEntity(Long id) { + this.id = id; + } + + public List getEmbeddables() { + return embeddables; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/embeddable/EmbeddableInheritance2LCTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/embeddable/EmbeddableInheritance2LCTest.java new file mode 100644 index 0000000000..77e22e215d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/embeddable/EmbeddableInheritance2LCTest.java @@ -0,0 +1,120 @@ +/* + * 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.inheritance.embeddable; + +import org.hibernate.cache.spi.CacheImplementor; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.stat.spi.StatisticsImplementor; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.Setting; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Cacheable; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Marco Belladelli + */ +@DomainModel( annotatedClasses = { + EmbeddableInheritance2LCTest.TestEntity.class, + ParentEmbeddable.class, + ChildOneEmbeddable.class, + SubChildOneEmbeddable.class, + ChildTwoEmbeddable.class, +} ) +@ServiceRegistry( settings = @Setting( name = AvailableSettings.USE_SECOND_LEVEL_CACHE, value = "true" ) ) +@SessionFactory( generateStatistics = true ) +public class EmbeddableInheritance2LCTest { + @Test + public void testFind(SessionFactoryScope scope) { + final StatisticsImplementor statistics = scope.getSessionFactory().getStatistics(); + statistics.clear(); + final CacheImplementor cache = scope.getSessionFactory().getCache(); + cache.evictEntityData(); + scope.inTransaction( session -> { + // load the entity in cache + session.find( TestEntity.class, 1L ); + assertThat( statistics.getSecondLevelCachePutCount() ).isEqualTo( 1 ); + } ); + scope.inTransaction( session -> { + final TestEntity result = session.find( TestEntity.class, 1L ); + assertThat( statistics.getSecondLevelCacheHitCount() ).isEqualTo( 1 ); + assertThat( result.getEmbeddable().getParentProp() ).isEqualTo( "embeddable_1" ); + assertThat( result.getEmbeddable() ).isExactlyInstanceOf( SubChildOneEmbeddable.class ); + assertThat( ( (SubChildOneEmbeddable) result.getEmbeddable() ).getChildOneProp() ).isEqualTo( 1 ); + assertThat( ( (SubChildOneEmbeddable) result.getEmbeddable() ).getSubChildOneProp() ).isEqualTo( 1.0 ); + } ); + } + + @Test + public void testQuery(SessionFactoryScope scope) { + final StatisticsImplementor statistics = scope.getSessionFactory().getStatistics(); + statistics.clear(); + final CacheImplementor cache = scope.getSessionFactory().getCache(); + cache.evictEntityData(); + scope.inTransaction( session -> { + session.createQuery( "from TestEntity where id = 2", TestEntity.class ).getSingleResult(); + assertThat( statistics.getSecondLevelCachePutCount() ).isEqualTo( 1 ); + } ); + scope.inTransaction( session -> { + final TestEntity result = session.find( TestEntity.class, 2L ); + assertThat( statistics.getSecondLevelCacheHitCount() ).isEqualTo( 1 ); + assertThat( result.getEmbeddable().getParentProp() ).isEqualTo( "embeddable_2" ); + assertThat( result.getEmbeddable() ).isExactlyInstanceOf( ChildTwoEmbeddable.class ); + assertThat( ( (ChildTwoEmbeddable) result.getEmbeddable() ).getChildTwoProp() ).isEqualTo( 2L ); + } ); + } + + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + session.persist( new TestEntity( 1L, new SubChildOneEmbeddable( "embeddable_1", 1, 1.0 ) ) ); + session.persist( new TestEntity( 2L, new ChildTwoEmbeddable( "embeddable_2", 2L ) ) ); + } ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> session.createMutationQuery( "delete from TestEntity" ).executeUpdate() ); + } + + @Entity( name = "TestEntity" ) + @Cacheable + static class TestEntity { + @Id + private Long id; + + @Embedded + private ParentEmbeddable embeddable; + + public TestEntity() { + } + + public TestEntity(Long id, ParentEmbeddable embeddable) { + this.id = id; + this.embeddable = embeddable; + } + + public ParentEmbeddable getEmbeddable() { + return embeddable; + } + + public void setEmbeddable(ParentEmbeddable embeddable) { + this.embeddable = embeddable; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/embeddable/EmbeddableInheritanceAssciationsTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/embeddable/EmbeddableInheritanceAssciationsTest.java new file mode 100644 index 0000000000..2f19b7138c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/embeddable/EmbeddableInheritanceAssciationsTest.java @@ -0,0 +1,338 @@ +/* + * 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.inheritance.embeddable; + +import java.util.ArrayList; +import java.util.List; + +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.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Embeddable; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Marco Belladelli + */ +@DomainModel( annotatedClasses = { + ParentEmbeddable.class, + EmbeddableInheritanceAssciationsTest.TestEntity.class, + EmbeddableInheritanceAssciationsTest.AssociatedEntity.class, + EmbeddableInheritanceAssciationsTest.AssociationChildOne.class, + EmbeddableInheritanceAssciationsTest.AssociationSubChildOne.class, + EmbeddableInheritanceAssciationsTest.AssociationChildTwo.class, + EmbeddableInheritanceAssciationsTest.AssociationChildThree.class, +} ) +@SessionFactory +public class EmbeddableInheritanceAssciationsTest { + @Test + public void testManyToOne(SessionFactoryScope scope) { + scope.inTransaction( session -> { + // insert + final AssociatedEntity associated = session.find( AssociatedEntity.class, 1L ); + session.persist( new TestEntity( 1L, new AssociationChildOne( "embeddable_1", associated ) ) ); + } ); + scope.inTransaction( session -> { + // queries + final AssociatedEntity result = session.createQuery( + "select embeddable.manyToOne from TestEntity where id = 1", + AssociatedEntity.class + ).getSingleResult(); + assertThat( result.getId() ).isEqualTo( 1L ); + assertThat( result.getName() ).isEqualTo( "associated_1" ); + } ); + scope.inTransaction( session -> { + // find + final TestEntity result = session.find( TestEntity.class, 1L ); + assertThat( result.getEmbeddable().getParentProp() ).isEqualTo( "embeddable_1" ); + assertThat( result.getEmbeddable() ).isExactlyInstanceOf( AssociationChildOne.class ); + final AssociationChildOne embeddable = (AssociationChildOne) result.getEmbeddable(); + assertThat( embeddable.getManyToOne().getId() ).isEqualTo( 1L ); + assertThat( embeddable.getManyToOne().getName() ).isEqualTo( "associated_1" ); + // update + final AssociatedEntity newAssociated = new AssociatedEntity( 11L, "associated_1_new" ); + session.persist( newAssociated ); + embeddable.setManyToOne( newAssociated ); + } ); + scope.inTransaction( session -> { + final TestEntity result = session.find( TestEntity.class, 1L ); + final AssociationChildOne embeddable = (AssociationChildOne) result.getEmbeddable(); + assertThat( embeddable.getManyToOne().getId() ).isEqualTo( 11L ); + assertThat( embeddable.getManyToOne().getName() ).isEqualTo( "associated_1_new" ); + } ); + } + + @Test + public void testManyToOneSubtype(SessionFactoryScope scope) { + scope.inTransaction( session -> { + // insert + final AssociatedEntity associated = session.find( AssociatedEntity.class, 2L ); + session.persist( new TestEntity( 2L, new AssociationSubChildOne( "embeddable_2", associated ) ) ); + } ); + scope.inTransaction( session -> { + // find + final TestEntity result = session.find( TestEntity.class, 2L ); + assertThat( result.getEmbeddable().getParentProp() ).isEqualTo( "embeddable_2" ); + assertThat( result.getEmbeddable() ).isExactlyInstanceOf( AssociationSubChildOne.class ); + final AssociationSubChildOne embeddable = (AssociationSubChildOne) result.getEmbeddable(); + assertThat( embeddable.getManyToOne().getId() ).isEqualTo( 2L ); + assertThat( embeddable.getManyToOne().getName() ).isEqualTo( "associated_2" ); + } ); + } + + @Test + public void testManyToMany(SessionFactoryScope scope) { + scope.inTransaction( session -> { + // insert + final AssociationChildTwo associationChildTwo = new AssociationChildTwo( "embeddable_3" ); + associationChildTwo.getManyToMany().add( session.find( AssociatedEntity.class, 3L ) ); + associationChildTwo.getManyToMany().add( session.find( AssociatedEntity.class, 4L ) ); + session.persist( new TestEntity( 3L, associationChildTwo ) ); + } ); + scope.inTransaction( session -> { + // queries + final List resultList = session.createQuery( + "select embeddable.manyToMany from TestEntity", + AssociatedEntity.class + ).getResultList(); + final Integer size = session.createQuery( + "select size(embeddable.manyToMany) from TestEntity where id = 3", + Integer.class + ).getSingleResult(); + assertThat( resultList ).hasSize( 2 ).hasSize( size ) + .extracting( AssociatedEntity::getName ) + .containsOnly( "associated_3", "associated_4" ); + } ); + scope.inTransaction( session -> { + // find + final TestEntity result = session.find( TestEntity.class, 3L ); + assertThat( result.getEmbeddable().getParentProp() ).isEqualTo( "embeddable_3" ); + assertThat( result.getEmbeddable() ).isExactlyInstanceOf( AssociationChildTwo.class ); + final AssociationChildTwo embeddable = (AssociationChildTwo) result.getEmbeddable(); + assertThat( embeddable.getManyToMany() ).hasSize( 2 ) + .extracting( AssociatedEntity::getName ) + .containsOnly( "associated_3", "associated_4" ); + // update + embeddable.getManyToMany().remove( 1 ); + final AssociatedEntity newAssociated = new AssociatedEntity( 44L, "associated_4_new" ); + session.persist( newAssociated ); + embeddable.getManyToMany().add( newAssociated ); + } ); + scope.inTransaction( session -> { + final TestEntity result = session.find( TestEntity.class, 3L ); + final AssociationChildTwo embeddable = (AssociationChildTwo) result.getEmbeddable(); + assertThat( embeddable.getManyToMany() ).hasSize( 2 ) + .extracting( AssociatedEntity::getName ) + .containsOnly( "associated_3", "associated_4_new" ); + } ); + } + + @Test + public void testOneToMany(SessionFactoryScope scope) { + scope.inTransaction( session -> { + // insert + final AssociationChildThree associationChildTwo = new AssociationChildThree( "embeddable_3" ); + associationChildTwo.getOneToMany().add( session.find( AssociatedEntity.class, 5L ) ); + associationChildTwo.getOneToMany().add( session.find( AssociatedEntity.class, 6L ) ); + session.persist( new TestEntity( 4L, associationChildTwo ) ); + } ); + scope.inTransaction( session -> { + // queries + final List resultList = session.createQuery( + "select embeddable.oneToMany from TestEntity", + AssociatedEntity.class + ).getResultList(); + final Integer size = session.createQuery( + "select size(embeddable.oneToMany) from TestEntity where id = 4", + Integer.class + ).getSingleResult(); + assertThat( resultList ).hasSize( 2 ).hasSize( size ) + .extracting( AssociatedEntity::getName ) + .containsOnly( "associated_5", "associated_6" ); + } ); + scope.inTransaction( session -> { + // find + final TestEntity result = session.find( TestEntity.class, 4L ); + assertThat( result.getEmbeddable().getParentProp() ).isEqualTo( "embeddable_3" ); + assertThat( result.getEmbeddable() ).isExactlyInstanceOf( AssociationChildThree.class ); + final AssociationChildThree embeddable = (AssociationChildThree) result.getEmbeddable(); + assertThat( embeddable.getOneToMany() ).hasSize( 2 ) + .extracting( AssociatedEntity::getName ) + .containsOnly( "associated_5", "associated_6" ); + // update + embeddable.getOneToMany().remove( 0 ); + final AssociatedEntity newAssociated = new AssociatedEntity( 55L, "associated_5_new" ); + session.persist( newAssociated ); + embeddable.getOneToMany().add( 0, newAssociated ); + } ); + scope.inTransaction( session -> { + final TestEntity result = session.find( TestEntity.class, 4L ); + final AssociationChildThree embeddable = (AssociationChildThree) result.getEmbeddable(); + assertThat( embeddable.getOneToMany() ).hasSize( 2 ) + .extracting( AssociatedEntity::getName ) + .containsOnly( "associated_5_new", "associated_6" ); + } ); + } + + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + session.persist( new AssociatedEntity( 1L, "associated_1" ) ); + session.persist( new AssociatedEntity( 2L, "associated_2" ) ); + session.persist( new AssociatedEntity( 3L, "associated_3" ) ); + session.persist( new AssociatedEntity( 4L, "associated_4" ) ); + session.persist( new AssociatedEntity( 5L, "associated_5" ) ); + session.persist( new AssociatedEntity( 6L, "associated_6" ) ); + } ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> { + session.createQuery( "from TestEntity where size(embeddable.oneToMany) > 0", TestEntity.class ) + .getResultList() + .forEach( t -> ( (AssociationChildThree) t.getEmbeddable() ).getOneToMany().clear() ); + session.flush(); + session.createMutationQuery( "delete from TestEntity" ).executeUpdate(); + session.createMutationQuery( "delete from AssociatedEntity" ).executeUpdate(); + } ); + } + + @Entity( name = "TestEntity" ) + static class TestEntity { + @Id + private Long id; + + @Embedded + private ParentEmbeddable embeddable; + + public TestEntity() { + } + + public TestEntity(Long id, ParentEmbeddable embeddable) { + this.id = id; + this.embeddable = embeddable; + } + + public ParentEmbeddable getEmbeddable() { + return embeddable; + } + + public void setEmbeddable(ParentEmbeddable embeddable) { + this.embeddable = embeddable; + } + } + + @Entity( name = "AssociatedEntity" ) + static class AssociatedEntity { + @Id + private Long id; + + private String name; + + public AssociatedEntity() { + } + + public AssociatedEntity(Long id, String name) { + this.id = id; + this.name = name; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + } + + @Embeddable + @DiscriminatorValue( "child_one" ) + static class AssociationChildOne extends ParentEmbeddable { + @ManyToOne + private AssociatedEntity manyToOne; + + public AssociationChildOne() { + } + + public AssociationChildOne(String parentProp, AssociatedEntity manyToOne) { + super( parentProp ); + this.manyToOne = manyToOne; + } + + public AssociatedEntity getManyToOne() { + return manyToOne; + } + + public void setManyToOne(AssociatedEntity manyToOne) { + this.manyToOne = manyToOne; + } + } + + @Embeddable + @DiscriminatorValue( "sub_child_one" ) + static class AssociationSubChildOne extends AssociationChildOne { + public AssociationSubChildOne() { + } + + public AssociationSubChildOne(String parentProp, AssociatedEntity manyToOne) { + super( parentProp, manyToOne ); + } + } + + @Embeddable + @DiscriminatorValue( "child_two" ) + static class AssociationChildTwo extends ParentEmbeddable { + @ManyToMany + private List manyToMany = new ArrayList<>(); + + public AssociationChildTwo() { + } + + public AssociationChildTwo(String parentProp) { + super( parentProp ); + } + + public List getManyToMany() { + return manyToMany; + } + } + + @Embeddable + @DiscriminatorValue( "child_three" ) + static class AssociationChildThree extends ParentEmbeddable { + @OneToMany( fetch = FetchType.EAGER ) + @JoinColumn + private List oneToMany = new ArrayList<>(); + + public AssociationChildThree() { + } + + public AssociationChildThree(String parentProp) { + super( parentProp ); + } + + public List getOneToMany() { + return oneToMany; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/embeddable/EmbeddableInheritanceAttributeOverrideTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/embeddable/EmbeddableInheritanceAttributeOverrideTest.java new file mode 100644 index 0000000000..2a6a098441 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/embeddable/EmbeddableInheritanceAttributeOverrideTest.java @@ -0,0 +1,176 @@ +/* + * 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.inheritance.embeddable; + +import java.util.ArrayList; +import java.util.List; + +import org.hibernate.testing.jdbc.SQLStatementInspector; +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.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.AttributeOverride; +import jakarta.persistence.Column; +import jakarta.persistence.ElementCollection; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Marco Belladelli + */ +@DomainModel( annotatedClasses = { + EmbeddableInheritanceAttributeOverrideTest.TestEntity.class, + ParentEmbeddable.class, + ChildOneEmbeddable.class, + SubChildOneEmbeddable.class, + ChildTwoEmbeddable.class, +} ) +@SessionFactory( useCollectingStatementInspector = true ) +public class EmbeddableInheritanceAttributeOverrideTest { + @Test + public void testQuery(SessionFactoryScope scope) { + final SQLStatementInspector inspector = scope.getCollectingStatementInspector(); + inspector.clear(); + scope.inTransaction( session -> { + final TestEntity result = session.createQuery( + "from TestEntity where id = 1", + TestEntity.class + ).getSingleResult(); + inspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "embeddable_disc", 1 ); + inspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "child_two_col", 1 ); + assertThat( result.getEmbeddable().getParentProp() ).isEqualTo( "embeddable_1" ); + assertThat( result.getEmbeddable() ).isExactlyInstanceOf( ChildTwoEmbeddable.class ); + assertThat( ( (ChildTwoEmbeddable) result.getEmbeddable() ).getChildTwoProp() ).isEqualTo( 1L ); + } ); + inspector.clear(); + scope.inTransaction( session -> { + final ParentEmbeddable result = session.createQuery( + "select embeddable from TestEntity where id = 2", + ParentEmbeddable.class + ).getSingleResult(); + inspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "embeddable_disc", 1 ); + inspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "child_two_col", 1 ); + assertThat( result.getParentProp() ).isEqualTo( "embeddable_2" ); + assertThat( result ).isExactlyInstanceOf( SubChildOneEmbeddable.class ); + assertThat( ( (SubChildOneEmbeddable) result ).getSubChildOneProp() ).isEqualTo( 2.0 ); + } ); + } + + @Test + public void testFindElementCollection(SessionFactoryScope scope) { + final SQLStatementInspector inspector = scope.getCollectingStatementInspector(); + inspector.clear(); + scope.inTransaction( session -> { + final TestEntity testEntity = session.find( TestEntity.class, 1L ); + assertThat( testEntity.getEmbeddables() ).hasSize( 2 ).allSatisfy( e -> { + if ( e instanceof ChildOneEmbeddable ) { + assertThat( e ).isExactlyInstanceOf( ChildOneEmbeddable.class ) + .extracting( ParentEmbeddable::getParentProp ).isEqualTo( "collection_1" ); + } + else { + assertThat( e ).isExactlyInstanceOf( ChildTwoEmbeddable.class ) + .extracting( ParentEmbeddable::getParentProp ).isEqualTo( "collection_2" ); + } + } ); + inspector.assertExecutedCount( 2 ); + inspector.assertNumberOfOccurrenceInQueryNoSpace( 1, "embeddables_disc", 1 ); + inspector.assertNumberOfOccurrenceInQueryNoSpace( 1, "child_one_col", 1 ); + } ); + } + + + @Test + public void testUpdate(SessionFactoryScope scope) { + final SQLStatementInspector inspector = scope.getCollectingStatementInspector(); + scope.inTransaction( session -> { + final TestEntity result = session.find( TestEntity.class, 3L ); + assertThat( result.getEmbeddable().getParentProp() ).isEqualTo( "embeddable_3" ); + inspector.clear(); + // update values + ( (ChildTwoEmbeddable) result.getEmbeddable() ).setChildTwoProp( 33L ); + } ); + inspector.assertIsUpdate( 0 ); + inspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "child_two_col", 1 ); + scope.inTransaction( session -> { + final TestEntity result = session.find( TestEntity.class, 3L ); + assertThat( ( (ChildTwoEmbeddable) result.getEmbeddable() ).getChildTwoProp() ).isEqualTo( 33L ); + inspector.clear(); + result.setEmbeddable( new SubChildOneEmbeddable( "embeddable_3_new", 3, 3.0 ) ); + } ); + inspector.assertIsUpdate( 0 ); + inspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "embeddable_disc", 1 ); + inspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "sub_child_one_col", 1 ); + scope.inTransaction( session -> { + final TestEntity result = session.find( TestEntity.class, 3L ); + assertThat( result.getEmbeddable().getParentProp() ).isEqualTo( "embeddable_3_new" ); + assertThat( result.getEmbeddable() ).isExactlyInstanceOf( SubChildOneEmbeddable.class ); + assertThat( ( (SubChildOneEmbeddable) result.getEmbeddable() ).getChildOneProp() ).isEqualTo( 3 ); + assertThat( ( (SubChildOneEmbeddable) result.getEmbeddable() ).getSubChildOneProp() ).isEqualTo( 3.0 ); + } ); + } + + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final TestEntity testEntity = new TestEntity( 1L, new ChildTwoEmbeddable( "embeddable_1", 1L ) ); + testEntity.getEmbeddables().add( new ChildOneEmbeddable( "collection_1", 1 ) ); + testEntity.getEmbeddables().add( new ChildTwoEmbeddable( "collection_2", 1L ) ); + session.persist( testEntity ); + session.persist( new TestEntity( 2L, new SubChildOneEmbeddable( "embeddable_2", 2, 2.0 ) ) ); + session.persist( new TestEntity( 3L, new ChildTwoEmbeddable( "embeddable_3", 3L ) ) ); + } ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> session.createMutationQuery( "delete from TestEntity" ).executeUpdate() ); + } + + @Entity( name = "TestEntity" ) + static class TestEntity { + @Id + private Long id; + + @Embedded + @AttributeOverride( name = "{discriminator}", column = @Column( name = "embeddable_disc" ) ) + @AttributeOverride( name = "subChildOneProp", column = @Column( name = "sub_child_one_col" ) ) + @AttributeOverride( name = "childTwoProp", column = @Column( name = "child_two_col" ) ) + private ParentEmbeddable embeddable; + + @ElementCollection + @AttributeOverride( name = "{discriminator}", column = @Column( name = "embeddables_disc" ) ) + @AttributeOverride( name = "childOneProp", column = @Column( name = "child_one_col" ) ) + private List embeddables = new ArrayList<>(); + + public TestEntity() { + } + + public TestEntity(Long id, ParentEmbeddable embeddable) { + this.id = id; + this.embeddable = embeddable; + } + + public ParentEmbeddable getEmbeddable() { + return embeddable; + } + + public void setEmbeddable(ParentEmbeddable embeddable) { + this.embeddable = embeddable; + } + + public List getEmbeddables() { + return embeddables; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/embeddable/JsonAggregateEmbeddableInheritanceTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/embeddable/JsonAggregateEmbeddableInheritanceTest.java new file mode 100644 index 0000000000..b098f90539 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/embeddable/JsonAggregateEmbeddableInheritanceTest.java @@ -0,0 +1,162 @@ +/* + * 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.inheritance.embeddable; + +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; + +import org.hibernate.testing.orm.junit.DialectFeatureChecks; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.RequiresDialectFeature; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Marco Belladelli + */ +@DomainModel( annotatedClasses = { + JsonAggregateEmbeddableInheritanceTest.TestEntity.class, + ParentEmbeddable.class, + ChildOneEmbeddable.class, + SubChildOneEmbeddable.class, + ChildTwoEmbeddable.class, +} ) +@SessionFactory +@RequiresDialectFeature( feature = DialectFeatureChecks.SupportsJsonAggregate.class ) +public class JsonAggregateEmbeddableInheritanceTest { + @Test + public void testFind(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final TestEntity result = session.find( TestEntity.class, 1L ); + assertThat( result.getEmbeddable().getParentProp() ).isEqualTo( "embeddable_1" ); + assertThat( result.getEmbeddable() ).isExactlyInstanceOf( ChildOneEmbeddable.class ); + assertThat( ( (ChildOneEmbeddable) result.getEmbeddable() ).getChildOneProp() ).isEqualTo( 1 ); + } ); + scope.inTransaction( session -> { + final TestEntity result = session.find( TestEntity.class, 3L ); + assertThat( result.getEmbeddable().getParentProp() ).isEqualTo( "embeddable_3" ); + assertThat( result.getEmbeddable() ).isExactlyInstanceOf( ParentEmbeddable.class ); + } ); + } + + @Test + public void testQueryEntity(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final TestEntity result = session.createQuery( + "from TestEntity where id = 2", + TestEntity.class + ).getSingleResult(); + assertThat( result.getEmbeddable().getParentProp() ).isEqualTo( "embeddable_2" ); + assertThat( result.getEmbeddable() ).isExactlyInstanceOf( ChildTwoEmbeddable.class ); + assertThat( ( (ChildTwoEmbeddable) result.getEmbeddable() ).getChildTwoProp() ).isEqualTo( 2L ); + } ); + } + + @Test + public void testQueryEmbeddable(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final ParentEmbeddable result = session.createQuery( + "select embeddable from TestEntity where id = 4", + ParentEmbeddable.class + ).getSingleResult(); + assertThat( result.getParentProp() ).isEqualTo( "embeddable_4" ); + assertThat( result ).isExactlyInstanceOf( SubChildOneEmbeddable.class ); + assertThat( ( (SubChildOneEmbeddable) result ).getChildOneProp() ).isEqualTo( 4 ); + assertThat( ( (SubChildOneEmbeddable) result ).getSubChildOneProp() ).isEqualTo( 4.0 ); + } ); + } + + @Test + public void testQueryJoinedEmbeddable(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final ParentEmbeddable result = session.createQuery( + "select e from TestEntity t join t.embeddable e where t.id = 2", + ParentEmbeddable.class + ).getSingleResult(); + assertThat( result.getParentProp() ).isEqualTo( "embeddable_2" ); + assertThat( result ).isExactlyInstanceOf( ChildTwoEmbeddable.class ); + assertThat( ( (ChildTwoEmbeddable) result ).getChildTwoProp() ).isEqualTo( 2L ); + } ); + } + + @Test + public void testUpdate(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final TestEntity result = session.find( TestEntity.class, 5L ); + assertThat( result.getEmbeddable().getParentProp() ).isEqualTo( "embeddable_5" ); + assertThat( result.getEmbeddable() ).isExactlyInstanceOf( ChildOneEmbeddable.class ); + assertThat( ( (ChildOneEmbeddable) result.getEmbeddable() ).getChildOneProp() ).isEqualTo( 5 ); + // update values + result.getEmbeddable().setParentProp( "embeddable_5_new" ); + ( (ChildOneEmbeddable) result.getEmbeddable() ).setChildOneProp( 55 ); + } ); + scope.inTransaction( session -> { + final TestEntity result = session.find( TestEntity.class, 5L ); + assertThat( result.getEmbeddable().getParentProp() ).isEqualTo( "embeddable_5_new" ); + assertThat( ( (ChildOneEmbeddable) result.getEmbeddable() ).getChildOneProp() ).isEqualTo( 55 ); + result.setEmbeddable( new SubChildOneEmbeddable( "embeddable_6", 6, 6.0 ) ); + } ); + scope.inTransaction( session -> { + final TestEntity result = session.find( TestEntity.class, 5L ); + assertThat( result.getEmbeddable().getParentProp() ).isEqualTo( "embeddable_6" ); + assertThat( result.getEmbeddable() ).isExactlyInstanceOf( SubChildOneEmbeddable.class ); + assertThat( ( (SubChildOneEmbeddable) result.getEmbeddable() ).getChildOneProp() ).isEqualTo( 6 ); + assertThat( ( (SubChildOneEmbeddable) result.getEmbeddable() ).getSubChildOneProp() ).isEqualTo( 6.0 ); + } ); + } + + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + session.persist( new TestEntity( 1L, new ChildOneEmbeddable( "embeddable_1", 1 ) ) ); + session.persist( new TestEntity( 2L, new ChildTwoEmbeddable( "embeddable_2", 2L ) ) ); + session.persist( new TestEntity( 3L, new ParentEmbeddable( "embeddable_3" ) ) ); + session.persist( new TestEntity( 4L, new SubChildOneEmbeddable( "embeddable_4", 4, 4.0 ) ) ); + session.persist( new TestEntity( 5L, new ChildOneEmbeddable( "embeddable_5", 5 ) ) ); + } ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> session.createMutationQuery( "delete from TestEntity" ).executeUpdate() ); + } + + @Entity( name = "TestEntity" ) + static class TestEntity { + @Id + private Long id; + + @Embedded + @JdbcTypeCode( SqlTypes.JSON ) + private ParentEmbeddable embeddable; + + public TestEntity() { + } + + public TestEntity(Long id, ParentEmbeddable embeddable) { + this.id = id; + this.embeddable = embeddable; + } + + public ParentEmbeddable getEmbeddable() { + return embeddable; + } + + public void setEmbeddable(ParentEmbeddable embeddable) { + this.embeddable = embeddable; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/embeddable/ParentEmbeddable.java b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/embeddable/ParentEmbeddable.java new file mode 100644 index 0000000000..139f603923 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/embeddable/ParentEmbeddable.java @@ -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.inheritance.embeddable; + +import java.io.Serializable; + +import jakarta.persistence.DiscriminatorColumn; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Embeddable; + +/** + * @author Marco Belladelli + */ +//tag::embeddable-inheritance-parent-example[] +@Embeddable +@DiscriminatorValue( "parent" ) +@DiscriminatorColumn( name = "embeddable_type" ) +class ParentEmbeddable implements Serializable { + private String parentProp; + + // ... +//end::embeddable-inheritance-parent-example[] + + public ParentEmbeddable() { + } + + public ParentEmbeddable(String parentProp) { + this.parentProp = parentProp; + } + + public String getParentProp() { + return parentProp; + } + + public void setParentProp(String parentProp) { + this.parentProp = parentProp; + } +//tag::embeddable-inheritance-parent-example[] +} +//end::embeddable-inheritance-parent-example[] diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/embeddable/StructAggregateEmbeddableInheritanceTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/embeddable/StructAggregateEmbeddableInheritanceTest.java new file mode 100644 index 0000000000..853577477d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/embeddable/StructAggregateEmbeddableInheritanceTest.java @@ -0,0 +1,343 @@ +/* + * 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.inheritance.embeddable; + +import java.util.Set; + +import org.hibernate.annotations.Struct; +import org.hibernate.boot.ResourceStreamLocator; +import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; +import org.hibernate.boot.model.relational.NamedAuxiliaryDatabaseObject; +import org.hibernate.boot.model.relational.Namespace; +import org.hibernate.boot.spi.AdditionalMappingContributions; +import org.hibernate.boot.spi.AdditionalMappingContributor; +import org.hibernate.boot.spi.InFlightMetadataCollector; +import org.hibernate.boot.spi.MetadataBuildingContext; +import org.hibernate.dialect.DB2Dialect; +import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.OracleDialect; +import org.hibernate.dialect.PostgreSQLDialect; +import org.hibernate.dialect.PostgresPlusDialect; +import org.hibernate.procedure.ProcedureCall; +import org.hibernate.query.procedure.ProcedureParameter; + +import org.hibernate.testing.jdbc.SharedDriverManagerTypeCacheClearingIntegrator; +import org.hibernate.testing.orm.junit.BootstrapServiceRegistry; +import org.hibernate.testing.orm.junit.DialectFeatureChecks; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.RequiresDialectFeature; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.SkipForDialect; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.ParameterMode; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Marco Belladelli + */ +@BootstrapServiceRegistry( + javaServices = @BootstrapServiceRegistry.JavaService( + role = AdditionalMappingContributor.class, + impl = StructAggregateEmbeddableInheritanceTest.class + ), + // Clear the type cache, otherwise we might run into ORA-21700: object does not exist or is marked for delete + integrators = SharedDriverManagerTypeCacheClearingIntegrator.class +) +@DomainModel( annotatedClasses = { + StructAggregateEmbeddableInheritanceTest.TestEntity.class, + ParentEmbeddable.class, + ChildOneEmbeddable.class, + SubChildOneEmbeddable.class, + ChildTwoEmbeddable.class, +} ) +@SessionFactory +@RequiresDialectFeature( feature = DialectFeatureChecks.SupportsStructAggregate.class ) +public class StructAggregateEmbeddableInheritanceTest implements AdditionalMappingContributor { + @Test + public void testFind(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final TestEntity result = session.find( TestEntity.class, 1L ); + assertThat( result.getEmbeddable().getParentProp() ).isEqualTo( "embeddable_1" ); + assertThat( result.getEmbeddable() ).isExactlyInstanceOf( ChildOneEmbeddable.class ); + assertThat( ( (ChildOneEmbeddable) result.getEmbeddable() ).getChildOneProp() ).isEqualTo( 1 ); + } ); + scope.inTransaction( session -> { + final TestEntity result = session.find( TestEntity.class, 3L ); + assertThat( result.getEmbeddable().getParentProp() ).isEqualTo( "embeddable_3" ); + assertThat( result.getEmbeddable() ).isExactlyInstanceOf( ParentEmbeddable.class ); + } ); + } + + @Test + public void testQueryEntity(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final TestEntity result = session.createQuery( + "from TestEntity where id = 2", + TestEntity.class + ).getSingleResult(); + assertThat( result.getEmbeddable().getParentProp() ).isEqualTo( "embeddable_2" ); + assertThat( result.getEmbeddable() ).isExactlyInstanceOf( ChildTwoEmbeddable.class ); + assertThat( ( (ChildTwoEmbeddable) result.getEmbeddable() ).getChildTwoProp() ).isEqualTo( 2L ); + } ); + } + + @Test + public void testQueryEmbeddable(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final ParentEmbeddable result = session.createQuery( + "select embeddable from TestEntity where id = 4", + ParentEmbeddable.class + ).getSingleResult(); + assertThat( result.getParentProp() ).isEqualTo( "embeddable_4" ); + assertThat( result ).isExactlyInstanceOf( SubChildOneEmbeddable.class ); + assertThat( ( (SubChildOneEmbeddable) result ).getChildOneProp() ).isEqualTo( 4 ); + assertThat( ( (SubChildOneEmbeddable) result ).getSubChildOneProp() ).isEqualTo( 4.0 ); + } ); + } + + @Test + public void testQueryJoinedEmbeddable(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final ParentEmbeddable result = session.createQuery( + "select e from TestEntity t join t.embeddable e where t.id = 2", + ParentEmbeddable.class + ).getSingleResult(); + assertThat( result.getParentProp() ).isEqualTo( "embeddable_2" ); + assertThat( result ).isExactlyInstanceOf( ChildTwoEmbeddable.class ); + assertThat( ( (ChildTwoEmbeddable) result ).getChildTwoProp() ).isEqualTo( 2L ); + } ); + } + + @Test + public void testUpdate(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final TestEntity result = session.find( TestEntity.class, 5L ); + assertThat( result.getEmbeddable().getParentProp() ).isEqualTo( "embeddable_5" ); + assertThat( result.getEmbeddable() ).isExactlyInstanceOf( ChildOneEmbeddable.class ); + assertThat( ( (ChildOneEmbeddable) result.getEmbeddable() ).getChildOneProp() ).isEqualTo( 5 ); + // update values + result.getEmbeddable().setParentProp( "embeddable_5_new" ); + ( (ChildOneEmbeddable) result.getEmbeddable() ).setChildOneProp( 55 ); + } ); + scope.inTransaction( session -> { + final TestEntity result = session.find( TestEntity.class, 5L ); + assertThat( result.getEmbeddable().getParentProp() ).isEqualTo( "embeddable_5_new" ); + assertThat( ( (ChildOneEmbeddable) result.getEmbeddable() ).getChildOneProp() ).isEqualTo( 55 ); + result.setEmbeddable( new SubChildOneEmbeddable( "embeddable_6", 6, 6.0 ) ); + } ); + scope.inTransaction( session -> { + final TestEntity result = session.find( TestEntity.class, 5L ); + assertThat( result.getEmbeddable().getParentProp() ).isEqualTo( "embeddable_6" ); + assertThat( result.getEmbeddable() ).isExactlyInstanceOf( SubChildOneEmbeddable.class ); + assertThat( ( (SubChildOneEmbeddable) result.getEmbeddable() ).getChildOneProp() ).isEqualTo( 6 ); + assertThat( ( (SubChildOneEmbeddable) result.getEmbeddable() ).getSubChildOneProp() ).isEqualTo( 6.0 ); + } ); + } + + @Test + public void testFunction(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final ProcedureCall structFunction = session.createStoredProcedureCall( "structFunction" ) + .markAsFunctionCall( ParentEmbeddable.class ); + //noinspection unchecked + final ParentEmbeddable result = (ParentEmbeddable) structFunction.getSingleResult(); + assertThat( result.getParentProp() ).isEqualTo( "function_embeddable" ); + assertThat( result ).isExactlyInstanceOf( SubChildOneEmbeddable.class ); + assertThat( ( (SubChildOneEmbeddable) result ).getChildOneProp() ).isEqualTo( 1 ); + assertThat( ( (SubChildOneEmbeddable) result ).getSubChildOneProp() ).isEqualTo( 1.0 ); + } ); + } + + @Test + @SkipForDialect( dialectClass = PostgreSQLDialect.class, majorVersion = 10, reason = "Procedures were only introduced in version 11" ) + @SkipForDialect( dialectClass = PostgresPlusDialect.class, majorVersion = 10, reason = "Procedures were only introduced in version 11" ) + @SkipForDialect( dialectClass = DB2Dialect.class, reason = "DB2 does not support struct types in procedures" ) + public void testProcedure(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final Dialect dialect = session.getJdbcServices().getDialect(); + final ParameterMode parameterMode; + if ( dialect instanceof PostgreSQLDialect ) { + parameterMode = ParameterMode.INOUT; + } + else { + parameterMode = ParameterMode.OUT; + } + final ProcedureCall structFunction = session.createStoredProcedureCall( "structProcedure" ); + final ProcedureParameter resultParameter = structFunction.registerParameter( + "structType", + ParentEmbeddable.class, + parameterMode + ); + structFunction.setParameter( resultParameter, null ); + final ParentEmbeddable result = structFunction.getOutputs().getOutputParameterValue( resultParameter ); + assertThat( result ).isInstanceOf( ParentEmbeddable.class ); + assertThat( result.getParentProp() ).isEqualTo( "procedure_embeddable" ); + assertThat( result ).isExactlyInstanceOf( ChildTwoEmbeddable.class ); + assertThat( ( (ChildTwoEmbeddable) result ).getChildTwoProp() ).isEqualTo( 2 ); + } ); + } + + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + session.persist( new TestEntity( 1L, new ChildOneEmbeddable( "embeddable_1", 1 ) ) ); + session.persist( new TestEntity( 2L, new ChildTwoEmbeddable( "embeddable_2", 2L ) ) ); + session.persist( new TestEntity( 3L, new ParentEmbeddable( "embeddable_3" ) ) ); + session.persist( new TestEntity( 4L, new SubChildOneEmbeddable( "embeddable_4", 4, 4.0 ) ) ); + session.persist( new TestEntity( 5L, new ChildOneEmbeddable( "embeddable_5", 5 ) ) ); + } ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> session.createMutationQuery( "delete from TestEntity" ).executeUpdate() ); + } + + @Override + public void contribute( + AdditionalMappingContributions contributions, + InFlightMetadataCollector metadata, + ResourceStreamLocator resourceStreamLocator, + MetadataBuildingContext buildingContext) { + final Namespace namespace = new Namespace( + PhysicalNamingStrategyStandardImpl.INSTANCE, + null, + new Namespace.Name( null, null ) + ); + + //--------------------------------------------------------- + // PostgreSQL + //--------------------------------------------------------- + + contributions.contributeAuxiliaryDatabaseObject( + new NamedAuxiliaryDatabaseObject( + "PostgreSQL structFunction", + namespace, + "create function structFunction() returns inheritance_embeddable as $$ declare result inheritance_embeddable; begin result.parentProp = 'function_embeddable'; result.childOneProp = 1; result.subChildOneProp = 1.0; result.childTwoProp = null; result.embeddable_type = 'sub_child_one'; return result; end $$ language plpgsql", + "drop function structFunction", + Set.of( PostgreSQLDialect.class.getName() ) + ) + ); + contributions.contributeAuxiliaryDatabaseObject( + new NamedAuxiliaryDatabaseObject( + "PostgreSQL structProcedure", + namespace, + "create procedure structProcedure(INOUT result inheritance_embeddable) AS $$ declare res inheritance_embeddable; begin res.parentProp = 'procedure_embeddable'; res.childOneProp = null; res.subChildOneProp = null; res.childTwoProp = 2; res.embeddable_type = 'ChildTwoEmbeddable'; result = res; end $$ language plpgsql", + "drop procedure structProcedure", + Set.of( PostgreSQLDialect.class.getName() ) + ) + ); + + //--------------------------------------------------------- + // PostgrePlus + //--------------------------------------------------------- + + contributions.contributeAuxiliaryDatabaseObject( + new NamedAuxiliaryDatabaseObject( + "PostgrePlus structFunction", + namespace, + "create function structFunction() returns inheritance_embeddable as $$ declare result inheritance_embeddable; begin result.parentProp = 'function_embeddable'; result.childOneProp = 1; result.subChildOneProp = 1.0; result.childTwoProp = null; result.embeddable_type = 'sub_child_one'; return result; end $$ language plpgsql", + "drop function structFunction", + Set.of( PostgresPlusDialect.class.getName() ) + ) + ); + contributions.contributeAuxiliaryDatabaseObject( + new NamedAuxiliaryDatabaseObject( + "PostgrePlus structProcedure", + namespace, + "create procedure structProcedure(result INOUT inheritance_embeddable) AS $$ declare res inheritance_embeddable; begin res.parentProp = 'procedure_embeddable'; res.childOneProp = null; res.subChildOneProp = null; res.childTwoProp = 2; res.embeddable_type = 'ChildTwoEmbeddable'; result = res; end $$ language plpgsql", + "drop procedure structProcedure", + Set.of( PostgresPlusDialect.class.getName() ) + ) + ); + + //--------------------------------------------------------- + // DB2 + //--------------------------------------------------------- + + contributions.contributeAuxiliaryDatabaseObject( + new NamedAuxiliaryDatabaseObject( + "DB2 structFunction", + namespace, + "create function structFunction() returns inheritance_embeddable language sql RETURN select inheritance_embeddable()..parentProp('function_embeddable')..childOneProp(1)..subChildOneProp(1.0)..embeddable_type('sub_child_one') from (values (1)) t", + "drop function structFunction", + Set.of( DB2Dialect.class.getName() ) + ) + ); + + //--------------------------------------------------------- + // Oracle + //--------------------------------------------------------- + + contributions.contributeAuxiliaryDatabaseObject( + new NamedAuxiliaryDatabaseObject( + "Oracle structFunction", + namespace, + "create function structFunction return inheritance_embeddable is result inheritance_embeddable; begin " + + "result := inheritance_embeddable(" + + "parentProp => 'function_embeddable'," + + "childOneProp => 1," + + "subChildOneProp => 1.0," + + "childTwoProp => null," + + "embeddable_type => 'sub_child_one'" + + "); return result; end;", + "drop function structFunction", + Set.of( OracleDialect.class.getName() ) + ) + ); + contributions.contributeAuxiliaryDatabaseObject( + new NamedAuxiliaryDatabaseObject( + "Oracle structProcedure", + namespace, + "create procedure structProcedure(result OUT inheritance_embeddable) AS begin " + + "result := inheritance_embeddable(" + + "parentProp => 'procedure_embeddable'," + + "childOneProp => null," + + "subChildOneProp => null," + + "childTwoProp => 2," + + "embeddable_type => 'ChildTwoEmbeddable'" + + "); end;", + "drop procedure structProcedure", + Set.of( OracleDialect.class.getName() ) + ) + ); + } + + @Entity( name = "TestEntity" ) + static class TestEntity { + @Id + private Long id; + + @Embedded + @Struct( name = "inheritance_embeddable" ) + private ParentEmbeddable embeddable; + + public TestEntity() { + } + + public TestEntity(Long id, ParentEmbeddable embeddable) { + this.id = id; + this.embeddable = embeddable; + } + + public ParentEmbeddable getEmbeddable() { + return embeddable; + } + + public void setEmbeddable(ParentEmbeddable embeddable) { + this.embeddable = embeddable; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/embeddable/SubChildOneEmbeddable.java b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/embeddable/SubChildOneEmbeddable.java new file mode 100644 index 0000000000..a8c4d590f7 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/embeddable/SubChildOneEmbeddable.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.orm.test.inheritance.embeddable; + +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Embeddable; + +/** + * @author Marco Belladelli + */ +//tag::embeddable-inheritance-sub-child-one-example[] +@Embeddable +@DiscriminatorValue( "sub_child_one" ) +class SubChildOneEmbeddable extends ChildOneEmbeddable { + private Double subChildOneProp; + + // ... +//end::embeddable-inheritance-sub-child-one-example[] + + public SubChildOneEmbeddable() { + } + + public SubChildOneEmbeddable(String parentProp, Integer childOneProp, Double subChildOneProp) { + super( parentProp, childOneProp ); + this.subChildOneProp = subChildOneProp; + } + + public Double getSubChildOneProp() { + return subChildOneProp; + } + + public void setSubChildOneProp(Double subChildOneProp) { + this.subChildOneProp = subChildOneProp; + } +//tag::embeddable-inheritance-sub-child-one-example[] +} +//end::embeddable-inheritance-sub-child-one-example[] diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/metamodel/attributeInSuper/AbstractWorkOrderId.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/metamodel/attributeInSuper/AbstractWorkOrderId.java new file mode 100644 index 0000000000..99fa8ec7e8 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/metamodel/attributeInSuper/AbstractWorkOrderId.java @@ -0,0 +1,21 @@ +/* + * 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 . + */ +package org.hibernate.orm.test.jpa.metamodel.attributeInSuper; + +import java.io.Serializable; + +import jakarta.persistence.MappedSuperclass; + +/** + * @author Steve Ebersole + */ +@MappedSuperclass +public class AbstractWorkOrderId implements Serializable { + private String workOrder; + private Long plantId; + /* other stuffs */ +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/metamodel/attributeInSuper/WorkOrderComponentId.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/metamodel/attributeInSuper/WorkOrderComponentId.java index 06df895882..deda2213a8 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/metamodel/attributeInSuper/WorkOrderComponentId.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/metamodel/attributeInSuper/WorkOrderComponentId.java @@ -12,7 +12,7 @@ import jakarta.persistence.Embeddable; * @author Steve Ebersole */ @Embeddable -public class WorkOrderComponentId extends WorkOrderId { +public class WorkOrderComponentId extends AbstractWorkOrderId { private Long lineNumber; /* other stuffs */ } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/metamodel/attributeInSuper/WorkOrderId.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/metamodel/attributeInSuper/WorkOrderId.java index a083ef18f8..a742f05bd0 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/metamodel/attributeInSuper/WorkOrderId.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/metamodel/attributeInSuper/WorkOrderId.java @@ -6,17 +6,11 @@ */ package org.hibernate.orm.test.jpa.metamodel.attributeInSuper; -import java.io.Serializable; import jakarta.persistence.Embeddable; -import jakarta.persistence.MappedSuperclass; /** * @author Steve Ebersole */ @Embeddable -@MappedSuperclass -public class WorkOrderId implements Serializable { - private String workOrder; - private Long plantId; - /* other stuffs */ +public class WorkOrderId extends AbstractWorkOrderId { } diff --git a/release-announcement.adoc b/release-announcement.adoc index 60e3af6b09..59463f047a 100644 --- a/release-announcement.adoc +++ b/release-announcement.adoc @@ -149,3 +149,54 @@ or obtain a substring by start and end index. `stringPath[2]` is syntax sugar for `substring(stringPath, 2, 1)` and returns a `Character`. `stringPath[2:3]` is syntax sugar for `substring(stringPath, 2, 3-2+1)`, where `3-2+1` is the expression to determine the desired string length. + + +[[embeddable-inheritance]] +== Embeddable Inheritance + +Another new feature of this version is discriminator-based inheritance for `@Embeddable` types. An `@Embeddable` class +may be extended by other `@Embeddable` classes, in which case the `@Embedded` properties using that type will +rely on an additional discriminator column to store information about the composite value's subtype. + +When retrieving the inherited embedded property, Hibernate will read the discriminator value and instantiate the +correct `@Embeddable` subtype with its corresponding properties. + +For example, a mapping like this: +[source,java] +---- +@Embeddable +@DiscriminatorValue( "parent" ) +@DiscriminatorColumn( name = "embeddable_type" ) +class ParentEmbeddable implements Serializable { + private String parentProp; + // ... +} + +@Embeddable +@DiscriminatorValue( "child_one" ) +class ChildOneEmbeddable extends ParentEmbeddable { + private Integer childOneProp; + // ... +} + +@Entity +class TestEntity { + @Embedded + private ParentEmbeddable embeddable; + // ... +} +---- + +Will result in the following table structure: +[source,sql] +---- +create table TestEntity ( + -- ... + embeddable_type varchar(31) not null, + parentProp varchar(255), + childOneProp integer, + -- ... +) +---- + +For more detailed information please refer to the link:{user-guide-url}#embeddable-inheritance[Embeddable inheritance] user guide chapter. \ No newline at end of file