diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedColumns.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedColumns.java index b194f58618f4..09e63210370f 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedColumns.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedColumns.java @@ -83,18 +83,10 @@ public void setJoins(Map joins) { } public Join getJoin() { - final AnnotatedColumn firstColumn = columns.get( 0 ); + final var firstColumn = columns.get( 0 ); final String explicitTableName = firstColumn.getExplicitTableName(); //note: checkPropertyConsistency() is responsible for ensuring they all have the same table name - Join join = joins.get( explicitTableName ); - if ( join == null ) { - // annotation binding seems to use logical and physical naming somewhat inconsistently... - final String physicalTableName = getBuildingContext().getMetadataCollector() - .getPhysicalTableName( explicitTableName ); - if ( physicalTableName != null ) { - join = joins.get( physicalTableName ); - } - } + final var join = getJoin( explicitTableName ); if ( join == null ) { throw new AnnotationException( "Secondary table '" + explicitTableName + "' for property '" + propertyName + "' of entity'" + getPropertyHolder().getClassName() @@ -106,8 +98,22 @@ public Join getJoin() { } } + private Join getJoin(String explicitTableName) { + final var join = joins.get( explicitTableName ); + if ( join != null ) { + return join; + } + else { + // annotation binding seems to use logical and physical naming somewhat inconsistently... + final String physicalTableName = + getBuildingContext().getMetadataCollector() + .getPhysicalTableName( explicitTableName ); + return physicalTableName != null ? joins.get( physicalTableName ) : null; + } + } + public boolean isSecondary() { - final AnnotatedColumn firstColumn = columns.get( 0 ); + final var firstColumn = columns.get( 0 ); final String explicitTableName = firstColumn.getExplicitTableName(); //note: checkPropertyConsistency() is responsible for ensuring they all have the same table name return isNotEmpty( explicitTableName ) diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedJoinColumns.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedJoinColumns.java index 3ecc53d520c3..db72632a4cd2 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedJoinColumns.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedJoinColumns.java @@ -37,8 +37,16 @@ import jakarta.persistence.JoinColumn; +import static org.hibernate.boot.model.internal.AnnotatedJoinColumn.buildExplicitJoinTableJoinColumn; +import static org.hibernate.boot.model.internal.AnnotatedJoinColumn.buildImplicitJoinTableJoinColumn; +import static org.hibernate.boot.model.internal.AnnotatedJoinColumn.buildJoinColumn; import static org.hibernate.boot.model.internal.BinderHelper.findReferencedColumnOwner; import static org.hibernate.boot.model.internal.BinderHelper.getRelativePath; +import static org.hibernate.boot.model.internal.ForeignKeyType.EXPLICIT_PRIMARY_KEY_REFERENCE; +import static org.hibernate.boot.model.internal.ForeignKeyType.NON_PRIMARY_KEY_REFERENCE; +import static org.hibernate.boot.model.naming.ImplicitJoinColumnNameSource.Nature.ELEMENT_COLLECTION; +import static org.hibernate.boot.model.naming.ImplicitJoinColumnNameSource.Nature.ENTITY; +import static org.hibernate.boot.model.naming.ImplicitJoinColumnNameSource.Nature.ENTITY_COLLECTION; import static org.hibernate.internal.util.StringHelper.isBlank; import static org.hibernate.internal.util.StringHelper.isNotBlank; import static org.hibernate.internal.util.StringHelper.isQuoted; @@ -94,7 +102,7 @@ public static AnnotatedJoinColumns buildJoinColumnsOrFormulas( AnnotatedJoinColumn.buildJoinFormula( formula, parent ); } else { - AnnotatedJoinColumn.buildJoinColumn( column, mappedBy, parent, propertyHolder, inferredData ); + buildJoinColumn( column, mappedBy, parent, propertyHolder, inferredData ); } } @@ -169,7 +177,7 @@ public static AnnotatedJoinColumns buildJoinColumnsWithDefaultColumnSuffix( parent.setPropertyName( getRelativePath( propertyHolder, propertyName ) ); parent.setMappedBy( mappedBy ); if ( isEmpty( actualColumns ) ) { - AnnotatedJoinColumn.buildJoinColumn( + buildJoinColumn( null, mappedBy, parent, @@ -181,7 +189,7 @@ public static AnnotatedJoinColumns buildJoinColumnsWithDefaultColumnSuffix( else { parent.setMappedBy( mappedBy ); for ( var actualColumn : actualColumns ) { - AnnotatedJoinColumn.buildJoinColumn( + buildJoinColumn( actualColumn, mappedBy, parent, @@ -212,11 +220,11 @@ public static AnnotatedJoinColumns buildJoinTableJoinColumns( parent.setPropertyName( getRelativePath( propertyHolder, inferredData.getPropertyName() ) ); parent.setMappedBy( mappedBy ); if ( joinColumns == null ) { - AnnotatedJoinColumn.buildImplicitJoinTableJoinColumn( parent, propertyHolder, inferredData ); + buildImplicitJoinTableJoinColumn( parent, propertyHolder, inferredData ); } else { for ( var joinColumn : joinColumns ) { - AnnotatedJoinColumn.buildExplicitJoinTableJoinColumn( parent, propertyHolder, inferredData, joinColumn ); + buildExplicitJoinTableJoinColumn( parent, propertyHolder, inferredData, joinColumn ); } } handlePropertyRef( inferredData.getAttributeMember(), parent ); @@ -228,11 +236,11 @@ Property resolveMapsId() { final var identifier = persistentClass.getIdentifier(); try { return identifier instanceof Component embeddedIdType - ? embeddedIdType.getProperty( getMapsId() ) // an @EmbeddedId - : persistentClass.getProperty( getMapsId() ); // a simple id or an @IdClass + ? embeddedIdType.getProperty( mapsId ) // an @EmbeddedId + : persistentClass.getProperty( mapsId ); // a simple id or an @IdClass } catch (MappingException me) { - throw new AnnotationException( "Identifier field '" + getMapsId() + throw new AnnotationException( "Identifier field '" + mapsId + "' named in '@MapsId' does not exist in entity '" + persistentClass.getEntityName() + "'", me ); } @@ -318,7 +326,7 @@ public void setMappedBy(String entityName, String logicalTableName, String mappe */ public ForeignKeyType getReferencedColumnsType(PersistentClass referencedEntity) { if ( referencedProperty != null ) { - return ForeignKeyType.NON_PRIMARY_KEY_REFERENCE; + return NON_PRIMARY_KEY_REFERENCE; } if ( columns.isEmpty() ) { @@ -341,7 +349,7 @@ public ForeignKeyType getReferencedColumnsType(PersistentClass referencedEntity) throw new FailedSecondPassException( me.getMessage(), me ); } } - final Table table = table( columnOwner ); + final var table = table( columnOwner ); // final List keyColumns = referencedEntity.getKey().getSelectables(); final var keyColumns = table.getPrimaryKey() == null @@ -353,7 +361,7 @@ public ForeignKeyType getReferencedColumnsType(PersistentClass referencedEntity) explicitColumnReference = true; if ( !keyColumns.contains( column( context, table, column.getReferencedColumn() ) ) ) { // we have a column which does not belong to the PK - return ForeignKeyType.NON_PRIMARY_KEY_REFERENCE; + return NON_PRIMARY_KEY_REFERENCE; } } } @@ -361,9 +369,9 @@ public ForeignKeyType getReferencedColumnsType(PersistentClass referencedEntity) // if we got to here, all the columns belong to the PK return keyColumns.size() == columns.size() // we have all the PK columns - ? ForeignKeyType.EXPLICIT_PRIMARY_KEY_REFERENCE + ? EXPLICIT_PRIMARY_KEY_REFERENCE // we have a subset of the PK columns - : ForeignKeyType.NON_PRIMARY_KEY_REFERENCE; + : NON_PRIMARY_KEY_REFERENCE; } else { // there were no nonempty referencedColumnNames @@ -395,8 +403,9 @@ private static Column column(MetadataBuildingContext context, Table table, Strin } String buildDefaultColumnName(PersistentClass referencedEntity, String logicalReferencedColumn) { - final var options = getBuildingContext().getBuildingOptions(); - final var collector = getBuildingContext().getMetadataCollector(); + final var context = getBuildingContext(); + final var options = context.getBuildingOptions(); + final var collector = context.getMetadataCollector(); final var database = collector.getDatabase(); final var jdbcEnvironment = database.getJdbcEnvironment(); final Identifier columnIdentifier = columnIdentifier( @@ -464,9 +473,10 @@ public Identifier getReferencedPrimaryKeyColumnName() { private Identifier handleElement(Identifier columnIdentifier) { // HHH-11826 magic. See AnnotatedColumn and the HHH-6005 comments - if ( columnIdentifier.getText().contains( "_{element}_" ) ) { + final String identifierText = columnIdentifier.getText(); + if ( identifierText.contains( "_{element}_" ) ) { return Identifier.toIdentifier( - columnIdentifier.getText().replace( "_{element}_", "_" ), + identifierText.replace( "_{element}_", "_" ), columnIdentifier.isQuoted() ); } @@ -502,13 +512,13 @@ private boolean isMappedBySide() { private ImplicitJoinColumnNameSource.Nature getImplicitNature() { if ( getPropertyHolder().isEntity() ) { - return ImplicitJoinColumnNameSource.Nature.ENTITY; + return ENTITY; } else if ( isElementCollection() ) { - return ImplicitJoinColumnNameSource.Nature.ELEMENT_COLLECTION; + return ELEMENT_COLLECTION; } else { - return ImplicitJoinColumnNameSource.Nature.ENTITY_COLLECTION; + return ENTITY_COLLECTION; } } @@ -589,7 +599,7 @@ public Identifier getReferencedColumnName() { return null; } - final Property mappedByProperty = + final var mappedByProperty = collector.getEntityBinding( getMappedByEntityName() ) .getProperty( getMappedByPropertyName() ); final var value = (SimpleValue) mappedByProperty.getValue(); 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 348230f0fc3c..386f45227ca2 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 @@ -82,9 +82,7 @@ public static void bindDefaults(MetadataBuildingContext context) { final var definitionBuilder = new IdentifierGeneratorDefinition.Builder(); interpretSequenceGenerator( generatorRegistration.configuration(), definitionBuilder ); final var idGenDef = definitionBuilder.build(); - if ( BOOT_LOGGER.isTraceEnabled() ) { - BOOT_LOGGER.addingGlobalSequenceGenerator( name ); - } + BOOT_LOGGER.addingGlobalSequenceGenerator( name ); metadataCollector.addDefaultIdentifierGenerator( idGenDef ); } ); @@ -92,9 +90,7 @@ public static void bindDefaults(MetadataBuildingContext context) { final var definitionBuilder = new IdentifierGeneratorDefinition.Builder(); interpretTableGenerator( generatorRegistration.configuration(), definitionBuilder ); final var idGenDef = definitionBuilder.build(); - if ( BOOT_LOGGER.isTraceEnabled() ) { - BOOT_LOGGER.addingGlobalTableGenerator( name ); - } + BOOT_LOGGER.addingGlobalTableGenerator( name ); metadataCollector.addDefaultIdentifierGenerator( idGenDef ); } ); @@ -277,7 +273,6 @@ private static void bindTypeDescriptorRegistrations( AnnotationTarget annotatedElement, MetadataBuildingContext context) { final var managedBeanRegistry = context.getBootstrapContext().getManagedBeanRegistry(); - final var sourceModelContext = modelsContext( context ); annotatedElement.forEachAnnotationUsage( JavaTypeRegistration.class, sourceModelContext, (usage) -> { @@ -422,9 +417,8 @@ private static void bindFetchProfile(FetchProfile fetchProfile, MetadataBuilding final String name = fetchProfile.name(); if ( reuseOrCreateFetchProfile( context, name ) ) { for ( var fetchOverride : fetchProfile.fetchOverrides() ) { - final FetchType fetchType = fetchOverride.fetch(); - final FetchMode fetchMode = fetchOverride.mode(); - if ( fetchType == FetchType.LAZY && fetchMode == FetchMode.JOIN ) { + if ( fetchOverride.fetch() == FetchType.LAZY + && fetchOverride.mode() == FetchMode.JOIN ) { throw new AnnotationException( "Fetch profile '" + name + "' has a '@FetchOverride' with 'fetch=LAZY' and 'mode=JOIN'" @@ -484,28 +478,29 @@ public static Map buildInheritanceStates( collector.registerEmbeddableSubclass( superEntityState.getClassDetails(), classDetails ); } } - logMixedInheritance( classDetails, superclassState, state ); - if ( superclassState.getType() != null ) { - state.setType( superclassState.getType() ); + checkMixedInheritance( classDetails, superclassState, state ); + final var inheritanceType = superclassState.getType(); + if ( inheritanceType != null ) { + state.setType( inheritanceType ); } } switch ( classType ) { - case ENTITY: - case MAPPED_SUPERCLASS: - case EMBEDDABLE: + case ENTITY, MAPPED_SUPERCLASS, EMBEDDABLE: inheritanceStatePerClass.put( classDetails, state ); } } return inheritanceStatePerClass; } - private static void logMixedInheritance(ClassDetails classDetails, InheritanceState superclassState, InheritanceState state) { - if ( state.getType() != null && superclassState.getType() != null ) { - final boolean nonDefault = InheritanceType.SINGLE_TABLE != state.getType(); - final boolean mixingStrategy = state.getType() != superclassState.getType(); + private static void checkMixedInheritance(ClassDetails classDetails, InheritanceState superclassState, InheritanceState state) { + final var inheritanceType = state.getType(); + final var superclassInheritanceType = superclassState.getType(); + if ( inheritanceType != null && superclassInheritanceType != null ) { + final boolean nonDefault = InheritanceType.SINGLE_TABLE != inheritanceType; + final boolean mixingStrategy = inheritanceType != superclassInheritanceType; if ( nonDefault && mixingStrategy ) { throw new AnnotationException( "Entity '" + classDetails.getName() - + "' may not override the inheritance mapping strategy '" + superclassState.getType() + + "' may not override the inheritance mapping strategy '" + superclassInheritanceType + "' of its hierarchy" + "' (each entity hierarchy has a single inheritance mapping strategy)" ); } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BasicValueBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BasicValueBinder.java index e1eeb8043eef..6658fc70de8e 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BasicValueBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BasicValueBinder.java @@ -23,7 +23,6 @@ import jakarta.persistence.Version; import org.checkerframework.checker.nullness.qual.Nullable; import org.hibernate.AnnotationException; -import org.hibernate.AssertionFailure; import org.hibernate.MappingException; import org.hibernate.type.TimeZoneStorageStrategy; import org.hibernate.annotations.*; @@ -34,7 +33,6 @@ import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.dialect.Dialect; import org.hibernate.dialect.NationalizationSupport; -import org.hibernate.dialect.aggregate.AggregateSupport; import org.hibernate.internal.util.ReflectHelper; import org.hibernate.mapping.BasicValue; import org.hibernate.mapping.Component; @@ -232,13 +230,14 @@ public int getPreferredSqlTypeCodeForArray() { public int resolveJdbcTypeCode(int jdbcTypeCode) { return aggregateComponent == null ? jdbcTypeCode - : getAggregateSupport() - .aggregateComponentSqlTypeCode( aggregateComponent.getAggregateColumn().getSqlTypeCode(), - jdbcTypeCode ); + : getAggregateComponentTypeCode( jdbcTypeCode ); } - private AggregateSupport getAggregateSupport() { - return getMetadataCollector().getDatabase().getDialect().getAggregateSupport(); + private int getAggregateComponentTypeCode(int jdbcTypeCode) { + final Integer aggregateColumnSqlTypeCode = + aggregateComponent.getAggregateColumn().getSqlTypeCode(); + return getDialect().getAggregateSupport() + .aggregateComponentSqlTypeCode( aggregateColumnSqlTypeCode, jdbcTypeCode ); } @Override @@ -293,66 +292,71 @@ private static JdbcType getDescriptor(TypeConfiguration typeConfiguration, int c return typeConfiguration.getJdbcTypeRegistry().getDescriptor( code ); } - public void setType(MemberDetails value, TypeDetails typeDetails) { - setType( value, typeDetails, null, null ); + public void setType(MemberDetails memberDetails, TypeDetails typeDetails) { + setType( memberDetails, typeDetails, null, null ); } + public void setType( - MemberDetails value, + MemberDetails memberDetails, TypeDetails typeDetails, @Nullable String declaringClassName, @Nullable ConverterDescriptor converterDescriptor) { - this.memberDetails = value; - final boolean isArray = value.isArray(); - if ( typeDetails == null && !isArray ) { - // we cannot guess anything - return; - } - - if ( columns == null ) { - throw new AssertionFailure( "`BasicValueBinder#setColumns` should be called before `BasicValueBinder#setType`" ); - } + this.memberDetails = memberDetails; + if ( typeDetails != null || memberDetails.isArray() ) { + assert columns != null + : "BasicValueBinder.setColumns must be called before BasicValueBinder.setType"; -// if ( columns.length != 1 ) { -// throw new AssertionFailure( "Expecting just one column, but found `" + Arrays.toString( columns ) + "`" ); -// } + if ( kind != Kind.LIST_INDEX && kind != Kind.MAP_KEY ) { + isLob = memberDetails.hasDirectAnnotationUsage( Lob.class ); + } - final var modelClassDetails = isArray ? value.getElementType() : typeDetails; + if ( getDialect().getNationalizationSupport() == NationalizationSupport.EXPLICIT ) { + isNationalized = isNationalized( memberDetails ); + } - if ( kind != Kind.LIST_INDEX && kind != Kind.MAP_KEY ) { - isLob = value.hasDirectAnnotationUsage( Lob.class ); + final boolean customType; + if ( converterDescriptor != null ) { + applyJpaConverter( memberDetails, converterDescriptor ); + customType = false; + } + else { + customType = applyCustomType( memberDetails, typeDetails ); + } + if ( !customType ) { + prepareValue( memberDetails, typeDetails, declaringClassName ); + } } + // else we cannot guess anything + } - final var modelContext = getSourceModelContext(); - - if ( getDialect().getNationalizationSupport() == NationalizationSupport.EXPLICIT ) { - isNationalized = buildingContext.getBuildingOptions().useNationalizedCharacterData() - || value.locateAnnotationUsage( Nationalized.class, modelContext ) != null; - } + private boolean isNationalized(MemberDetails memberDetails) { + return buildingContext.getBuildingOptions().useNationalizedCharacterData() + || memberDetails.locateAnnotationUsage( Nationalized.class, getSourceModelContext() ) != null; + } - if ( converterDescriptor != null ) { - applyJpaConverter( value, converterDescriptor ); + private boolean applyCustomType(MemberDetails memberDetails, TypeDetails typeDetails) { + final var modelContext = getSourceModelContext(); + final var userTypeImpl = kind.mappingAccess.customType( memberDetails, modelContext ); + if ( userTypeImpl != null ) { + applyExplicitType( userTypeImpl, + kind.mappingAccess.customTypeParameters( memberDetails, modelContext ) ); + // An explicit custom UserType has top precedence when we get to BasicValue resolution. + return true; } - else { - final var userTypeImpl = - kind.mappingAccess.customType( value, modelContext ); - if ( userTypeImpl != null ) { - applyExplicitType( userTypeImpl, - kind.mappingAccess.customTypeParameters( value, modelContext ) ); - // An explicit custom UserType has top precedence when we get to BasicValue resolution. - return; - } - else if ( modelClassDetails != null ) { - final var rawClassDetails = modelClassDetails.determineRawClass(); - final var basicClass = rawClassDetails.toJavaClass(); - final var registeredUserTypeImpl = - getMetadataCollector().findRegisteredUserType( basicClass ); - if ( registeredUserTypeImpl != null ) { - applyExplicitType( registeredUserTypeImpl, emptyMap() ); - return; - } + final var modelClassDetails = memberDetails.isArray() ? memberDetails.getElementType() : typeDetails; + if ( modelClassDetails != null ) { + final var basicClass = modelClassDetails.determineRawClass().toJavaClass(); + final var registeredUserTypeImpl = + getMetadataCollector().findRegisteredUserType( basicClass ); + if ( registeredUserTypeImpl != null ) { + applyExplicitType( registeredUserTypeImpl, emptyMap() ); + return true; } } + return false; + } + private void prepareValue(MemberDetails value, TypeDetails typeDetails, @Nullable String declaringClassName) { switch ( kind ) { case ATTRIBUTE: prepareBasicAttribute( declaringClassName, value, typeDetails ); @@ -378,17 +382,16 @@ else if ( modelClassDetails != null ) { default: throw new IllegalArgumentException( "Unexpected binder type : " + kind ); } - } - private void applyExplicitType(Class> impl, Map params) { - this.explicitCustomType = impl; - this.explicitLocalTypeParams = params; + private void applyExplicitType(Class> userTypeImpl, Map parameters) { + explicitCustomType = userTypeImpl; + explicitLocalTypeParams = parameters; } private void prepareCollectionId(MemberDetails attribute) { - final var collectionIdAnn = attribute.getDirectAnnotationUsage( CollectionId.class ); - if ( collectionIdAnn == null ) { + final var collectionId = attribute.getDirectAnnotationUsage( CollectionId.class ); + if ( collectionId == null ) { throw new MappingException( "idbag mapping missing @CollectionId" ); } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java index a98d12a44cb8..fe322fa24793 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java @@ -88,6 +88,7 @@ import static org.hibernate.boot.model.internal.AnnotatedClassType.EMBEDDABLE; import static org.hibernate.boot.model.internal.AnnotatedClassType.NONE; import static org.hibernate.boot.model.internal.AnnotatedColumn.buildColumnFromAnnotation; +import static org.hibernate.boot.model.internal.AnnotatedColumn.buildColumnFromAnnotations; import static org.hibernate.boot.model.internal.AnnotatedColumn.buildColumnFromNoAnnotation; import static org.hibernate.boot.model.internal.AnnotatedColumn.buildColumnsFromAnnotations; import static org.hibernate.boot.model.internal.AnnotatedColumn.buildFormulaFromAnnotation; @@ -568,12 +569,8 @@ private static JoinColumn[] mapKeyJoinColumnAnnotations( MemberDetails property, MetadataBuildingContext context) { final var modelsContext = context.getBootstrapContext().getModelsContext(); - - final var mapKeyJoinColumns = property.getRepeatedAnnotationUsages( - JpaAnnotations.MAP_KEY_JOIN_COLUMN, - modelsContext - ); - + final var mapKeyJoinColumns = + property.getRepeatedAnnotationUsages( JpaAnnotations.MAP_KEY_JOIN_COLUMN, modelsContext ); if ( isEmpty( mapKeyJoinColumns ) ) { return null; } @@ -593,7 +590,7 @@ private static AnnotatedColumns mapKeyColumns( MetadataBuildingContext context, MemberDetails property) { // Comment comment) { - return AnnotatedColumn.buildColumnFromAnnotations( + return buildColumnFromAnnotations( property.hasDirectAnnotationUsage( MapKeyColumn.class ) ? MapKeyColumnJpaAnnotation.toColumnAnnotation( property.getDirectAnnotationUsage( MapKeyColumn.class ), @@ -1502,17 +1499,7 @@ private boolean isReversePropertyInJoin( PersistentClass persistentClass, Map persistentClasses) { if ( persistentClass != null && isUnownedCollection() ) { - final Property mappedByProperty; - try { - mappedByProperty = persistentClass.getRecursiveProperty( mappedBy ); - } - catch (MappingException e) { - throw new AnnotationException( - "Collection '" + safeCollectionRole() - + "' is 'mappedBy' a property named '" + mappedBy - + "' which does not exist in the target entity '" + elementType.getName() + "'" - ); - } + final var mappedByProperty = getMappedByProperty( elementType, persistentClass ); checkMappedByType( mappedBy, mappedByProperty.getValue(), propertyName, propertyHolder, persistentClasses ); return persistentClass.getJoinNumber( mappedByProperty ) != 0; } @@ -1521,6 +1508,19 @@ private boolean isReversePropertyInJoin( } } + private Property getMappedByProperty(TypeDetails elementType, PersistentClass persistentClass) { + try { + return persistentClass.getRecursiveProperty( mappedBy ); + } + catch (MappingException e) { + throw new AnnotationException( + "Collection '" + safeCollectionRole() + + "' is 'mappedBy' a property named '" + mappedBy + + "' which does not exist in the target entity '" + elementType.getName() + "'" + ); + } + } + private boolean noAssociationTable(Map persistentClasses) { final var persistentClass = persistentClasses.get( getElementType().getName() ); return persistentClass != null @@ -2377,10 +2377,10 @@ private void handleOwnedManyToMany(PersistentClass collectionEntity, boolean isC owner.getEntityName(), owner.getJpaEntityName(), collector.getLogicalTableName( owner.getTable() ), - collectionEntity != null ? collectionEntity.getClassName() : null, - collectionEntity != null ? collectionEntity.getEntityName() : null, - collectionEntity != null ? collectionEntity.getJpaEntityName() : null, - collectionEntity != null ? collector.getLogicalTableName( collectionEntity.getTable() ) : null, + collectionEntity == null ? null : collectionEntity.getClassName(), + collectionEntity == null ? null : collectionEntity.getEntityName(), + collectionEntity == null ? null : collectionEntity.getJpaEntityName(), + collectionEntity == null ? null : collector.getLogicalTableName( collectionEntity.getTable() ), joinColumns.getPropertyName() ); } @@ -2435,34 +2435,30 @@ private void handleUnownedManyToMany( TypeDetails elementType, PersistentClass collectionEntity, boolean isCollectionOfEntities) { - if ( !isCollectionOfEntities) { + if ( !isCollectionOfEntities ) { throw new AnnotationException( "Association '" + safeCollectionRole() + "'" + targetEntityMessage( elementType ) ); } joinColumns.setManyToManyOwnerSideEntityName( collectionEntity.getEntityName() ); - - final Property otherSideProperty; - try { - otherSideProperty = collectionEntity.getRecursiveProperty( mappedBy ); - } - catch ( MappingException e ) { - throw new AnnotationException( "Association '" + safeCollectionRole() - + "is 'mappedBy' a property named '" + mappedBy - + "' which does not exist in the target entity '" + elementType.getName() + "'" ); - } - final var otherSidePropertyValue = otherSideProperty.getValue(); + final var mappedByPropertyValue = + getMappedByProperty( elementType, collectionEntity ) + .getValue(); final var table = - otherSidePropertyValue instanceof Collection collectionProperty + mappedByPropertyValue instanceof Collection collectionProperty // this is a collection on the other side ? collectionProperty.getCollectionTable() // this is a ToOne with a @JoinTable or a regular property - : otherSidePropertyValue.getTable(); + : mappedByPropertyValue.getTable(); collection.setCollectionTable( table ); + processSoftDeletes(); + checkCheckAnnotation(); + } + private void checkCheckAnnotation() { if ( property.hasDirectAnnotationUsage( Checks.class ) - || property.hasDirectAnnotationUsage( Check.class ) ) { + || property.hasDirectAnnotationUsage( Check.class ) ) { throw new AnnotationException( "Association '" + safeCollectionRole() + " is an unowned collection and may not be annotated '@Check'" ); } @@ -2497,9 +2493,10 @@ else if (isManyToAny) { } static String targetEntityMessage(TypeDetails elementType) { - final String problem = elementType.determineRawClass().hasDirectAnnotationUsage( Entity.class ) - ? " which does not belong to the same persistence unit" - : " which is not an '@Entity' type"; + final String problem = + elementType.determineRawClass().hasDirectAnnotationUsage( Entity.class ) + ? " which does not belong to the same persistence unit" + : " which is not an '@Entity' type"; return " targets the type '" + elementType.getName() + "'" + problem; } @@ -2507,15 +2504,16 @@ private static Class resolveCustomInstantiator MemberDetails property, TypeDetails propertyClass, MetadataBuildingContext context) { - final var propertyAnnotation - = property.getDirectAnnotationUsage( org.hibernate.annotations.EmbeddableInstantiator.class ); + final var propertyAnnotation = + property.getDirectAnnotationUsage( org.hibernate.annotations.EmbeddableInstantiator.class ); if ( propertyAnnotation != null ) { return propertyAnnotation.value(); } final var rawPropertyClassDetails = propertyClass.determineRawClass(); - final var classAnnotation - = rawPropertyClassDetails.getDirectAnnotationUsage( org.hibernate.annotations.EmbeddableInstantiator.class ); + + final var classAnnotation = + rawPropertyClassDetails.getDirectAnnotationUsage( org.hibernate.annotations.EmbeddableInstantiator.class ); if ( classAnnotation != null ) { return classAnnotation.value(); } @@ -2687,49 +2685,54 @@ private void bindUnownedManyToManyInverseForeignKey( PersistentClass targetEntity, AnnotatedJoinColumns joinColumns, SimpleValue value) { - final Property property = targetEntity.getRecursiveProperty( mappedBy ); + final var mappedByProperty = targetEntity.getRecursiveProperty( mappedBy ); final var firstColumn = joinColumns.getJoinColumns().get(0); - for ( var selectable: mappedByColumns( targetEntity, property ) ) { + for ( var selectable: mappedByColumns( targetEntity, mappedByProperty ) ) { firstColumn.linkValueUsingAColumnCopy( (Column) selectable, value); } + final var manyToOne = (ManyToOne) value; + setReferencedProperty( targetEntity.getEntityName(), mappedBy, manyToOne ); + // Ensure that we copy over the delete action from the owner side before creating the foreign key + setOnDeleteAction( mappedByProperty, manyToOne ); + value.createForeignKey(); + } + + private void setReferencedProperty(String targetEntityName, String mappedBy, ManyToOne manyToOne) { final var metadataCollector = getMetadataCollector(); final String referencedPropertyName = - metadataCollector.getPropertyReferencedAssociation( targetEntity.getEntityName(), mappedBy ); - final var manyToOne = (ManyToOne) value; + metadataCollector.getPropertyReferencedAssociation( targetEntityName, mappedBy ); if ( referencedPropertyName != null ) { //TODO always a many to one? manyToOne.setReferencedPropertyName( referencedPropertyName ); - metadataCollector.addUniquePropertyReference( targetEntity.getEntityName(), referencedPropertyName ); + metadataCollector.addUniquePropertyReference( targetEntityName, referencedPropertyName ); } - // Ensure that we copy over the delete action from the owner side before creating the foreign key - if ( property.getValue() instanceof Collection collectionValue ) { + manyToOne.setReferenceToPrimaryKey( referencedPropertyName == null ); + } + + private static void setOnDeleteAction(Property mappedByProperty, ManyToOne manyToOne) { + final var mappedByPropertyValue = mappedByProperty.getValue(); + if ( mappedByPropertyValue instanceof Collection collectionValue ) { manyToOne.setOnDeleteAction( ( (SimpleValue) collectionValue.getKey() ).getOnDeleteAction() ); } - else if ( property.getValue() instanceof ToOne toOne ) { + else if ( mappedByPropertyValue instanceof ToOne toOne ) { manyToOne.setOnDeleteAction( toOne.getOnDeleteAction() ); } - manyToOne.setReferenceToPrimaryKey( referencedPropertyName == null ); - value.createForeignKey(); } private static List mappedByColumns(PersistentClass referencedEntity, Property property) { - if ( property.getValue() instanceof Collection collection ) { - return collection.getKey().getSelectables(); - } - else { - //find the appropriate reference key, can be in a join - KeyValue key = null; - for ( var join : referencedEntity.getJoins() ) { - if ( join.containsProperty(property) ) { - key = join.getKey(); - break; - } - } - if ( key == null ) { - key = property.getPersistentClass().getIdentifier(); + return property.getValue() instanceof Collection collection + ? collection.getKey().getSelectables() + : getReferenceKey( referencedEntity, property ).getSelectables(); + } + + private static KeyValue getReferenceKey(PersistentClass referencedEntity, Property property) { + //find the appropriate reference key, can be in a join + for ( var join : referencedEntity.getJoins() ) { + if ( join.containsProperty( property ) ) { + return join.getKey(); } - return key.getSelectables(); } + return property.getPersistentClass().getIdentifier(); } private void setFkJoinColumns(AnnotatedJoinColumns annotatedJoinColumns) { @@ -2770,7 +2773,7 @@ private void setLocalGenerators(Map local private void logOneToManySecondPass() { if ( BOOT_LOGGER.isTraceEnabled() ) { -BOOT_LOGGER.bindingOneToManyThroughForeignKey( safeCollectionRole() ); + BOOT_LOGGER.bindingOneToManyThroughForeignKey( safeCollectionRole() ); } } @@ -2779,19 +2782,19 @@ private void logManyToManySecondPass( boolean isCollectionOfEntities, boolean isManyToAny) { if ( BOOT_LOGGER.isTraceEnabled() ) { + final String role = safeCollectionRole(); if ( isCollectionOfEntities && isOneToMany ) { - BOOT_LOGGER.bindingOneToManyThroughAssociationTable( safeCollectionRole() ); + BOOT_LOGGER.bindingOneToManyThroughAssociationTable( role ); } else if ( isCollectionOfEntities ) { - BOOT_LOGGER.bindingManyToManyThroughAssociationTable( safeCollectionRole() ); + BOOT_LOGGER.bindingManyToManyThroughAssociationTable( role ); } else if ( isManyToAny ) { - BOOT_LOGGER.bindingManyToAny( safeCollectionRole() ); + BOOT_LOGGER.bindingManyToAny( role ); } else { - BOOT_LOGGER.bindingElementCollectionToCollectionTable( safeCollectionRole() ); + BOOT_LOGGER.bindingElementCollectionToCollectionTable( role ); } } } - } 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 696ec9bab2a5..e553f89e1c2b 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 @@ -109,6 +109,7 @@ import static org.hibernate.boot.model.internal.QueryBinder.bindQuery; import static org.hibernate.boot.model.internal.TableBinder.bindForeignKey; import static org.hibernate.boot.model.naming.Identifier.toIdentifier; +import static org.hibernate.boot.spi.AccessType.getAccessStrategy; import static org.hibernate.engine.OptimisticLockStyle.fromLockType; import static org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle.fromResultCheckStyle; import static org.hibernate.boot.BootLogging.BOOT_LOGGER; @@ -462,12 +463,12 @@ private boolean mapAsIdClass( MetadataBuildingContext context, ClassDetails compositeClass, ClassDetails classWithIdClass) { - final TypeDetails compositeType = new ClassTypeDetailsImpl( compositeClass, TypeDetails.Kind.CLASS ); - final TypeDetails classWithIdType = new ClassTypeDetailsImpl( classWithIdClass, TypeDetails.Kind.CLASS ); + final var compositeType = new ClassTypeDetailsImpl( compositeClass, TypeDetails.Kind.CLASS ); + final var classWithIdType = new ClassTypeDetailsImpl( classWithIdClass, TypeDetails.Kind.CLASS ); - final AccessType accessType = getPropertyAccessType(); - final PropertyData inferredData = new PropertyPreloadedData( accessType, "id", compositeType ); - final PropertyData baseInferredData = new PropertyPreloadedData( accessType, "id", classWithIdType ); + final var accessType = getPropertyAccessType(); + final var inferredData = new PropertyPreloadedData( accessType, "id", compositeType ); + final var baseInferredData = new PropertyPreloadedData( accessType, "id", classWithIdType ); final AccessType propertyAccessor = getPropertyAccessor( compositeClass ); // In JPA 2, there is a shortcut if the IdClass is the PK of the associated class pointed to by the id @@ -648,7 +649,7 @@ private boolean isIdClassPrimaryKeyOfAssociatedEntity( if ( elementsToProcess.getIdPropertyCount() == 1 ) { // There's only one @Id field, so it might be the @EmbeddedId of an associated // entity referenced via a @ManyToOne or @OneToOne association - final PropertyData idPropertyOnBaseClass = + final var idPropertyOnBaseClass = getUniqueIdPropertyFromBaseClass( inferredData, baseInferredData, propertyAccessor, context ); final var idTypeDetails = idPropertyOnBaseClass.getClassOrElementType(); final var state = inheritanceStates.get( idTypeDetails.determineRawClass() ); @@ -690,7 +691,7 @@ private static boolean isIdClassOfAssociatedEntity( final int idPropertyCount = addElementsOfClass( idProperties, propertyContainer, context, 0 ); if ( idPropertyCount == 1 ) { // Exactly one @Id or @EmbeddedId attribute - final PropertyData idPropertyOfAssociatedEntity = idProperties.get( 0 ); + final var idPropertyOfAssociatedEntity = idProperties.get( 0 ); return compositeClass.getName() .equals( idPropertyOfAssociatedEntity.getPropertyType().getName() ); } @@ -1427,8 +1428,8 @@ private A resolveCustomSqlAnnotation( // - we first look for all uses of DialectOverride.SQLInsert, if any, and see if they "match" // - if so, we return the matched override // - if not, we return the normal SQLInsert (if one) - final Class overrideAnnotation = getOverrideAnnotation( annotationType ); - final Annotation[] dialectOverrides = + final var overrideAnnotation = getOverrideAnnotation( annotationType ); + final var dialectOverrides = annotatedClass.getRepeatedAnnotationUsages( overrideAnnotation, modelsContext() ); if ( isNotEmpty( dialectOverrides ) ) { final var dialect = getDatabase().getDialect(); @@ -1784,7 +1785,7 @@ private void bindTableForDiscriminatedSubclass(String entityName) { final var collector = getMetadataCollector(); final var superTableXref = collector.getEntityTableXref( entityName ); - final Table primaryTable = superTableXref.getPrimaryTable(); + final var primaryTable = superTableXref.getPrimaryTable(); collector.addEntityTableXref( persistentClass.getEntityName(), collector.getDatabase().toIdentifier( collector.getLogicalTableName( primaryTable ) ), @@ -1803,7 +1804,7 @@ private void bindTable( InFlightMetadataCollector.EntityTableXref denormalizedSuperTableXref) { final String entityName = persistentClass.getEntityName(); final Identifier logicalTableName = logicalTableName( tableName, entityName ); - final Table table = TableBinder.buildAndFillTable( + final var table = TableBinder.buildAndFillTable( schema, catalog, logicalTableName, @@ -1868,14 +1869,20 @@ private void createPrimaryColumnsToSecondaryTable( Join join) { // `incoming` will be an array of some sort of annotation final var joinColumnSource = (Annotation[]) incoming; - final AnnotatedJoinColumns annotatedJoinColumns; + final var annotatedJoinColumns = secondaryTableJoinColumns( propertyHolder, joinColumnSource ); + for ( var joinColumn : annotatedJoinColumns.getJoinColumns() ) { + joinColumn.forceNotNull(); + } + bindJoinToPersistentClass( join, annotatedJoinColumns, context ); + } + + private AnnotatedJoinColumns secondaryTableJoinColumns(PropertyHolder propertyHolder, Annotation[] joinColumnSource) { if ( isEmpty( joinColumnSource ) ) { - annotatedJoinColumns = createDefaultJoinColumn( propertyHolder ); + return createDefaultJoinColumn( propertyHolder ); } else { final PrimaryKeyJoinColumn[] pkJoinColumns; final JoinColumn[] joinColumns; - final Annotation first = joinColumnSource[0]; if ( first instanceof PrimaryKeyJoinColumn ) { pkJoinColumns = (PrimaryKeyJoinColumn[]) joinColumnSource; @@ -1893,14 +1900,8 @@ else if ( first instanceof JoinColumn ) { ); } - - annotatedJoinColumns = createJoinColumns( propertyHolder, pkJoinColumns, joinColumns ); + return createJoinColumns( propertyHolder, pkJoinColumns, joinColumns ); } - - for ( var joinColumn : annotatedJoinColumns.getJoinColumns() ) { - joinColumn.forceNotNull(); - } - bindJoinToPersistentClass( join, annotatedJoinColumns, context ); } private AnnotatedJoinColumns createDefaultJoinColumn(PropertyHolder propertyHolder) { @@ -1971,14 +1972,16 @@ private void setForeignKeyNameIfDefined(Join join) { final var jpaSecondaryTable = findMatchingSecondaryTable( join ); if ( jpaSecondaryTable != null ) { final boolean noConstraintByDefault = context.getBuildingOptions().isNoConstraintByDefault(); - if ( jpaSecondaryTable.foreignKey().value() == ConstraintMode.NO_CONSTRAINT - || jpaSecondaryTable.foreignKey().value() == ConstraintMode.PROVIDER_DEFAULT && noConstraintByDefault ) { + final var foreignKey = jpaSecondaryTable.foreignKey(); + final var constraintMode = foreignKey.value(); + if ( constraintMode == ConstraintMode.NO_CONSTRAINT + || constraintMode == ConstraintMode.PROVIDER_DEFAULT && noConstraintByDefault ) { key.disableForeignKey(); } else { - key.setForeignKeyName( nullIfEmpty( jpaSecondaryTable.foreignKey().name() ) ); - key.setForeignKeyDefinition( nullIfEmpty( jpaSecondaryTable.foreignKey().foreignKeyDefinition() ) ); - key.setForeignKeyOptions( jpaSecondaryTable.foreignKey().options() ); + key.setForeignKeyName( nullIfEmpty( foreignKey.name() ) ); + key.setForeignKeyDefinition( nullIfEmpty( foreignKey.foreignKeyDefinition() ) ); + key.setForeignKeyOptions( foreignKey.options() ); } } } @@ -2234,7 +2237,7 @@ private AccessType getExplicitAccessType(AnnotationTarget element) { if ( element != null ) { final var access = element.getAnnotationUsage( Access.class, modelsContext() ); if ( access != null ) { - return AccessType.getAccessStrategy( access.value() ); + return getAccessStrategy( access.value() ); } } return null; diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/GeneratorBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/GeneratorBinder.java index 4af123243fff..201abc49b146 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/GeneratorBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/GeneratorBinder.java @@ -69,6 +69,7 @@ import static org.hibernate.internal.util.StringHelper.isNotEmpty; import static org.hibernate.internal.util.StringHelper.qualify; import static org.hibernate.internal.util.collections.CollectionHelper.combineUntyped; +import static org.hibernate.resource.beans.internal.Helper.getBean; /** * Responsible for configuring and instantiating {@link Generator}s. @@ -95,48 +96,80 @@ public boolean isAssigned() { */ public static void makeIdGenerator( SimpleValue identifierValue, - MemberDetails idAttributeMember, + MemberDetails idMember, String generatorType, String generatorName, MetadataBuildingContext context, Map localGenerators) { //generator settings - final Map configuration = new HashMap<>(); - configuration.put( GENERATOR_NAME, generatorName ); - configuration.put( PersistentIdentifierGenerator.TABLE, identifierValue.getTable().getName() ); - if ( identifierValue.getColumnSpan() == 1 ) { - configuration.put( PersistentIdentifierGenerator.PK, identifierValue.getColumns().get(0).getName() ); - } + final var configuration = initializeGeneratorSettings( identifierValue, generatorName ); + final String generatorStrategy; if ( generatorName.isEmpty() ) { - final var generatedValue = idAttributeMember.getDirectAnnotationUsage( GeneratedValue.class ); - if ( generatedValue != null ) { - // The mapping used @GeneratedValue but specified no name. This is a special case added in JPA 3.2. - // Look for a matching "implied generator" based on the GenerationType - final var strategy = generatedValue.strategy(); - final String strategyGeneratorClassName = correspondingGeneratorName( strategy ); - final var impliedGenerator = - determineImpliedGenerator( strategy, strategyGeneratorClassName, localGenerators ); - if ( impliedGenerator != null ) { - configuration.putAll( impliedGenerator.getParameters() ); - instantiateNamedStrategyGenerator( identifierValue, strategyGeneratorClassName, configuration, context ); - return; - } + if ( idMember.hasDirectAnnotationUsage( GeneratedValue.class ) + && handleDefaultGenerator( identifierValue, context, localGenerators, idMember, configuration ) ) { + // we found an appropriate a "default" generator (as per JPA 3.2) + return; // EARLY EXIT + } + else { + generatorStrategy = generatorType; } } + else if ( generatorName.isBlank() ) { + throw new MappingException( "Generator name is cannot be blank" ); + } + else { + //we have a named generator + generatorStrategy = determineStrategy( + idMember, + generatorType, + generatorName, + context, + localGenerators, + configuration + ); + } - final String generatorStrategy = determineStrategy( - idAttributeMember, - generatorType, - generatorName, - context, - localGenerators, - configuration - ); setGeneratorCreator( identifierValue, configuration, generatorStrategy, context ); } + /** + * Called if {@link @GeneratedValue} specified no name. + * This is a new special case added in JPA 3.2. + * We look for an appropriate matching "default generator recipe" + * based on the {@link GenerationType}. + */ + private static boolean handleDefaultGenerator( + SimpleValue identifierValue, + MetadataBuildingContext context, + Map localGenerators, + MemberDetails idMember, + Map configuration) { + final var strategy = idMember.getDirectAnnotationUsage( GeneratedValue.class ).strategy(); + final String strategyGeneratorClassName = correspondingGeneratorName( strategy ); + final var impliedGenerator = + determineImpliedGenerator( strategy, strategyGeneratorClassName, localGenerators ); + if ( impliedGenerator != null ) { + configuration.putAll( impliedGenerator.getParameters() ); + instantiateNamedStrategyGenerator( identifierValue, strategyGeneratorClassName, configuration, context ); + return true; + } + else { + return false; + } + } + + private static Map initializeGeneratorSettings(SimpleValue identifierValue, String generatorName) { + final Map configuration = new HashMap<>(); + configuration.put( GENERATOR_NAME, generatorName ); + configuration.put( PersistentIdentifierGenerator.TABLE, identifierValue.getTable().getName() ); + if ( identifierValue.getColumnSpan() == 1 ) { + configuration.put( PersistentIdentifierGenerator.PK, identifierValue.getColumns().get(0).getName() ); + } + return configuration; + } + private static IdentifierGeneratorDefinition determineImpliedGenerator( GenerationType strategy, String strategyGeneratorClassName, @@ -148,13 +181,20 @@ private static IdentifierGeneratorDefinition determineImpliedGenerator( if ( localGenerators.size() == 1 ) { final var generatorDefinition = localGenerators.values().iterator().next(); // NOTE: a little bit of a special rule here for the case of just one - - // we consider it a match, based on strategy, if the strategy is AUTO or matches + // consider it a match, based on strategy, if the strategy is AUTO or matches if ( strategy == AUTO || isImpliedGenerator( strategy, strategyGeneratorClassName, generatorDefinition ) ) { return generatorDefinition; } } + return matchingLocalGenerator( strategy, strategyGeneratorClassName, localGenerators ); + } + + private static IdentifierGeneratorDefinition matchingLocalGenerator( + GenerationType strategy, + String strategyGeneratorClassName, + Map localGenerators) { IdentifierGeneratorDefinition matching = null; for ( var localGenerator : localGenerators.values() ) { if ( isImpliedGenerator( strategy, strategyGeneratorClassName, localGenerator ) ) { @@ -192,29 +232,25 @@ private static String determineStrategy( MetadataBuildingContext context, Map localGenerators, Map configuration) { - if ( !generatorName.isEmpty() ) { - //we have a named generator - final var definition = - makeIdentifierGeneratorDefinition( generatorName, idAttributeMember, localGenerators, context ); - if ( definition == null ) { - throw new AnnotationException( "No id generator was declared with the name '" + generatorName - + "' specified by '@GeneratedValue'" - + " (define a named generator using '@SequenceGenerator', '@TableGenerator', or '@GenericGenerator')" ); - } - //This is quite vague in the spec but a generator could override the generator choice - final String generatorStrategy = - generatorType == null - //yuk! this is a hack not to override 'AUTO' even if generator is set - || !definition.getStrategy().equals( "identity" ) - ? definition.getStrategy() - : generatorType; - //checkIfMatchingGenerator(definition, generatorType, generatorName); - configuration.putAll( definition.getParameters() ); - return generatorStrategy; - } - else { - return generatorType; + final var definition = + makeIdentifierGeneratorDefinition( generatorName, idAttributeMember, localGenerators, context ); + if ( definition == null ) { + throw new AnnotationException( "No id generator was declared with the name '" + generatorName + + "' specified by '@GeneratedValue'" + + " (define a named generator using '@SequenceGenerator', '@TableGenerator', or '@GenericGenerator')" ); } + configuration.putAll( definition.getParameters() ); + // This is quite vague in the spec, but a generator could override the generator choice + return generatorStrategy( generatorType, definition ); + } + + private static String generatorStrategy(String generatorType, IdentifierGeneratorDefinition definition) { + return generatorType != null + // Yuck! this is a hack to not override 'AUTO', + // even if GeneratedValue.generator is specified + && definition.getStrategy().equals( "identity" ) + ? generatorType + : definition.getStrategy(); } private static IdentifierGeneratorDefinition makeIdentifierGeneratorDefinition( @@ -286,6 +322,14 @@ public static void registerGlobalGenerators( } private static IdentifierGeneratorDefinition buildIdGenerator(GenericGenerator generatorAnnotation) { + final var definitionBuilder = genericDefinitionBuilder( generatorAnnotation ); + if ( BOOT_LOGGER.isTraceEnabled() ) { + BOOT_LOGGER.addedGenerator( definitionBuilder.getName(), definitionBuilder.getStrategy() ); + } + return definitionBuilder.build(); + } + + private static IdentifierGeneratorDefinition.Builder genericDefinitionBuilder(GenericGenerator generatorAnnotation) { final var definitionBuilder = new IdentifierGeneratorDefinition.Builder(); definitionBuilder.setName( generatorAnnotation.name() ); final var generatorClass = generatorAnnotation.type(); @@ -295,11 +339,7 @@ private static IdentifierGeneratorDefinition buildIdGenerator(GenericGenerator g : generatorClass.getName(); definitionBuilder.setStrategy( strategy ); definitionBuilder.addParams( extractParameterMap( generatorAnnotation.parameters() ) ); - - if ( BOOT_LOGGER.isTraceEnabled() ) { - BOOT_LOGGER.addedGenerator( definitionBuilder.getName(), definitionBuilder.getStrategy() ); - } - return definitionBuilder.build(); + return definitionBuilder; } private static IdentifierGeneratorDefinition buildSequenceIdGenerator(SequenceGenerator generatorAnnotation) { @@ -323,16 +363,16 @@ private static IdentifierGeneratorDefinition buildTableIdGenerator(TableGenerato private static void checkGeneratorClass(Class generatorClass) { if ( !BeforeExecutionGenerator.class.isAssignableFrom( generatorClass ) && !OnExecutionGenerator.class.isAssignableFrom( generatorClass ) ) { - throw new MappingException("Generator class '" + generatorClass.getName() - + "' must implement either 'BeforeExecutionGenerator' or 'OnExecutionGenerator'"); + throw new MappingException( "Generator class '" + generatorClass.getName() + + "' must implement either 'BeforeExecutionGenerator' or 'OnExecutionGenerator'" ); } } private static void checkGeneratorInterfaces(Class generatorClass) { // A regular value generator should not implement legacy IdentifierGenerator if ( IdentifierGenerator.class.isAssignableFrom( generatorClass ) ) { - throw new AnnotationException("Generator class '" + generatorClass.getName() - + "' implements 'IdentifierGenerator' and may not be used with '@ValueGenerationType'"); + throw new AnnotationException( "Generator class '" + generatorClass.getName() + + "' implements 'IdentifierGenerator' and may not be used with '@ValueGenerationType'" ); } } @@ -352,7 +392,7 @@ private static GeneratorCreator generatorCreator( checkGeneratorClass( generatorClass ); checkGeneratorInterfaces( generatorClass ); return creationContext -> { - final Generator generator = + final var generator = instantiateAndInitializeGenerator( value, annotation, @@ -375,7 +415,7 @@ private static Generator instantiateAndInitializeGenerator( Class generatorClass, MemberDetails memberDetails, Class annotationType) { - final Generator generator = instantiateGenerator( + final var generator = instantiateGenerator( annotation, beanContainer, creationContext, @@ -469,7 +509,7 @@ private static Generator instantiateGeneratorAsBean( Class generatorClass, MemberDetails memberDetails, Class annotationType) { - return Helper.getBean( + return getBean( beanContainer, generatorClass, false, @@ -494,7 +534,7 @@ private static Generator instantiateGeneratorAsBean( private static T instantiateGeneratorAsBean( BeanContainer beanContainer, Class generatorClass) { - return Helper.getBean( + return getBean( beanContainer, generatorClass, false, @@ -617,7 +657,8 @@ public static void callConfigure( creationContext.getDatabase().getDialect(), creationContext.getRootClass(), configuration, - creationContext.getServiceRegistry().requireService( ConfigurationService.class ) + creationContext.getServiceRegistry() + .requireService( ConfigurationService.class ) ); configurable.configure( creationContext, parameters ); } @@ -632,11 +673,11 @@ public static void callConfigure( private static void checkIdGeneratorTiming(Class annotationType, Generator generator) { if ( !generator.generatesOnInsert() ) { throw new MappingException( "Annotation '" + annotationType - + "' is annotated 'IdGeneratorType' but the given 'Generator' does not generate on inserts"); + + "' is annotated 'IdGeneratorType' but the given 'Generator' does not generate on inserts" ); } if ( generator.generatesOnUpdate() ) { throw new MappingException( "Annotation '" + annotationType - + "' is annotated 'IdGeneratorType' but the given 'Generator' generates on updates (it must generate only on inserts)"); + + "' is annotated 'IdGeneratorType' but the given 'Generator' generates on updates (it must generate only on inserts)" ); } } @@ -790,11 +831,11 @@ static void createIdGeneratorsFromGeneratorAnnotations( final var idMemberDetails = inferredData.getAttributeMember(); final var idGeneratorAnnotations = idMemberDetails.getMetaAnnotated( IdGeneratorType.class, modelsContext ); final var generatorAnnotations = idMemberDetails.getMetaAnnotated( ValueGenerationType.class, modelsContext ); - // Since these collections may contain Proxies created by common-annotations module we cannot reliably use simple remove/removeAll - // collection methods as those proxies do not implement hashcode/equals and even a simple `a.equals(a)` will return `false`. - // Instead, we will check the annotation types, since generator annotations should not be "repeatable" we should have only - // at most one annotation for a generator: - for ( Annotation id : idGeneratorAnnotations ) { + // Since these collections may contain proxies created by common-annotations module, we cannot reliably use + // simple remove/removeAll collection methods as those proxies do not implement hashcode/equals and even a + // simple 'a.equals(a)' will return 'false'. Instead, we will check the annotation types. Since generator + // annotations should not be "repeatable", we should have only at most one annotation for a generator. + for ( var id : idGeneratorAnnotations ) { generatorAnnotations.removeIf( gen -> gen.annotationType().equals( id.annotationType() ) ); } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/IdBagBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/IdBagBinder.java index ed7e0ac5a6b5..a19cbaf0f48a 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/IdBagBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/IdBagBinder.java @@ -11,7 +11,6 @@ import org.hibernate.MappingException; import org.hibernate.annotations.CollectionId; import org.hibernate.boot.spi.MetadataBuildingContext; -import org.hibernate.boot.spi.PropertyData; import org.hibernate.mapping.BasicValue; import org.hibernate.mapping.Collection; import org.hibernate.mapping.IdentifierBag; @@ -47,14 +46,14 @@ protected Collection createCollection(PersistentClass owner) { @Override protected boolean bindStarToManySecondPass(Map persistentClasses) { - boolean result = super.bindStarToManySecondPass( persistentClasses ); + final boolean result = super.bindStarToManySecondPass( persistentClasses ); final var collectionIdAnn = property.getDirectAnnotationUsage( CollectionId.class ); if ( collectionIdAnn == null ) { throw new MappingException( "idbag mapping missing '@CollectionId' annotation" ); } - final PropertyData propertyData = new WrappedInferredData( + final var propertyData = new WrappedInferredData( new PropertyInferredData( null, declaringClass, diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/IdGeneratorResolverSecondPass.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/IdGeneratorResolverSecondPass.java index 6281fbc4c1e9..90bae28d1dbf 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/IdGeneratorResolverSecondPass.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/IdGeneratorResolverSecondPass.java @@ -16,6 +16,7 @@ import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.SimpleValue; +import org.hibernate.models.spi.ClassDetailsRegistry; import org.hibernate.models.spi.MemberDetails; import jakarta.persistence.GeneratedValue; @@ -217,6 +218,22 @@ private void validateTableGeneration() { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // AUTO + private boolean handleAsUuid(ClassDetailsRegistry classDetailsRegistry) { + final var idMemberType = idMember.getType(); + if ( idMemberType.isImplementor( UUID.class ) || idMemberType.isImplementor( String.class ) ) { + GeneratorAnnotationHelper.handleUuidStrategy( + idValue, + idMember, + classDetailsRegistry.getClassDetails( entityMapping.getClassName() ), + buildingContext + ); + return true; + } + else { + return false; + } + } + @Override protected void handleUnnamedAutoGenerator() { // todo (7.0) : null or entityMapping.getJpaEntityName() for "name from GeneratedValue"? @@ -272,14 +289,7 @@ protected void handleUnnamedAutoGenerator() { return; } - if ( idMember.getType().isImplementor( UUID.class ) - || idMember.getType().isImplementor( String.class ) ) { - GeneratorAnnotationHelper.handleUuidStrategy( - idValue, - idMember, - classDetailsRegistry.getClassDetails( entityMapping.getClassName() ), - buildingContext - ); + if ( handleAsUuid( classDetailsRegistry ) ) { return; } @@ -316,15 +326,7 @@ protected void handleNamedAutoGenerator() { return; } - if ( idMember.getType().isImplementor( UUID.class ) - || idMember.getType().isImplementor( String.class ) ) { - GeneratorAnnotationHelper.handleUuidStrategy( - idValue, - idMember, - buildingContext.getMetadataCollector().getClassDetailsRegistry() - .getClassDetails( entityMapping.getClassName() ), - buildingContext - ); + if ( handleAsUuid( buildingContext.getMetadataCollector().getClassDetailsRegistry() ) ) { return; }