diff --git a/src/main/java/org/hibernate/boot/models/bind/internal/UnionTable.java b/src/main/java/org/hibernate/boot/models/bind/internal/UnionTable.java index 78a01c3..4a7fc35 100644 --- a/src/main/java/org/hibernate/boot/models/bind/internal/UnionTable.java +++ b/src/main/java/org/hibernate/boot/models/bind/internal/UnionTable.java @@ -15,7 +15,7 @@ */ public record UnionTable( Identifier logicalName, - UnionTable base, + TableReference base, DenormalizedTable binding, boolean exportable) implements TableReference { } diff --git a/src/main/java/org/hibernate/boot/models/bind/internal/binders/AttributeBinder.java b/src/main/java/org/hibernate/boot/models/bind/internal/binders/AttributeBinder.java index 1b3f32f..7aac42c 100644 --- a/src/main/java/org/hibernate/boot/models/bind/internal/binders/AttributeBinder.java +++ b/src/main/java/org/hibernate/boot/models/bind/internal/binders/AttributeBinder.java @@ -11,43 +11,35 @@ import java.util.List; import org.hibernate.annotations.Immutable; -import org.hibernate.annotations.JavaType; -import org.hibernate.annotations.JdbcType; -import org.hibernate.annotations.JdbcTypeCode; import org.hibernate.annotations.Mutability; -import org.hibernate.annotations.Nationalized; import org.hibernate.annotations.OptimisticLock; import org.hibernate.annotations.TimeZoneColumn; import org.hibernate.annotations.TimeZoneStorage; import org.hibernate.annotations.TimeZoneStorageType; import org.hibernate.boot.model.convert.internal.ClassBasedConverterDescriptor; import org.hibernate.boot.model.naming.Identifier; +import org.hibernate.boot.models.AnnotationPlacementException; import org.hibernate.boot.models.bind.internal.BindingHelper; +import org.hibernate.boot.models.bind.internal.SecondPass; import org.hibernate.boot.models.bind.spi.BindingContext; import org.hibernate.boot.models.bind.spi.BindingOptions; import org.hibernate.boot.models.bind.spi.BindingState; import org.hibernate.boot.models.bind.spi.TableReference; +import org.hibernate.boot.models.categorize.spi.AttributeMetadata; import org.hibernate.mapping.BasicValue; import org.hibernate.mapping.Property; import org.hibernate.models.ModelsException; -import org.hibernate.boot.models.AnnotationPlacementException; -import org.hibernate.boot.models.bind.internal.SecondPass; -import org.hibernate.boot.models.categorize.spi.AttributeMetadata; import org.hibernate.models.spi.AnnotationUsage; import org.hibernate.models.spi.ClassDetails; import org.hibernate.models.spi.MemberDetails; -import org.hibernate.type.descriptor.java.BasicJavaType; import org.hibernate.type.descriptor.java.MutabilityPlan; import jakarta.persistence.AttributeConverter; import jakarta.persistence.Column; import jakarta.persistence.Convert; -import jakarta.persistence.Enumerated; -import jakarta.persistence.Lob; import jakarta.persistence.Temporal; import jakarta.persistence.TemporalType; -import static jakarta.persistence.EnumType.ORDINAL; import static org.hibernate.annotations.TimeZoneStorageType.AUTO; import static org.hibernate.annotations.TimeZoneStorageType.COLUMN; import static org.hibernate.boot.models.categorize.spi.AttributeMetadata.AttributeNature.BASIC; @@ -105,136 +97,46 @@ public void processSecondPasses() { private BasicValue createBasicValue() { final BasicValue basicValue = new BasicValue( bindingState.getMetadataBuildingContext() ); // probably we don't need this as a second pass... - registerValueSecondPass( new BasicValueSecondPass( attributeMetadata, getBinding(), basicValue, bindingState, bindingContext ) ); + registerValueSecondPass( new BasicValueSecondPass( attributeMetadata, getBinding(), basicValue, bindingOptions, bindingState, bindingContext ) ); return basicValue; } - private static void processColumn( + public static void bindImplicitJavaType( MemberDetails member, - Property property, + @SuppressWarnings("unused") Property property, BasicValue basicValue, - BindingState bindingState, - BindingContext bindingContext) { - // todo : implicit column - final var columnAnn = member.getAnnotationUsage( Column.class ); - final var column = ColumnBinder.bindColumn( columnAnn, property::getName ); - - if ( columnAnn != null ) { - final var tableName = columnAnn.getString( "table", null ); - TableReference tableByName = null; - if ( tableName != null ) { - final Identifier identifier = Identifier.toIdentifier( tableName ); - tableByName = bindingState.getTableByName( identifier.getCanonicalName() ); - basicValue.setTable( tableByName.binding() ); - } - } - - basicValue.addColumn( column ); + @SuppressWarnings("unused") BindingOptions bindingOptions, + @SuppressWarnings("unused") BindingState bindingState, + @SuppressWarnings("unused") BindingContext bindingContext) { + basicValue.setImplicitJavaTypeAccess( (typeConfiguration) -> member.getType().toJavaClass() ); } - private static void processLob(MemberDetails member, BasicValue basicValue) { - if ( member.getAnnotationUsage( Lob.class ) != null ) { - basicValue.makeLob(); - } - } - - private static void processNationalized(MemberDetails member, BasicValue basicValue) { - if ( member.getAnnotationUsage( Nationalized.class ) != null ) { - basicValue.makeNationalized(); - } - } - - private static void processEnumerated(MemberDetails member, BasicValue basicValue) { - final AnnotationUsage enumerated = member.getAnnotationUsage( Enumerated.class ); - if ( enumerated == null ) { - return; - } - - basicValue.setEnumerationStyle( enumerated.getEnum( "value", ORDINAL ) ); - } - - private static void processConversion( + public static void bindOptimisticLocking( MemberDetails member, - BasicValue basicValue, - BindingContext bindingContext) { - // todo : do we need to account for auto-applied converters here? - final var convertAnn = member.getAnnotationUsage( Convert.class ); - if ( convertAnn == null ) { - return; - } - - if ( convertAnn.getBoolean( "disableConversion" ) ) { - return; - } - - if ( convertAnn.getString( "attributeName" ) != null ) { - throw new ModelsException( "@Convert#attributeName should not be specified on basic mappings - " + member.getName() ); - } - - final ClassDetails converterClassDetails = convertAnn.getClassDetails( "converter" ); - final Class> javaClass = converterClassDetails.toJavaClass(); - basicValue.setJpaAttributeConverterDescriptor( new ClassBasedConverterDescriptor( - javaClass, - bindingContext.getClassmateContext() - ) ); - } - - private static void processJavaType(MemberDetails member, BasicValue basicValue) { - // todo : do we need to account for JavaTypeRegistration here? - final var javaTypeAnn = member.getAnnotationUsage( JavaType.class ); - if ( javaTypeAnn == null ) { - return; - } - - basicValue.setExplicitJavaTypeAccess( (typeConfiguration) -> { - final var classDetails = javaTypeAnn.getClassDetails( "value" ); - final Class> javaClass = classDetails.toJavaClass(); - try { - return javaClass.getConstructor().newInstance(); - } - catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { - final ModelsException modelsException = new ModelsException( "Error instantiating local @JavaType - " + member.getName() ); - modelsException.addSuppressed( e ); - throw modelsException; - } - } ); - } - - private static void processJdbcType(MemberDetails member, BasicValue basicValue) { - // todo : do we need to account for JdbcTypeRegistration here? - final var jdbcTypeAnn = member.getAnnotationUsage( JdbcType.class ); - final var jdbcTypeCodeAnn = member.getAnnotationUsage( JdbcTypeCode.class ); - - if ( jdbcTypeAnn != null ) { - if ( jdbcTypeCodeAnn != null ) { - throw new AnnotationPlacementException( - "Illegal combination of @JdbcType and @JdbcTypeCode - " + member.getName() - ); + Property property, + @SuppressWarnings("unused") BasicValue basicValue, + @SuppressWarnings("unused") BindingOptions bindingOptions, + @SuppressWarnings("unused") BindingState bindingState, + @SuppressWarnings("unused") BindingContext bindingContext) { + final var annotationUsage = member.getAnnotationUsage( OptimisticLock.class ); + if ( annotationUsage != null ) { + if ( annotationUsage.getBoolean( "excluded" ) ) { + property.setOptimisticLocked( false ); + return; } - - basicValue.setExplicitJdbcTypeAccess( (typeConfiguration) -> { - final var classDetails = jdbcTypeAnn.getClassDetails( "value" ); - final Class javaClass = classDetails.toJavaClass(); - try { - return javaClass.getConstructor().newInstance(); - } - catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { - final ModelsException modelsException = new ModelsException( "Error instantiating local @JdbcType - " + member.getName() ); - modelsException.addSuppressed( e ); - throw modelsException; - } - } ); - } - else if ( jdbcTypeCodeAnn != null ) { - final Integer typeCode = jdbcTypeCodeAnn.getInteger( "value" ); - basicValue.setExplicitJdbcTypeCode( typeCode ); } + + property.setOptimisticLocked( true ); } - private static void processMutability( + public static void bindMutability( MemberDetails member, - Property property, BasicValue basicValue) { + Property property, + BasicValue basicValue, + @SuppressWarnings("unused") BindingOptions bindingOptions, + @SuppressWarnings("unused") BindingState bindingState, + @SuppressWarnings("unused") BindingContext bindingContext) { final var mutabilityAnn = member.getAnnotationUsage( Mutability.class ); final var immutableAnn = member.getAnnotationUsage( Immutable.class ); @@ -263,21 +165,6 @@ else if ( mutabilityAnn != null ) { } } - private static void processOptimisticLocking( - MemberDetails member, - Property property, - BasicValue basicValue) { - final var annotationUsage = member.getAnnotationUsage( OptimisticLock.class ); - if ( annotationUsage != null ) { - if ( annotationUsage.getBoolean( "excluded" ) ) { - property.setOptimisticLocked( false ); - return; - } - } - - property.setOptimisticLocked( true ); - } - @FunctionalInterface interface ValueSecondPass extends SecondPass { @@ -294,30 +181,80 @@ private record BasicValueSecondPass( AttributeMetadata attributeMetadata, Property property, BasicValue basicValue, + BindingOptions bindingOptions, BindingState bindingState, BindingContext bindingContext) implements ValueSecondPass { @Override - public boolean processValue() { - final MemberDetails member = attributeMetadata.getMember(); - processColumn( member, property, basicValue, bindingState, bindingContext ); - processLob( member, basicValue ); - processNationalized( member, basicValue ); - processEnumerated( member, basicValue ); - processConversion( member, basicValue, bindingContext ); - processImplicitJavaType( member, basicValue ); - processJavaType( member, basicValue ); - processJdbcType( member, basicValue ); - processMutability( member, property, basicValue ); - processOptimisticLocking( member, property, basicValue ); - processTemporalPrecision( member, basicValue ); - processTimeZoneStorage( member, property, basicValue ); - - return true; + public boolean processValue() { + final MemberDetails member = attributeMetadata.getMember(); + bindImplicitJavaType( member, property, basicValue, bindingOptions, bindingState, bindingContext ); + bindMutability( member, property, basicValue, bindingOptions, bindingState, bindingContext ); + bindOptimisticLocking( member, property, basicValue, bindingOptions, bindingState, bindingContext ); + bindConversion( member, property, basicValue, bindingOptions, bindingState, bindingContext ); + + processColumn( member, property, basicValue, bindingState, bindingContext ); + + BasicValueBinder.bindJavaType( member, property, basicValue, bindingOptions, bindingState, bindingContext ); + BasicValueBinder.bindJdbcType( member, property, basicValue, bindingOptions, bindingState, bindingContext ); + BasicValueBinder.bindLob( member, property, basicValue, bindingOptions, bindingState, bindingContext ); + BasicValueBinder.bindNationalized( member, property, basicValue, bindingOptions, bindingState, bindingContext ); + BasicValueBinder.bindEnumerated( member, property, basicValue, bindingOptions, bindingState, bindingContext ); + BasicValueBinder.bindTemporalPrecision( member, property, basicValue, bindingOptions, bindingState, bindingContext ); + BasicValueBinder.bindTimeZoneStorage( member, property, basicValue, bindingOptions, bindingState, bindingContext ); + + return true; + } + + private static void processColumn( + MemberDetails member, + Property property, + BasicValue basicValue, + BindingState bindingState, + @SuppressWarnings("unused") BindingContext bindingContext) { + // todo : implicit column + final var columnAnn = member.getAnnotationUsage( Column.class ); + final var column = ColumnBinder.bindColumn( columnAnn, property::getName ); + + if ( columnAnn != null ) { + final var tableName = columnAnn.getString( "table", null ); + if ( tableName != null ) { + final Identifier identifier = Identifier.toIdentifier( tableName ); + final TableReference tableByName = bindingState.getTableByName( identifier.getCanonicalName() ); + basicValue.setTable( tableByName.binding() ); + } + } + + basicValue.addColumn( column ); + } + + private static void bindConversion( + MemberDetails member, + @SuppressWarnings("unused") Property property, + @SuppressWarnings("unused") BasicValue basicValue, + @SuppressWarnings("unused") BindingOptions bindingOptions, + @SuppressWarnings("unused") BindingState bindingState, + @SuppressWarnings("unused") BindingContext bindingContext) { + // todo : do we need to account for auto-applied converters here? + final var convertAnn = member.getAnnotationUsage( Convert.class ); + if ( convertAnn == null ) { + return; + } + + if ( convertAnn.getBoolean( "disableConversion" ) ) { + return; + } + + if ( convertAnn.getString( "attributeName" ) != null ) { + throw new ModelsException( "@Convert#attributeName should not be specified on basic mappings - " + member.getName() ); } - private void processImplicitJavaType(MemberDetails member, BasicValue basicValue) { - basicValue.setImplicitJavaTypeAccess( (typeConfiguration) -> member.getType().toJavaClass() ); + final ClassDetails converterClassDetails = convertAnn.getClassDetails( "converter" ); + final Class> javaClass = converterClassDetails.toJavaClass(); + basicValue.setJpaAttributeConverterDescriptor( new ClassBasedConverterDescriptor( + javaClass, + bindingContext.getClassmateContext() + ) ); } private void processTemporalPrecision(MemberDetails member, BasicValue basicValue) { diff --git a/src/main/java/org/hibernate/boot/models/bind/internal/binders/BasicValueBinder.java b/src/main/java/org/hibernate/boot/models/bind/internal/binders/BasicValueBinder.java new file mode 100644 index 0000000..ee21438 --- /dev/null +++ b/src/main/java/org/hibernate/boot/models/bind/internal/binders/BasicValueBinder.java @@ -0,0 +1,202 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.boot.models.bind.internal.binders; + +import java.lang.reflect.InvocationTargetException; + +import org.hibernate.annotations.JavaType; +import org.hibernate.annotations.JdbcType; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.annotations.Nationalized; +import org.hibernate.annotations.TimeZoneColumn; +import org.hibernate.annotations.TimeZoneStorage; +import org.hibernate.annotations.TimeZoneStorageType; +import org.hibernate.boot.model.naming.Identifier; +import org.hibernate.boot.models.AnnotationPlacementException; +import org.hibernate.boot.models.bind.spi.BindingContext; +import org.hibernate.boot.models.bind.spi.BindingOptions; +import org.hibernate.boot.models.bind.spi.BindingState; +import org.hibernate.boot.models.bind.spi.TableReference; +import org.hibernate.mapping.BasicValue; +import org.hibernate.mapping.Property; +import org.hibernate.models.ModelsException; +import org.hibernate.models.spi.AnnotationUsage; +import org.hibernate.models.spi.MemberDetails; +import org.hibernate.type.descriptor.java.BasicJavaType; + +import jakarta.persistence.Enumerated; +import jakarta.persistence.Lob; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; + +import static jakarta.persistence.EnumType.ORDINAL; +import static org.hibernate.annotations.TimeZoneStorageType.AUTO; +import static org.hibernate.annotations.TimeZoneStorageType.COLUMN; + +/** + * @author Steve Ebersole + */ +public class BasicValueBinder { + + public static void bindJavaType( + MemberDetails member, + Property property, + BasicValue basicValue, + BindingOptions bindingOptions, + BindingState bindingState, + BindingContext bindingContext) { + // todo : do we need to account for JavaTypeRegistration here? + final var javaTypeAnn = member.getAnnotationUsage( JavaType.class ); + if ( javaTypeAnn == null ) { + return; + } + + basicValue.setExplicitJavaTypeAccess( (typeConfiguration) -> { + final var classDetails = javaTypeAnn.getClassDetails( "value" ); + final Class> javaClass = classDetails.toJavaClass(); + try { + return javaClass.getConstructor().newInstance(); + } + catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + final ModelsException modelsException = new ModelsException( "Error instantiating local @JavaType - " + member.getName() ); + modelsException.addSuppressed( e ); + throw modelsException; + } + } ); + } + + public static void bindJdbcType( + MemberDetails member, + Property property, + BasicValue basicValue, + BindingOptions bindingOptions, + BindingState bindingState, + BindingContext bindingContext) { + // todo : do we need to account for JdbcTypeRegistration here? + final var jdbcTypeAnn = member.getAnnotationUsage( JdbcType.class ); + final var jdbcTypeCodeAnn = member.getAnnotationUsage( JdbcTypeCode.class ); + + if ( jdbcTypeAnn != null ) { + if ( jdbcTypeCodeAnn != null ) { + throw new AnnotationPlacementException( + "Illegal combination of @JdbcType and @JdbcTypeCode - " + member.getName() + ); + } + + basicValue.setExplicitJdbcTypeAccess( (typeConfiguration) -> { + final var classDetails = jdbcTypeAnn.getClassDetails( "value" ); + final Class javaClass = classDetails.toJavaClass(); + try { + return javaClass.getConstructor().newInstance(); + } + catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + final ModelsException modelsException = new ModelsException( "Error instantiating local @JdbcType - " + member.getName() ); + modelsException.addSuppressed( e ); + throw modelsException; + } + } ); + } + else if ( jdbcTypeCodeAnn != null ) { + final Integer typeCode = jdbcTypeCodeAnn.getInteger( "value" ); + basicValue.setExplicitJdbcTypeCode( typeCode ); + } + } + + public static void bindNationalized( + MemberDetails member, + Property property, + BasicValue basicValue, + BindingOptions bindingOptions, + BindingState bindingState, + BindingContext bindingContext) { + if ( member.getAnnotationUsage( Nationalized.class ) != null ) { + basicValue.makeNationalized(); + } + } + + public static void bindLob( + MemberDetails member, + Property property, + BasicValue basicValue, + BindingOptions bindingOptions, + BindingState bindingState, + BindingContext bindingContext) { + if ( member.getAnnotationUsage( Lob.class ) != null ) { + basicValue.makeLob(); + } + } + + public static void bindEnumerated( + MemberDetails member, + Property property, + BasicValue basicValue, + BindingOptions bindingOptions, + BindingState bindingState, + BindingContext bindingContext) { + final AnnotationUsage enumerated = member.getAnnotationUsage( Enumerated.class ); + if ( enumerated == null ) { + return; + } + + basicValue.setEnumerationStyle( enumerated.getEnum( "value", ORDINAL ) ); + } + + public static void bindTemporalPrecision( + MemberDetails member, + Property property, + BasicValue basicValue, + BindingOptions bindingOptions, + BindingState bindingState, + BindingContext bindingContext) { + final AnnotationUsage temporalAnn = member.getAnnotationUsage( Temporal.class ); + if ( temporalAnn == null ) { + return; + } + + //noinspection deprecation + final TemporalType precision = temporalAnn.getEnum( "value" ); + basicValue.setTemporalPrecision( precision ); + } + + public static void bindTimeZoneStorage( + MemberDetails member, + Property property, + BasicValue basicValue, + BindingOptions bindingOptions, + BindingState bindingState, + BindingContext bindingContext) { + final AnnotationUsage storageAnn = member.getAnnotationUsage( TimeZoneStorage.class ); + final AnnotationUsage columnAnn = member.getAnnotationUsage( TimeZoneColumn.class ); + if ( storageAnn != null ) { + final TimeZoneStorageType strategy = storageAnn.getEnum( "value", AUTO ); + if ( strategy != COLUMN && columnAnn != null ) { + throw new AnnotationPlacementException( + "Illegal combination of @TimeZoneStorage(" + strategy.name() + ") and @TimeZoneColumn" + ); + } + basicValue.setTimeZoneStorageType( strategy ); + } + + if ( columnAnn != null ) { + final org.hibernate.mapping.Column column = (org.hibernate.mapping.Column) basicValue.getColumn(); + column.setName( columnAnn.getString( "name", property.getName() + "_tz" ) ); + column.setSqlType( columnAnn.getString( "columnDefinition", null ) ); + + final var tableName = columnAnn.getString( "table", null ); + TableReference tableByName = null; + if ( tableName != null ) { + final Identifier identifier = Identifier.toIdentifier( tableName ); + tableByName = bindingState.getTableByName( identifier.getCanonicalName() ); + basicValue.setTable( tableByName.binding() ); + } + + property.setInsertable( columnAnn.getBoolean( "insertable", true ) ); + property.setUpdateable( columnAnn.getBoolean( "updatable", true ) ); + } + } + +} diff --git a/src/main/java/org/hibernate/boot/models/bind/internal/binders/ColumnBinder.java b/src/main/java/org/hibernate/boot/models/bind/internal/binders/ColumnBinder.java index f8a5910..083b3ac 100644 --- a/src/main/java/org/hibernate/boot/models/bind/internal/binders/ColumnBinder.java +++ b/src/main/java/org/hibernate/boot/models/bind/internal/binders/ColumnBinder.java @@ -32,6 +32,22 @@ public static Column bindColumn( ); } + public static Column bindColumn( + AnnotationUsage annotationUsage, + Supplier defaultNameSupplier, + boolean uniqueByDefault, + boolean nullableByDefault) { + return bindColumn( + annotationUsage, + defaultNameSupplier, + uniqueByDefault, + nullableByDefault, + 255, + 0, + 0 + ); + } + public static Column bindColumn( AnnotationUsage annotationUsage, Supplier defaultNameSupplier, diff --git a/src/main/java/org/hibernate/boot/models/bind/internal/binders/EntityTypeBinder.java b/src/main/java/org/hibernate/boot/models/bind/internal/binders/EntityTypeBinder.java index bbf7b3d..ee45e91 100644 --- a/src/main/java/org/hibernate/boot/models/bind/internal/binders/EntityTypeBinder.java +++ b/src/main/java/org/hibernate/boot/models/bind/internal/binders/EntityTypeBinder.java @@ -26,6 +26,11 @@ import org.hibernate.boot.models.bind.spi.BindingContext; import org.hibernate.boot.models.bind.spi.BindingOptions; import org.hibernate.boot.models.bind.spi.BindingState; +import org.hibernate.boot.models.categorize.spi.AggregatedKeyMapping; +import org.hibernate.boot.models.categorize.spi.AttributeMetadata; +import org.hibernate.boot.models.categorize.spi.BasicKeyMapping; +import org.hibernate.boot.models.categorize.spi.KeyMapping; +import org.hibernate.boot.models.categorize.spi.NonAggregatedKeyMapping; import org.hibernate.engine.OptimisticLockStyle; import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.collections.CollectionHelper; @@ -40,6 +45,8 @@ import org.hibernate.mapping.JoinedSubclass; import org.hibernate.mapping.MappedSuperclass; import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.PrimaryKey; +import org.hibernate.mapping.Property; import org.hibernate.mapping.RootClass; import org.hibernate.mapping.SingleTableSubclass; import org.hibernate.mapping.Subclass; @@ -62,6 +69,7 @@ import jakarta.persistence.Entity; import jakarta.persistence.InheritanceType; +import static org.hibernate.boot.models.bind.internal.binders.IdentifierBinder.bindIdentifier; import static org.hibernate.internal.util.StringHelper.coalesce; /** @@ -116,11 +124,11 @@ public EntityTypeBinder( state.registerTypeBinder( type, this ); state.getMetadataBuildingContext().getMetadataCollector().addImport( importName, entityName ); - final var primaryTable = modelBinders.getTableBinder().processPrimaryTable( getManagedType() ); + final var primaryTable = modelBinders.getTableBinder().bindPrimaryTable( getManagedType(), hierarchyRelation ); final var table = primaryTable.binding(); ( (TableOwner) binding ).setTable( table ); - final var secondaryTables = modelBinders.getTableBinder().processSecondaryTables( getManagedType() ); + final var secondaryTables = modelBinders.getTableBinder().bindSecondaryTables( getManagedType() ); secondaryTables.forEach( this::processSecondaryTable ); final IdentifiableTypeBinder superTypeBinder = getSuperTypeBinder(); @@ -433,11 +441,19 @@ private PersistentClass resolveSuperEntity(IdentifiableTypeClass superTypeBindin @Override protected void prepareBinding(ModelBinders modelBinders) { - // todo : possibly Hierarchy details - version, tenant-id, ... + if ( getHierarchyRelation() == EntityHierarchy.HierarchyRelation.ROOT ) { + prepareRootEntityBinding( (RootClass) getTypeBinding(), modelBinders ); + } super.prepareBinding( modelBinders ); } + private void prepareRootEntityBinding(RootClass typeBinding, ModelBinders modelBinders) { + // todo : possibly Hierarchy details - version, tenant-id, ... + + bindIdentifier( getManagedType(), typeBinding, modelBinders, getBindingState(), getOptions(), getBindingContext() ); + } + private void processSoftDelete( Table primaryTable, RootClass rootClass, diff --git a/src/main/java/org/hibernate/boot/models/bind/internal/binders/IdentifiableTypeBinder.java b/src/main/java/org/hibernate/boot/models/bind/internal/binders/IdentifiableTypeBinder.java index 060f8ca..46b0457 100644 --- a/src/main/java/org/hibernate/boot/models/bind/internal/binders/IdentifiableTypeBinder.java +++ b/src/main/java/org/hibernate/boot/models/bind/internal/binders/IdentifiableTypeBinder.java @@ -13,17 +13,16 @@ import org.hibernate.boot.models.bind.spi.BindingContext; import org.hibernate.boot.models.bind.spi.BindingOptions; import org.hibernate.boot.models.bind.spi.BindingState; +import org.hibernate.boot.models.categorize.spi.AttributeMetadata; +import org.hibernate.boot.models.categorize.spi.EntityHierarchy; +import org.hibernate.boot.models.categorize.spi.EntityTypeMetadata; +import org.hibernate.boot.models.categorize.spi.IdentifiableTypeMetadata; import org.hibernate.mapping.BasicValue; import org.hibernate.mapping.IdentifiableTypeClass; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; import org.hibernate.mapping.Table; import org.hibernate.mapping.Value; -import org.hibernate.boot.models.categorize.spi.AttributeMetadata; -import org.hibernate.boot.models.categorize.spi.EntityHierarchy; -import org.hibernate.boot.models.categorize.spi.EntityTypeMetadata; -import org.hibernate.boot.models.categorize.spi.IdentifiableTypeMetadata; -import org.hibernate.boot.models.categorize.spi.KeyMapping; /** * @author Steve Ebersole @@ -94,6 +93,10 @@ protected void prepareBinding(ModelBinders modelBinders) { final var managedType = getManagedType(); managedType.forEachAttribute( (index, attributeMetadata) -> { + if ( managedType.getHierarchy().getIdMapping().contains( attributeMetadata ) ) { + return; + } + final var attributeBinder = new AttributeBinder( attributeMetadata, getBindingState(), @@ -106,7 +109,6 @@ protected void prepareBinding(ModelBinders modelBinders) { final var value = property.getValue(); applyTable( value, table ); - processIdMapping( attributeMetadata, property ); processNaturalId( attributeMetadata, property ); attributeBinders.add( attributeBinder ); @@ -116,15 +118,6 @@ protected void prepareBinding(ModelBinders modelBinders) { super.prepareBinding( modelBinders ); } - private void processIdMapping(AttributeMetadata attributeMetadata, Property property) { - final KeyMapping idMapping = getManagedType().getHierarchy().getIdMapping(); - if ( !idMapping.contains( attributeMetadata ) ) { - return; - } - - // todo : do it - } - private void processNaturalId(AttributeMetadata attributeMetadata, Property property) { final var naturalIdAnn = attributeMetadata.getMember().getAnnotationUsage( NaturalId.class ); if ( naturalIdAnn == null ) { diff --git a/src/main/java/org/hibernate/boot/models/bind/internal/binders/IdentifierBinder.java b/src/main/java/org/hibernate/boot/models/bind/internal/binders/IdentifierBinder.java new file mode 100644 index 0000000..41db924 --- /dev/null +++ b/src/main/java/org/hibernate/boot/models/bind/internal/binders/IdentifierBinder.java @@ -0,0 +1,143 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.boot.models.bind.internal.binders; + +import org.hibernate.boot.models.bind.spi.BindingContext; +import org.hibernate.boot.models.bind.spi.BindingOptions; +import org.hibernate.boot.models.bind.spi.BindingState; +import org.hibernate.boot.models.categorize.spi.AggregatedKeyMapping; +import org.hibernate.boot.models.categorize.spi.AttributeMetadata; +import org.hibernate.boot.models.categorize.spi.BasicKeyMapping; +import org.hibernate.boot.models.categorize.spi.EntityHierarchy; +import org.hibernate.boot.models.categorize.spi.EntityTypeMetadata; +import org.hibernate.boot.models.categorize.spi.KeyMapping; +import org.hibernate.boot.models.categorize.spi.NonAggregatedKeyMapping; +import org.hibernate.mapping.BasicValue; +import org.hibernate.mapping.Component; +import org.hibernate.mapping.KeyValue; +import org.hibernate.mapping.PrimaryKey; +import org.hibernate.mapping.Property; +import org.hibernate.mapping.RootClass; +import org.hibernate.mapping.Table; +import org.hibernate.models.spi.AnnotationUsage; +import org.hibernate.models.spi.MemberDetails; + +import jakarta.persistence.Column; +import jakarta.persistence.Id; + +/** + * @author Steve Ebersole + */ +public class IdentifierBinder { + private final ModelBinders modelBinders; + + private final BindingState state; + private final BindingOptions options; + private final BindingContext context; + + public IdentifierBinder( + ModelBinders modelBinders, + BindingState state, + BindingOptions options, + BindingContext context) { + this.modelBinders = modelBinders; + this.state = state; + this.options = options; + this.context = context; + } + + public static KeyValue bindIdentifier( + EntityTypeMetadata type, + RootClass typeBinding, + ModelBinders modelBinders, + BindingState state, + BindingOptions options, + BindingContext context) { + final IdentifierBinder identifierBinder = new IdentifierBinder( modelBinders, state, options, context ); + return identifierBinder.bindIdentifier( type, typeBinding ); + } + + private KeyValue bindIdentifier(EntityTypeMetadata type, RootClass typeBinding) { + final EntityHierarchy hierarchy = type.getHierarchy(); + final KeyMapping idMapping = hierarchy.getIdMapping(); + final Table table = typeBinding.getTable(); + + final PrimaryKey primaryKey = new PrimaryKey( table ); + table.setPrimaryKey( primaryKey ); + + if ( idMapping instanceof BasicKeyMapping basicKeyMapping ) { + return bindBasicIdentifier( basicKeyMapping, table, type, typeBinding ); + } + else if ( idMapping instanceof AggregatedKeyMapping aggregatedKeyMapping ) { + return bindAggregatedIdentifier( aggregatedKeyMapping, table, type, typeBinding ); + } + else { + return bindNonAggregatedIdentifier( (NonAggregatedKeyMapping) idMapping, table, type, typeBinding ); + } + } + + private KeyValue bindBasicIdentifier( + BasicKeyMapping basicKeyMapping, + Table table, + EntityTypeMetadata typeMetadata, + RootClass typeBinding) { + final AttributeMetadata idAttribute = basicKeyMapping.getAttribute(); + final MemberDetails idAttributeMember = idAttribute.getMember(); + + final BasicValue idValue = new BasicValue( state.getMetadataBuildingContext(), table ); + typeBinding.setIdentifier( idValue ); + + final Property idProperty = new Property(); + idProperty.setName( idAttribute.getName() ); + idProperty.setValue( idValue ); + typeBinding.addProperty( idProperty ); + typeBinding.setIdentifierProperty( idProperty ); + + final PrimaryKey primaryKey = new PrimaryKey( table ); + table.setPrimaryKey( primaryKey ); + + final AnnotationUsage idColumnAnn = idAttributeMember.getAnnotationUsage( Column.class ); + final org.hibernate.mapping.Column column = ColumnBinder.bindColumn( + idColumnAnn, + () -> "id", + true, + false + ); + idValue.addColumn( column, true, false ); + primaryKey.addColumn( column ); + + AttributeBinder.bindImplicitJavaType( idAttributeMember, idProperty, idValue, options, state, context ); + BasicValueBinder.bindJavaType( idAttributeMember, idProperty, idValue, options, state, context ); + BasicValueBinder.bindJdbcType( idAttributeMember, idProperty, idValue, options, state, context ); + BasicValueBinder.bindNationalized( idAttributeMember, idProperty, idValue, options, state, context ); + + return idValue; + } + + private KeyValue bindAggregatedIdentifier( + AggregatedKeyMapping aggregatedKeyMapping, + Table table, + EntityTypeMetadata type, + RootClass typeBinding) { + final Component idValue = new Component( state.getMetadataBuildingContext(), typeBinding ); + typeBinding.setIdentifier( idValue ); + + final Property idProperty = new Property(); + idProperty.setValue( idValue ); + typeBinding.setIdentifierProperty( idProperty ); + + return idValue; + } + + private KeyValue bindNonAggregatedIdentifier( + NonAggregatedKeyMapping idMapping, + Table table, + EntityTypeMetadata type, + RootClass typeBinding) { + throw new UnsupportedOperationException( "Not yet implemented" ); + } +} diff --git a/src/main/java/org/hibernate/boot/models/bind/internal/binders/TableBinder.java b/src/main/java/org/hibernate/boot/models/bind/internal/binders/TableBinder.java index 92e542a..e4d9158 100644 --- a/src/main/java/org/hibernate/boot/models/bind/internal/binders/TableBinder.java +++ b/src/main/java/org/hibernate/boot/models/bind/internal/binders/TableBinder.java @@ -30,11 +30,13 @@ import org.hibernate.boot.models.bind.spi.PhysicalTableReference; import org.hibernate.boot.models.bind.spi.QuotedIdentifierTarget; import org.hibernate.boot.models.bind.spi.TableReference; +import org.hibernate.boot.models.categorize.spi.EntityHierarchy; import org.hibernate.boot.models.categorize.spi.EntityTypeMetadata; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.internal.util.StringHelper; import org.hibernate.mapping.DenormalizedTable; +import org.hibernate.mapping.PrimaryKey; import org.hibernate.mapping.Table; import org.hibernate.models.spi.AnnotationUsage; import org.hibernate.models.spi.ClassDetails; @@ -81,7 +83,7 @@ public TableBinder( this.jdbcEnvironment = bindingContext.getServiceRegistry().getService( JdbcEnvironment.class ); } - public TableReference processPrimaryTable(EntityTypeMetadata type) { + public TableReference bindPrimaryTable(EntityTypeMetadata type, EntityHierarchy.HierarchyRelation hierarchyRelation) { final ClassDetails typeClassDetails = type.getClassDetails(); final AnnotationUsage tableAnn = typeClassDetails.getAnnotationUsage( jakarta.persistence.Table.class ); final AnnotationUsage subselectAnn = typeClassDetails.getAnnotationUsage( Subselect.class ); @@ -90,42 +92,42 @@ public TableReference processPrimaryTable(EntityTypeMetadata type) { if ( type.getHierarchy().getInheritanceType() == InheritanceType.TABLE_PER_CLASS ) { assert subselectAnn == null; - tableReference = processUnionTable( type, tableAnn ); + + if ( hierarchyRelation == EntityHierarchy.HierarchyRelation.ROOT ) { + tableReference = bindPhysicalTable( type, tableAnn, true ); + } + else { + tableReference = bindUnionTable( type, tableAnn ); + } } else { if ( subselectAnn != null ) { if ( tableAnn != null ) { throw new AnnotationPlacementException( "Illegal combination of @Table and @Subselect on " + typeClassDetails.getName() ); } - tableReference = processVirtualTable( type, subselectAnn ); + tableReference = bindVirtualTable( type, subselectAnn ); } else { // either an explicit or implicit @Table - tableReference = processPhysicalTable( type, tableAnn, true ); + tableReference = bindPhysicalTable( type, tableAnn, true ); } } bindingState.addTable( type, tableReference ); + + final PrimaryKey primaryKey = new PrimaryKey( tableReference.binding() ); + tableReference.binding().setPrimaryKey( primaryKey ); + return tableReference; } - private TableReference processUnionTable( + private TableReference bindUnionTable( EntityTypeMetadata type, AnnotationUsage tableAnn) { - final UnionTable base; - final Table included; - if ( type.getSuperType() != null ) { - base = bindingState.getTableByOwner( type.getSuperType() ); - included = base.binding(); - } - else { - base = null; - // todo : change this in hibernate-core - // - `DenormalizedTable` assumes the `included` is non-null meaning that the - // root always needs to be a non-null `Table` - included = new Table("orm"); - included.setAbstract( true ); - } + assert type.getSuperType() != null; + + final TableReference superTypeTable = bindingState.getTableByOwner( type.getSuperType() ); + final Table unionBaseTable = superTypeTable.binding(); final Identifier logicalName = determineLogicalName( type, tableAnn ); final Identifier logicalSchemaName = resolveDatabaseIdentifier( @@ -147,16 +149,16 @@ private TableReference processUnionTable( logicalSchemaName == null ? null : logicalSchemaName.getCanonicalName(), logicalCatalogName == null ? null : logicalCatalogName.getCanonicalName(), logicalName.getCanonicalName(), - type.hasSubTypes(), + type.isAbstract(), null, - included, + unionBaseTable, bindingState.getMetadataBuildingContext() ); - return new UnionTable( logicalName, base, binding, !type.hasSubTypes() ); + return new UnionTable( logicalName, superTypeTable, binding, !type.hasSubTypes() ); } - public List processSecondaryTables(EntityTypeMetadata type) { + public List bindSecondaryTables(EntityTypeMetadata type) { final ClassDetails typeClassDetails = type.getClassDetails(); final List> secondaryTableAnns = typeClassDetails.getRepeatedAnnotationUsages( SecondaryTable.class ); @@ -168,14 +170,14 @@ public List processSecon secondaryTableAnn.getString( "name" ), "table" ); - final org.hibernate.boot.models.bind.internal.SecondaryTable binding = processSecondaryTable( type, secondaryTableAnn, secondaryRowAnn ); + final org.hibernate.boot.models.bind.internal.SecondaryTable binding = bindSecondaryTable( type, secondaryTableAnn, secondaryRowAnn ); result.add( binding ); bindingState.addSecondaryTable( binding ); } ); return result; } - private InLineView processVirtualTable(EntityTypeMetadata type, AnnotationUsage subselectAnn) { + private InLineView bindVirtualTable(EntityTypeMetadata type, AnnotationUsage subselectAnn) { final Identifier logicalName = implicitNamingStrategy.determinePrimaryTableName( new ImplicitEntityNameSource() { @Override @@ -203,19 +205,19 @@ public MetadataBuildingContext getBuildingContext() { ); } - private PhysicalTableReference processPhysicalTable( + private PhysicalTableReference bindPhysicalTable( EntityTypeMetadata type, AnnotationUsage tableAnn, boolean isPrimary) { if ( tableAnn != null ) { - return createExplicitPhysicalTable( type, tableAnn, isPrimary ); + return bindExplicitPhysicalTable( type, tableAnn, isPrimary ); } else { - return createImplicitPhysicalTable( type, isPrimary ); + return bindImplicitPhysicalTable( type, isPrimary ); } } - private PhysicalTable createImplicitPhysicalTable(EntityTypeMetadata type, boolean isPrimary) { + private PhysicalTable bindImplicitPhysicalTable(EntityTypeMetadata type, boolean isPrimary) { final Identifier logicalName = determineLogicalName( type, null ); final Table binding = bindingState.getMetadataBuildingContext().getMetadataCollector().addTable( @@ -289,7 +291,7 @@ public MetadataBuildingContext getBuildingContext() { ); } - private PhysicalTable createExplicitPhysicalTable( + private PhysicalTable bindExplicitPhysicalTable( EntityTypeMetadata type, AnnotationUsage tableAnn, boolean isPrimary) { @@ -332,7 +334,7 @@ private PhysicalTable createExplicitPhysicalTable( ); } - private org.hibernate.boot.models.bind.internal.SecondaryTable processSecondaryTable( + private org.hibernate.boot.models.bind.internal.SecondaryTable bindSecondaryTable( EntityTypeMetadata type, AnnotationUsage secondaryTableAnn, AnnotationUsage secondaryRowAnn) { diff --git a/src/test/java/org/hibernate/models/orm/bind/id/SimpleIdEntity.java b/src/test/java/org/hibernate/models/orm/bind/id/BasicIdEntity.java similarity index 95% rename from src/test/java/org/hibernate/models/orm/bind/id/SimpleIdEntity.java rename to src/test/java/org/hibernate/models/orm/bind/id/BasicIdEntity.java index b727996..41f3380 100644 --- a/src/test/java/org/hibernate/models/orm/bind/id/SimpleIdEntity.java +++ b/src/test/java/org/hibernate/models/orm/bind/id/BasicIdEntity.java @@ -17,7 +17,7 @@ * @author Steve Ebersole */ @Entity -public class SimpleIdEntity { +public class BasicIdEntity { @Id private Integer id; @Version diff --git a/src/test/java/org/hibernate/models/orm/bind/id/SimpleIdTests.java b/src/test/java/org/hibernate/models/orm/bind/id/SimpleIdTests.java index 0dd5c85..4cde920 100644 --- a/src/test/java/org/hibernate/models/orm/bind/id/SimpleIdTests.java +++ b/src/test/java/org/hibernate/models/orm/bind/id/SimpleIdTests.java @@ -33,7 +33,7 @@ public class SimpleIdTests { @Test void testSimpleId() { - final Set entityHierarchies = buildHierarchyMetadata( SimpleIdEntity.class ); + final Set entityHierarchies = buildHierarchyMetadata( BasicIdEntity.class ); assertThat( entityHierarchies ).hasSize( 1 ); final EntityHierarchy entityHierarchy = entityHierarchies.iterator().next(); diff --git a/src/test/java/org/hibernate/models/orm/bind/mappedsuper/MappedSuperclassTests.java b/src/test/java/org/hibernate/models/orm/bind/mappedsuper/MappedSuperclassTests.java index 06262a3..5e73be3 100644 --- a/src/test/java/org/hibernate/models/orm/bind/mappedsuper/MappedSuperclassTests.java +++ b/src/test/java/org/hibernate/models/orm/bind/mappedsuper/MappedSuperclassTests.java @@ -12,9 +12,13 @@ import org.hibernate.boot.models.categorize.spi.BasicKeyMapping; import org.hibernate.boot.models.categorize.spi.EntityHierarchy; import org.hibernate.cache.spi.access.AccessType; +import org.hibernate.mapping.MappedSuperclass; +import org.hibernate.mapping.PersistentClass; import org.hibernate.models.orm.bind.callbacks.HierarchyRoot; import org.hibernate.models.orm.bind.callbacks.HierarchySuper; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.ServiceRegistryScope; import org.junit.jupiter.api.Test; import jakarta.persistence.EmbeddedId; @@ -24,6 +28,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.hibernate.models.orm.bind.BindingTestingHelper.buildHierarchyMetadata; +import static org.hibernate.models.orm.bind.BindingTestingHelper.checkDomainModel; /** * @author Steve Ebersole @@ -31,7 +36,7 @@ public class MappedSuperclassTests { @Test - void testHierarchy() { + void testAssumptions() { final Set entityHierarchies = buildHierarchyMetadata( HierarchyRoot.class, HierarchySuper.class ); assertThat( entityHierarchies ).hasSize( 1 ); final EntityHierarchy entityHierarchy = entityHierarchies.iterator().next(); @@ -54,6 +59,37 @@ void testHierarchy() { assertThat( entityHierarchy.getInheritanceType() ).isEqualTo( InheritanceType.JOINED ); } + @SuppressWarnings("JUnitMalformedDeclaration") + @Test + @ServiceRegistry + void testBindings(ServiceRegistryScope scope) { + checkDomainModel( + (context) -> { + final var metadataCollector = context.getMetadataCollector(); + final MappedSuperclass superBinding = metadataCollector.getMappedSuperclass( HierarchySuper.class ); + final PersistentClass rootBinding = metadataCollector.getEntityBinding( HierarchyRoot.class.getName() ); + + // todo - https://github.com/sebersole/hibernate-models2/issues/81 +// assertThat( superBinding.getMappedClass() ).isEqualTo( HierarchySuper.class ); +// assertThat( superBinding.getImplicitTable() ).isNull(); +// assertThat( superBinding.hasIdentifierProperty() ).isFalse(); + assertThat( rootBinding.getMappedClass() ).isEqualTo( HierarchyRoot.class ); + assertThat( rootBinding.getSuperMappedSuperclass() ).isSameAs( superBinding ); + assertThat( rootBinding.getImplicitTable() ).isNotNull(); + assertThat( rootBinding.getTable() ).isNotNull(); + assertThat( rootBinding.getRootTable() ).isSameAs( rootBinding.getTable() ); + assertThat( rootBinding.getIdentityTable() ).isSameAs( rootBinding.getTable() ); + assertThat( rootBinding.getIdentityTable().getPrimaryKey() ).isNotNull(); + assertThat( rootBinding.getIdentityTable().getPrimaryKey().getColumns() ).hasSize( 1 ); + assertThat( rootBinding.getIdentifier() ).isNotNull(); + assertThat( rootBinding.getIdentifierProperty() ).isNotNull(); + assertThat( rootBinding.getIdentifierProperty().getColumns() ).hasSize( 1 ); + }, + scope.getRegistry(), + HierarchyRoot.class, + HierarchySuper.class + ); + } } diff --git a/src/test/java/org/hibernate/models/orm/bind/union/UnionSubclassTests.java b/src/test/java/org/hibernate/models/orm/bind/union/UnionSubclassTests.java index b4aa4f5..a180fab 100644 --- a/src/test/java/org/hibernate/models/orm/bind/union/UnionSubclassTests.java +++ b/src/test/java/org/hibernate/models/orm/bind/union/UnionSubclassTests.java @@ -8,6 +8,7 @@ import org.hibernate.mapping.DenormalizedTable; import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.Table; import org.hibernate.testing.orm.junit.ServiceRegistry; import org.hibernate.testing.orm.junit.ServiceRegistryScope; @@ -30,17 +31,24 @@ void testSimpleModel(ServiceRegistryScope scope) { final PersistentClass rootBinding = metadataCollector.getEntityBinding( UnionRoot.class.getName() ); assertThat( rootBinding ).isSameAs( rootBinding.getRootClass() ); assertThat( rootBinding.getTable() ).isSameAs( rootBinding.getRootTable() ); - assertThat( rootBinding.getTable() ).isInstanceOf( DenormalizedTable.class ); - assertThat( rootBinding.getTable().isAbstract() ).isTrue(); + assertThat( rootBinding.getTable() ).isInstanceOf( Table.class ); + assertThat( rootBinding.getTable().isAbstract() ).isFalse(); assertThat( rootBinding.getTable().getName() ).isEqualToIgnoringCase( "unionroot" ); final PersistentClass subBinding = metadataCollector.getEntityBinding( UnionSub.class.getName() ); assertThat( subBinding ).isNotSameAs( subBinding.getRootClass() ); assertThat( subBinding.getRootClass() ).isSameAs( rootBinding ); assertThat( subBinding.getTable() ).isNotSameAs( subBinding.getRootTable() ); + assertThat( rootBinding.getTable() ).isSameAs( subBinding.getRootTable() ); assertThat( subBinding.getTable() ).isInstanceOf( DenormalizedTable.class ); assertThat( subBinding.getTable().isAbstract() ).isFalse(); assertThat( subBinding.getTable().getName() ).isEqualToIgnoringCase( "unionsub" ); + + assertThat( rootBinding.getIdentifier() ).isNotNull(); + assertThat( rootBinding.getTable().getPrimaryKey() ).isNotNull(); + + assertThat( subBinding.getIdentifier() ).isNotNull(); + assertThat( subBinding.getTable().getPrimaryKey() ).isNotNull(); }, scope.getRegistry(), UnionRoot.class,