diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java index a1eaf06429..6bae325721 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java @@ -2824,9 +2824,8 @@ private static TypeResolution resolveType( } String typeName = typeSource.getName(); - Map typeParameters = new HashMap<>(); - final TypeDefinition typeDefinition = sourceDocument.getMetadataCollector().getTypeDefinition( typeName ); + final Map typeParameters = new HashMap<>(); if ( typeDefinition != null ) { // the explicit name referred to a type-def typeName = typeDefinition.getTypeImplementorClass().getName(); @@ -3302,13 +3301,10 @@ protected void bindCollectionKey() { final String propRef = keySource.getReferencedPropertyName(); getCollectionBinding().setReferencedPropertyName( propRef ); - final KeyValue keyVal; - if ( propRef == null ) { - keyVal = getCollectionBinding().getOwner().getIdentifier(); - } - else { - keyVal = (KeyValue) getCollectionBinding().getOwner().getRecursiveProperty( propRef ).getValue(); - } + final PersistentClass owner = getCollectionBinding().getOwner(); + final KeyValue keyVal = propRef == null + ? owner.getIdentifier() + : (KeyValue) owner.getRecursiveProperty( propRef ).getValue(); final DependantValue key = new DependantValue( mappingDocument, getCollectionBinding().getCollectionTable(), diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotatedColumn.java b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotatedColumn.java index 5253ec253a..37d9362f49 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotatedColumn.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotatedColumn.java @@ -44,7 +44,17 @@ import static org.hibernate.internal.util.StringHelper.isNotEmpty; /** - * A {@link jakarta.persistence.Column} annotation + * A mapping to a column, logically representing a + * {@link jakarta.persistence.Column} annotation, but not + * every instance corresponds to an explicit annotation in + * the Java code. + *

+ * This class holds a representation that is intermediate + * between the annotation of the Java source code, and the + * mapping model object {@link Column}. It's used only by + * the {@link AnnotationBinder} while parsing annotations, + * and does not survive into later stages of the startup + * process. * * @author Emmanuel Bernard */ @@ -66,7 +76,7 @@ public class AnnotatedColumn { private Integer precision; private Integer scale; private String logicalColumnName; - private String propertyName; + private String propertyName; // this is really a .-separated property path private boolean unique; private boolean nullable = true; private String formulaString; @@ -184,6 +194,9 @@ public void setPropertyName(String propertyName) { this.propertyName = propertyName; } + /** + * A property path relative to the {@link #getPropertyHolder() PropertyHolder}. + */ public String getPropertyName() { return propertyName; } @@ -328,7 +341,7 @@ public void redefineColumnName(String columnName, String propertyName, boolean a private String processColumnName(String columnName, boolean applyNamingStrategy) { if ( applyNamingStrategy ) { - Database database = context.getMetadataCollector().getDatabase(); + final Database database = context.getMetadataCollector().getDatabase(); return context.getBuildingOptions().getPhysicalNamingStrategy() .toPhysicalColumnName( database.toIdentifier( columnName ), database.getJdbcEnvironment() ) .render( database.getDialect() ); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotatedJoinColumn.java b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotatedJoinColumn.java index f9b037df7b..fabb992dfd 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotatedJoinColumn.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotatedJoinColumn.java @@ -7,7 +7,6 @@ package org.hibernate.cfg; import java.util.List; -import java.util.Locale; import java.util.Map; import jakarta.persistence.JoinColumn; import jakarta.persistence.PrimaryKeyJoinColumn; @@ -16,26 +15,15 @@ import org.hibernate.AssertionFailure; import org.hibernate.MappingException; import org.hibernate.annotations.Comment; -import org.hibernate.annotations.JoinColumnOrFormula; import org.hibernate.annotations.JoinFormula; -import org.hibernate.annotations.common.reflection.XClass; -import org.hibernate.boot.model.naming.EntityNaming; import org.hibernate.boot.model.naming.Identifier; -import org.hibernate.boot.model.naming.ImplicitJoinColumnNameSource; -import org.hibernate.boot.model.naming.ImplicitNamingStrategy; -import org.hibernate.boot.model.naming.ImplicitPrimaryKeyJoinColumnNameSource; import org.hibernate.boot.model.naming.ObjectNameNormalizer; -import org.hibernate.boot.model.naming.PhysicalNamingStrategy; import org.hibernate.boot.model.relational.Database; -import org.hibernate.boot.model.source.spi.AttributePath; import org.hibernate.boot.spi.InFlightMetadataCollector; import org.hibernate.boot.spi.MetadataBuildingContext; -import org.hibernate.boot.spi.MetadataBuildingOptions; -import org.hibernate.internal.util.StringHelper; import org.hibernate.mapping.Column; import org.hibernate.mapping.Join; import org.hibernate.mapping.PersistentClass; -import org.hibernate.mapping.Property; import org.hibernate.mapping.Selectable; import org.hibernate.mapping.SimpleValue; import org.hibernate.mapping.Table; @@ -47,99 +35,35 @@ import static org.hibernate.cfg.BinderHelper.isEmptyOrNullAnnotationValue; import static org.hibernate.internal.util.StringHelper.isEmpty; import static org.hibernate.internal.util.StringHelper.isNotEmpty; +import static org.hibernate.internal.util.StringHelper.isQuoted; import static org.hibernate.internal.util.StringHelper.qualify; +import static org.hibernate.internal.util.StringHelper.unquote; /** - * A {@link jakarta.persistence.JoinColumn} annotation + * An element of a join condition, logically representing a + * {@link jakarta.persistence.JoinColumn} annotation, but not + * every instance corresponds to an explicit annotation in the + * Java code. + *

+ * There's no exact analog of this class in the mapping model, + * so some information is lost when it's transformed into a + * {@link Column}. * * @author Emmanuel Bernard */ public class AnnotatedJoinColumn extends AnnotatedColumn { private String referencedColumn; - - @Deprecated private String mappedBy; - @Deprecated private String mappedByPropertyName; //property name on the owning side if any - @Deprecated private String mappedByTableName; //table name on the mapped by side if any - @Deprecated private String mappedByEntityName; - @Deprecated private boolean JPA2ElementCollection; - @Deprecated private String manyToManyOwnerSideEntityName; + private AnnotatedJoinColumns parent; // due to @AnnotationOverride overriding rules, // we don't want the constructor to be public - private AnnotatedJoinColumn() { - mappedBy = ""; - } - - /** - * @deprecated this is not a column-level setting, so it's better to - * hang this information somewhere else - */ - @Deprecated - public void setJPA2ElementCollection(boolean JPA2ElementCollection) { - this.JPA2ElementCollection = JPA2ElementCollection; - } - - /** - * @deprecated this is not a column-level setting, so it's better to - * get this information from somewhere else - */ - @Deprecated - public String getManyToManyOwnerSideEntityName() { - return manyToManyOwnerSideEntityName; - } - - /** - * @deprecated this is not a column-level setting, so it's better to - * get this information from somewhere else - */ - @Deprecated - public void setManyToManyOwnerSideEntityName(String manyToManyOwnerSideEntityName) { - this.manyToManyOwnerSideEntityName = manyToManyOwnerSideEntityName; - } + private AnnotatedJoinColumn() {} public void setReferencedColumn(String referencedColumn) { this.referencedColumn = referencedColumn; } - /** - * @deprecated this is not a column-level setting, so it's better to - * get this information from somewhere else - */ - @Deprecated - public String getMappedBy() { - return mappedBy; - } - - /** - * @deprecated this is not column-level information, so it's better to - * hang all this information somewhere else - */ - public void setMappedBy(String entityName, String logicalTableName, String mappedByProperty) { - mappedByEntityName = entityName; - mappedByTableName = logicalTableName; - mappedByPropertyName = mappedByProperty; - } - - /** - * @deprecated this is not a column-level setting, so it's better to - * hang this information somewhere else - */ - @Deprecated - public void setMappedBy(String mappedBy) { - this.mappedBy = mappedBy; - } - - /** - * @return true if the association mapping annotation did specify - * {@link jakarta.persistence.OneToMany#mappedBy() mappedBy}, - * meaning that this {@code @JoinColumn} mapping belongs to an - * unowned many-valued association. - */ - public boolean hasMappedBy() { - return !isEmptyOrNullAnnotationValue( mappedBy ); - } - /** * The {@link JoinColumn#referencedColumnName() referencedColumnName}. */ @@ -155,138 +79,58 @@ public boolean isReferenceImplicit() { return isEmptyOrNullAnnotationValue( referencedColumn ); } - public static AnnotatedJoinColumn[] buildJoinColumnsOrFormulas( - JoinColumnOrFormula[] joinColumnOrFormulas, + static AnnotatedJoinColumn buildJoinColumn( + JoinColumn joinColumn, String mappedBy, Map joins, PropertyHolder propertyHolder, String propertyName, MetadataBuildingContext buildingContext) { - final AnnotatedJoinColumn[] joinColumns = new AnnotatedJoinColumn[joinColumnOrFormulas.length]; - for (int i = 0; i < joinColumnOrFormulas.length; i++) { - JoinColumnOrFormula columnOrFormula = joinColumnOrFormulas[i]; - JoinFormula formula = columnOrFormula.formula(); - if ( formula.value() != null && !formula.value().isEmpty() ) { - joinColumns[i] = buildJoinFormula( formula, joins, propertyHolder, propertyName, buildingContext ); - } - else { - joinColumns[i] = buildJoinColumn( mappedBy, joins, propertyHolder, propertyName, buildingContext, columnOrFormula ); - } + final String path = qualify( propertyHolder.getPath(), propertyName ); + final JoinColumn[] overriddes = propertyHolder.getOverriddenJoinColumn( path ); + if ( overriddes != null ) { + //TODO: relax this restriction + throw new AnnotationException("Property '" + path + + "' overrides mapping specified using '@JoinColumnOrFormula'"); } - return joinColumns; - } - - private static AnnotatedJoinColumn buildJoinColumn( - String mappedBy, - Map joins, - PropertyHolder propertyHolder, - String propertyName, MetadataBuildingContext buildingContext, - JoinColumnOrFormula join) { - return buildJoinColumns( - new JoinColumn[]{ join.column() }, + return buildJoinColumn( + joinColumn, null, mappedBy, joins, propertyHolder, propertyName, - buildingContext - )[0]; - } - - /** - * build join formula - */ - public static AnnotatedJoinColumn buildJoinFormula( - JoinFormula joinFormula, - Map joins, - PropertyHolder propertyHolder, - String propertyName, - MetadataBuildingContext buildingContext) { - AnnotatedJoinColumn formulaColumn = new AnnotatedJoinColumn(); - formulaColumn.setFormula( joinFormula.value() ); - formulaColumn.setReferencedColumn(joinFormula.referencedColumnName()); - formulaColumn.setBuildingContext( buildingContext ); - formulaColumn.setPropertyHolder( propertyHolder ); - formulaColumn.setPropertyName( getRelativePath( propertyHolder, propertyName ) ); - formulaColumn.setJoins( joins ); - formulaColumn.bind(); - return formulaColumn; - } - - public static AnnotatedJoinColumn[] buildJoinColumns( - JoinColumn[] joinColumns, - Comment comment, - String mappedBy, - Map joins, - PropertyHolder propertyHolder, - String propertyName, - MetadataBuildingContext buildingContext) { - return buildJoinColumnsWithDefaultColumnSuffix( - joinColumns, - comment, - mappedBy, - joins, - propertyHolder, - propertyName, "", buildingContext ); } - public static AnnotatedJoinColumn[] buildJoinColumnsWithDefaultColumnSuffix( - JoinColumn[] joinColumns, + public static AnnotatedJoinColumn buildJoinFormula( + JoinFormula joinFormula, + Map joins, + PropertyHolder propertyHolder, + String propertyName, + MetadataBuildingContext buildingContext) { + final AnnotatedJoinColumn formulaColumn = new AnnotatedJoinColumn(); + formulaColumn.setFormula( joinFormula.value() ); + formulaColumn.setReferencedColumn( joinFormula.referencedColumnName() ); + formulaColumn.setBuildingContext( buildingContext ); + formulaColumn.setPropertyHolder( propertyHolder ); + formulaColumn.setPropertyName( getRelativePath( propertyHolder, propertyName ) ); + formulaColumn.setJoins( joins ); + formulaColumn.bind(); + return formulaColumn; + } + + static AnnotatedJoinColumn buildJoinColumn( + JoinColumn joinColumn, Comment comment, String mappedBy, Map joins, PropertyHolder propertyHolder, String propertyName, - String suffixForDefaultColumnName, - MetadataBuildingContext buildingContext) { - JoinColumn[] overriddes = propertyHolder.getOverriddenJoinColumn( qualify( propertyHolder.getPath(), propertyName ) ); - JoinColumn[] actualColumns = overriddes == null ? joinColumns : overriddes; - if ( actualColumns == null || actualColumns.length == 0 ) { - return new AnnotatedJoinColumn[] { - buildJoinColumn( - null, - comment, - mappedBy, - joins, - propertyHolder, - propertyName, - suffixForDefaultColumnName, - buildingContext - ) - }; - } - else { - final AnnotatedJoinColumn[] result = new AnnotatedJoinColumn[actualColumns.length]; - for (int index = 0; index < actualColumns.length; index++ ) { - result[index] = buildJoinColumn( - actualColumns[index], - comment, - mappedBy, - joins, - propertyHolder, - propertyName, - suffixForDefaultColumnName, - buildingContext - ); - } - return result; - } - } - - /** - * build join column for SecondaryTables - */ - private static AnnotatedJoinColumn buildJoinColumn( - JoinColumn joinColumn, - Comment comment, - String mappedBy, Map joins, - PropertyHolder propertyHolder, - String propertyName, - String suffixForDefaultColumnName, - MetadataBuildingContext buildingContext) { + String defaultColumnSuffix, + MetadataBuildingContext context) { if ( joinColumn != null ) { if ( !isEmptyOrNullAnnotationValue( mappedBy ) ) { throw new AnnotationException( @@ -294,40 +138,59 @@ private static AnnotatedJoinColumn buildJoinColumn( + "' is 'mappedBy' a different entity and may not explicitly specify the '@JoinColumn'" ); } - AnnotatedJoinColumn column = new AnnotatedJoinColumn(); - column.setComment( comment != null ? comment.value() : null ); - column.setBuildingContext( buildingContext ); - column.setJoinAnnotation( joinColumn, null ); - if ( isEmpty( column.getLogicalColumnName() ) && isNotEmpty( suffixForDefaultColumnName ) ) { - column.setLogicalColumnName( propertyName + suffixForDefaultColumnName ); - } - column.setJoins( joins ); - column.setPropertyHolder( propertyHolder ); - column.setPropertyName( getRelativePath( propertyHolder, propertyName ) ); - column.setImplicit( false ); - column.bind(); - return column; + return explicitJoinColumn( joinColumn, comment, joins, propertyHolder, propertyName, defaultColumnSuffix, context ); } else { - AnnotatedJoinColumn column = new AnnotatedJoinColumn(); - column.setMappedBy( mappedBy ); - column.setJoins( joins ); - column.setPropertyHolder( propertyHolder ); - column.setPropertyName( getRelativePath( propertyHolder, propertyName ) ); - // property name + suffix is an "explicit" column name - if ( isNotEmpty( suffixForDefaultColumnName ) ) { - column.setLogicalColumnName( propertyName + suffixForDefaultColumnName ); - column.setImplicit( false ); - } - else { - column.setImplicit( true ); - } - column.setBuildingContext( buildingContext ); - column.bind(); - return column; + return implicitJoinColumn( joins, propertyHolder, propertyName, defaultColumnSuffix, context ); } } + private static AnnotatedJoinColumn explicitJoinColumn( + JoinColumn joinColumn, + Comment comment, + Map joins, + PropertyHolder propertyHolder, + String propertyName, + String defaultColumnSuffix, + MetadataBuildingContext context) { + final AnnotatedJoinColumn column = new AnnotatedJoinColumn(); + column.setComment( comment != null ? comment.value() : null ); + column.setBuildingContext( context ); + column.setJoinAnnotation(joinColumn, null ); + if ( isEmpty( column.getLogicalColumnName() ) && isNotEmpty( defaultColumnSuffix ) ) { + column.setLogicalColumnName( propertyName + defaultColumnSuffix ); + } + column.setJoins( joins ); + column.setPropertyHolder( propertyHolder ); + column.setPropertyName( getRelativePath( propertyHolder, propertyName ) ); + column.setImplicit( false ); + column.bind(); + return column; + } + + private static AnnotatedJoinColumn implicitJoinColumn( + Map joins, + PropertyHolder propertyHolder, + String propertyName, + String defaultColumnSuffix, + MetadataBuildingContext context) { + final AnnotatedJoinColumn column = new AnnotatedJoinColumn(); + column.setJoins( joins ); + column.setPropertyHolder( propertyHolder ); + column.setPropertyName( getRelativePath( propertyHolder, propertyName ) ); + // property name + suffix is an "explicit" column name + if ( isNotEmpty( defaultColumnSuffix ) ) { + column.setLogicalColumnName( propertyName + defaultColumnSuffix ); + column.setImplicit( false ); + } + else { + column.setImplicit( true ); + } + column.setBuildingContext( context ); + column.bind(); + return column; + } + // TODO default name still useful in association table public void setJoinAnnotation(JoinColumn joinColumn, String defaultName) { @@ -337,7 +200,8 @@ public void setJoinAnnotation(JoinColumn joinColumn, String defaultName) { else { setImplicit( false ); if ( !isEmptyAnnotationValue( joinColumn.columnDefinition() ) ) { - setSqlType( getBuildingContext().getObjectNameNormalizer().applyGlobalQuoting( joinColumn.columnDefinition() ) ); + setSqlType( getBuildingContext().getObjectNameNormalizer() + .applyGlobalQuoting( joinColumn.columnDefinition() ) ); } if ( !isEmptyAnnotationValue( joinColumn.name() ) ) { setLogicalColumnName( joinColumn.name() ); @@ -372,72 +236,67 @@ public static AnnotatedJoinColumn buildJoinColumn( Map joins, PropertyHolder propertyHolder, MetadataBuildingContext context) { - - final String defaultColumnName = context.getMetadataCollector().getLogicalColumnName( - identifier.getTable(), - identifier.getColumns().get(0).getQuotedName() - ); - final ObjectNameNormalizer normalizer = context.getObjectNameNormalizer(); - - if ( primaryKeyJoinColumn != null || joinColumn != null ) { - final String columnName; - final String columnDefinition; - final String referencedColumnName; - if ( primaryKeyJoinColumn != null ) { - columnName = primaryKeyJoinColumn.name(); - columnDefinition = primaryKeyJoinColumn.columnDefinition(); - referencedColumnName = primaryKeyJoinColumn.referencedColumnName(); - } - else { - columnName = joinColumn.name(); - columnDefinition = joinColumn.columnDefinition(); - referencedColumnName = joinColumn.referencedColumnName(); - } - final String columnDef = columnDefinition.isEmpty() ? null - : normalizer.toDatabaseIdentifierText( columnDefinition ); - final String logicalColumnName = columnName != null && columnName.isEmpty() - ? normalizer.normalizeIdentifierQuotingAsString( defaultColumnName ) - : normalizer.normalizeIdentifierQuotingAsString( columnName ); - AnnotatedJoinColumn column = new AnnotatedJoinColumn(); - column.setSqlType( columnDef ); - column.setLogicalColumnName( logicalColumnName ); - column.setReferencedColumn( referencedColumnName ); - column.setPropertyHolder( propertyHolder ); - column.setJoins( joins ); - column.setBuildingContext( context ); - column.setImplicit( false ); - column.setNullable( false ); - column.bind(); - return column; - } - else { - AnnotatedJoinColumn column = new AnnotatedJoinColumn(); - column.setLogicalColumnName( normalizer.normalizeIdentifierQuotingAsString( defaultColumnName ) ); - column.setPropertyHolder( propertyHolder ); - column.setJoins( joins ); - column.setBuildingContext( context ); - column.setImplicit( true ); - column.setNullable( false ); - column.bind(); - return column; - } + final String defaultColumnName = context.getMetadataCollector() + .getLogicalColumnName( identifier.getTable(), identifier.getColumns().get(0).getQuotedName() ); + return primaryKeyJoinColumn != null || joinColumn != null + ? explicitJoinColumn( primaryKeyJoinColumn, joinColumn, joins, propertyHolder, context, defaultColumnName ) + : implicitJoinColumn( joins, propertyHolder, context, defaultColumnName ); } - /** - * Override persistent class on oneToMany Cases for late settings - * Must only be used on second level pass binding - */ - public void setPersistentClass( - PersistentClass persistentClass, + private static AnnotatedJoinColumn explicitJoinColumn( + PrimaryKeyJoinColumn primaryKeyJoinColumn, + JoinColumn joinColumn, Map joins, - Map inheritanceStatePerClass) { - // TODO shouldn't we deduce the classname from the persistentClass? - this.propertyHolder = PropertyHolderBuilder.buildPropertyHolder( - persistentClass, - joins, - getBuildingContext(), - inheritanceStatePerClass - ); + PropertyHolder propertyHolder, + MetadataBuildingContext context, + String defaultColumnName) { + final String columnName; + final String columnDefinition; + final String referencedColumnName; + if ( primaryKeyJoinColumn != null ) { + columnName = primaryKeyJoinColumn.name(); + columnDefinition = primaryKeyJoinColumn.columnDefinition(); + referencedColumnName = primaryKeyJoinColumn.referencedColumnName(); + } + else { + columnName = joinColumn.name(); + columnDefinition = joinColumn.columnDefinition(); + referencedColumnName = joinColumn.referencedColumnName(); + } + final ObjectNameNormalizer normalizer = context.getObjectNameNormalizer(); + final String columnDef = columnDefinition.isEmpty() ? null + : normalizer.toDatabaseIdentifierText( columnDefinition ); + final String logicalColumnName = columnName != null && columnName.isEmpty() + ? normalizer.normalizeIdentifierQuotingAsString(defaultColumnName) + : normalizer.normalizeIdentifierQuotingAsString( columnName ); + final AnnotatedJoinColumn column = new AnnotatedJoinColumn(); + column.setSqlType( columnDef ); + column.setLogicalColumnName( logicalColumnName ); + column.setReferencedColumn( referencedColumnName ); + column.setPropertyHolder(propertyHolder); + column.setJoins(joins); + column.setBuildingContext(context); + column.setImplicit( false ); + column.setNullable( false ); + column.bind(); + return column; + } + + private static AnnotatedJoinColumn implicitJoinColumn( + Map joins, + PropertyHolder propertyHolder, + MetadataBuildingContext context, + String defaultColumnName ) { + final AnnotatedJoinColumn column = new AnnotatedJoinColumn(); + final ObjectNameNormalizer normalizer = context.getObjectNameNormalizer(); + column.setLogicalColumnName( normalizer.normalizeIdentifierQuotingAsString(defaultColumnName) ); + column.setPropertyHolder(propertyHolder); + column.setJoins(joins); + column.setBuildingContext(context); + column.setImplicit( true ); + column.setNullable( false ); + column.bind(); + return column; } public static void checkIfJoinColumn(Object columns, PropertyHolder holder, PropertyData property) { @@ -467,281 +326,32 @@ public void linkValueUsingDefaultColumnNaming( Column referencedColumn, PersistentClass referencedEntity, SimpleValue value) { - String logicalReferencedColumn = getBuildingContext().getMetadataCollector().getLogicalColumnName( - referencedEntity.getTable(), - referencedColumn.getQuotedName() - ); - String columnName = buildDefaultColumnName( referencedEntity, logicalReferencedColumn ); - + final String logicalReferencedColumn = getBuildingContext().getMetadataCollector() + .getLogicalColumnName( referencedEntity.getTable(), referencedColumn.getQuotedName() ); + final String columnName = parent.buildDefaultColumnName( referencedEntity, logicalReferencedColumn ); //yuk side effect on an implicit column setLogicalColumnName( columnName ); setReferencedColumn( logicalReferencedColumn ); + final Column mappingColumn = getMappingColumn(); initMappingColumn( columnName, null, referencedColumn.getLength(), referencedColumn.getPrecision(), referencedColumn.getScale(), - getMappingColumn() != null && getMappingColumn().isNullable(), + mappingColumn != null && mappingColumn.isNullable(), referencedColumn.getSqlType(), - getMappingColumn() != null && getMappingColumn().isUnique(), + mappingColumn != null && mappingColumn.isUnique(), false ); linkWithValue( value ); } public void addDefaultJoinColumnName(PersistentClass referencedEntity, String logicalReferencedColumn) { - final String columnName = buildDefaultColumnName( referencedEntity, logicalReferencedColumn ); + final String columnName = parent.buildDefaultColumnName( referencedEntity, logicalReferencedColumn ); getMappingColumn().setName( columnName ); setLogicalColumnName( columnName ); } - private String buildDefaultColumnName(final PersistentClass referencedEntity, final String logicalReferencedColumn) { - final InFlightMetadataCollector collector = getBuildingContext().getMetadataCollector(); - final Database database = collector.getDatabase(); - final MetadataBuildingOptions options = getBuildingContext().getBuildingOptions(); - final ImplicitNamingStrategy implicitNamingStrategy = options.getImplicitNamingStrategy(); - final PhysicalNamingStrategy physicalNamingStrategy = options.getPhysicalNamingStrategy(); - - boolean mappedBySide = mappedByTableName != null || mappedByPropertyName != null; - boolean ownerSide = getPropertyName() != null; - boolean isRefColumnQuoted = StringHelper.isQuoted( logicalReferencedColumn ); - - Identifier columnIdentifier; - if ( mappedBySide ) { - // NOTE : While it is completely misleading here to allow for the combination - // of a "JPA ElementCollection" to be mappedBy, the code that uses this - // class relies on this behavior for handling the inverse side of - // many-to-many mappings - columnIdentifier = implicitNamingStrategy.determineJoinColumnName( - new ImplicitJoinColumnNameSource() { - final AttributePath attributePath = AttributePath.parse( mappedByPropertyName ); - final ImplicitJoinColumnNameSource.Nature implicitNamingNature = getImplicitNature(); - - private final EntityNaming entityNaming = new EntityNaming() { - @Override - public String getClassName() { - return referencedEntity.getClassName(); - } - - @Override - public String getEntityName() { - return referencedEntity.getEntityName(); - } - - @Override - public String getJpaEntityName() { - return referencedEntity.getJpaEntityName(); - } - }; - - private final Identifier referencedTableName = database.toIdentifier( mappedByTableName ); - - @Override - public Nature getNature() { - return implicitNamingNature; - } - - @Override - public EntityNaming getEntityNaming() { - return entityNaming; - } - - @Override - public AttributePath getAttributePath() { - return attributePath; - } - - @Override - public Identifier getReferencedTableName() { - return referencedTableName; - } - - @Override - public Identifier getReferencedColumnName() { - if ( logicalReferencedColumn != null ) { - return database.toIdentifier( logicalReferencedColumn ); - } - - if ( mappedByEntityName == null || mappedByPropertyName == null ) { - return null; - } - - final Property mappedByProperty = collector.getEntityBinding( mappedByEntityName ) - .getProperty( mappedByPropertyName ); - final SimpleValue value = (SimpleValue) mappedByProperty.getValue(); - if ( value.getSelectables().isEmpty() ) { - throw new AnnotationException( - String.format( - Locale.ENGLISH, - "Association '%s' is 'mappedBy' a property '%s' of entity '%s' with no columns", - propertyHolder.getPath(), - mappedByPropertyName, - mappedByEntityName - ) - ); - } - final Selectable selectable = value.getSelectables().get(0); - if ( !(selectable instanceof Column) ) { - throw new AnnotationException( - String.format( - Locale.ENGLISH, - "Association '%s' is 'mappedBy' a property '%s' of entity '%s' which maps to a formula", - propertyHolder.getPath(), - mappedByPropertyName, - propertyHolder.getPath() - ) - ); - } - if ( value.getSelectables().size()>1 ) { - throw new AnnotationException( - String.format( - Locale.ENGLISH, - "Association '%s' is 'mappedBy' a property '%s' of entity '%s' with multiple columns", - propertyHolder.getPath(), - mappedByPropertyName, - propertyHolder.getPath() - ) - ); - } - return database.toIdentifier( ( (Column) selectable ).getQuotedName() ); - } - - @Override - public MetadataBuildingContext getBuildingContext() { - return AnnotatedJoinColumn.this.getBuildingContext(); - } - } - ); - - //one element was quoted so we quote - if ( isRefColumnQuoted || StringHelper.isQuoted( mappedByTableName ) ) { - columnIdentifier = Identifier.quote( columnIdentifier ); - } - } - else if ( ownerSide ) { - final String logicalTableName = collector.getLogicalTableName( referencedEntity.getTable() ); - - columnIdentifier = implicitNamingStrategy.determineJoinColumnName( - new ImplicitJoinColumnNameSource() { - final ImplicitJoinColumnNameSource.Nature implicitNamingNature = getImplicitNature(); - - private final EntityNaming entityNaming = new EntityNaming() { - @Override - public String getClassName() { - return referencedEntity.getClassName(); - } - - @Override - public String getEntityName() { - return referencedEntity.getEntityName(); - } - - @Override - public String getJpaEntityName() { - return referencedEntity.getJpaEntityName(); - } - }; - - private final AttributePath attributePath = AttributePath.parse( getPropertyName() ); - private final Identifier referencedTableName = database.toIdentifier( logicalTableName ); - private final Identifier referencedColumnName = database.toIdentifier( logicalReferencedColumn ); - - @Override - public Nature getNature() { - return implicitNamingNature; - } - - @Override - public EntityNaming getEntityNaming() { - return entityNaming; - } - - @Override - public AttributePath getAttributePath() { - return attributePath; - } - - @Override - public Identifier getReferencedTableName() { - return referencedTableName; - } - - @Override - public Identifier getReferencedColumnName() { - return referencedColumnName; - } - - @Override - public MetadataBuildingContext getBuildingContext() { - return AnnotatedJoinColumn.this.getBuildingContext(); - } - } - ); - - // HHH-11826 magic. See Ejb3Column and the HHH-6005 comments - if ( columnIdentifier.getText().contains( "_collection&&element_" ) ) { - columnIdentifier = Identifier.toIdentifier( - columnIdentifier.getText().replace( "_collection&&element_", "_" ), - columnIdentifier.isQuoted() - ); - } - - //one element was quoted so we quote - if ( isRefColumnQuoted || StringHelper.isQuoted( logicalTableName ) ) { - columnIdentifier = Identifier.quote( columnIdentifier ); - } - } - else { - final Identifier logicalTableName = database.toIdentifier( - collector.getLogicalTableName( referencedEntity.getTable() ) - ); - - // is an intra-entity hierarchy table join so copy the name by default - columnIdentifier = implicitNamingStrategy.determinePrimaryKeyJoinColumnName( - new ImplicitPrimaryKeyJoinColumnNameSource() { - @Override - public MetadataBuildingContext getBuildingContext() { - return AnnotatedJoinColumn.this.getBuildingContext(); - } - - @Override - public Identifier getReferencedTableName() { - return logicalTableName; - } - - @Override - public Identifier getReferencedPrimaryKeyColumnName() { - return database.toIdentifier( logicalReferencedColumn ); - } - } - ); - - if ( !columnIdentifier.isQuoted() && ( isRefColumnQuoted || logicalTableName.isQuoted() ) ) { - columnIdentifier = Identifier.quote( columnIdentifier ); - } - } - - return physicalNamingStrategy.toPhysicalColumnName( columnIdentifier, database.getJdbcEnvironment() ) - .render( database.getJdbcEnvironment().getDialect() ); - } - - /** - * @deprecated this is not a column-level setting, so it's better to - * do this work somewhere else - */ - @Deprecated - private ImplicitJoinColumnNameSource.Nature getImplicitNature() { - if ( getPropertyHolder().isEntity() ) { - return ImplicitJoinColumnNameSource.Nature.ENTITY; - } - else if ( JPA2ElementCollection ) { - return ImplicitJoinColumnNameSource.Nature.ELEMENT_COLLECTION; - } - else { - return ImplicitJoinColumnNameSource.Nature.ENTITY_COLLECTION; - } - } - /** * used for mappedBy cases */ @@ -749,7 +359,8 @@ public void linkValueUsingAColumnCopy(Column column, SimpleValue value) { initMappingColumn( //column.getName(), column.getQuotedName(), - null, column.getLength(), + null, + column.getLength(), column.getPrecision(), column.getScale(), getMappingColumn().isNullable(), @@ -762,15 +373,15 @@ public void linkValueUsingAColumnCopy(Column column, SimpleValue value) { @Override protected void addColumnBinding(SimpleValue value) { - if ( isEmpty( mappedBy ) ) { + if ( isEmpty( parent.getMappedBy() ) ) { // was the column explicitly quoted in the mapping/annotation // TODO: in metamodel, we need to better split global quoting and explicit quoting w/ respect to logical names - boolean isLogicalColumnQuoted = StringHelper.isQuoted( getLogicalColumnName() ); - final ObjectNameNormalizer nameNormalizer = getBuildingContext().getObjectNameNormalizer(); - final String logicalColumnName = nameNormalizer.normalizeIdentifierQuotingAsString( getLogicalColumnName() ); - final String referencedColumn = nameNormalizer.normalizeIdentifierQuotingAsString( getReferencedColumn() ); - final String unquotedLogColName = StringHelper.unquote( logicalColumnName ); - final String unquotedRefColumn = StringHelper.unquote( referencedColumn ); + boolean isLogicalColumnQuoted = isQuoted( getLogicalColumnName() ); + final ObjectNameNormalizer normalizer = getBuildingContext().getObjectNameNormalizer(); + final String logicalColumnName = normalizer.normalizeIdentifierQuotingAsString( getLogicalColumnName() ); + final String referencedColumn = normalizer.normalizeIdentifierQuotingAsString( getReferencedColumn() ); + final String unquotedLogColName = unquote( logicalColumnName ); + final String unquotedRefColumn = unquote( referencedColumn ); final String collectionColName = isNotEmpty( unquotedLogColName ) ? unquotedLogColName : getPropertyName() + '_' + unquotedRefColumn; @@ -793,18 +404,20 @@ protected void addColumnBinding(SimpleValue value) { public static final int NON_PK_REFERENCE = 2; public static int checkReferencedColumnsType( - AnnotatedJoinColumn[] joinColumns, + AnnotatedJoinColumns joinColumns, PersistentClass referencedEntity, MetadataBuildingContext context) { - if ( joinColumns.length == 0 ) { + final AnnotatedJoinColumn[] columns = joinColumns.getColumns(); + if ( columns.length == 0 ) { return NO_REFERENCE; //shortcut } - final Object columnOwner = findReferencedColumnOwner( referencedEntity, joinColumns[0], context ); + final AnnotatedJoinColumn firstColumn = columns[0]; + final Object columnOwner = findReferencedColumnOwner( referencedEntity, firstColumn, context ); if ( columnOwner == null ) { try { throw new MappingException( "A '@JoinColumn' references a column named '" - + joinColumns[0].getReferencedColumn() + "' but the target entity '" + + firstColumn.getReferencedColumn() + "' but the target entity '" + referencedEntity.getEntityName() + "' has no property which maps to this column" ); } catch (MappingException me) { @@ -817,7 +430,7 @@ public static int checkReferencedColumnsType( final Table table = getTable( columnOwner ); final List keyColumns = referencedEntity.getKey().getSelectables(); boolean explicitColumnReference = false; - for ( AnnotatedJoinColumn column : joinColumns ) { + for ( AnnotatedJoinColumn column : columns ) { if ( !column.isReferenceImplicit() ) { explicitColumnReference = true; if ( !keyColumns.contains( column( context, table, column.getReferencedColumn() ) ) ) { @@ -828,7 +441,7 @@ public static int checkReferencedColumnsType( } if ( explicitColumnReference ) { // if we got to here, all the columns belong to the PK - return keyColumns.size() == joinColumns.length + return keyColumns.size() == columns.length // we have all the PK columns ? PK_REFERENCE // we have a subset of the PK columns @@ -862,7 +475,7 @@ private static Column column(MetadataBuildingContext context, Table table, Strin * @param column the referenced column. */ public void overrideFromReferencedColumnIfNecessary(Column column) { - Column mappingColumn = getMappingColumn(); + final Column mappingColumn = getMappingColumn(); if ( mappingColumn != null ) { // columnDefinition can also be specified using @JoinColumn, hence we have to check // whether it is set or not @@ -883,50 +496,44 @@ public void redefineColumnName(String columnName, String propertyName, boolean a super.redefineColumnName( columnName, null, applyNamingStrategy ); } - public static AnnotatedJoinColumn[] buildJoinTableJoinColumns( - JoinColumn[] joinColumns, + static AnnotatedJoinColumn buildImplicitJoinTableJoinColumn( Map secondaryTables, PropertyHolder propertyHolder, String propertyName, - String mappedBy, - MetadataBuildingContext buildingContext) { - if ( joinColumns == null ) { - final AnnotatedJoinColumn column = new AnnotatedJoinColumn(); - column.setImplicit( true ); - column.setNullable( false ); //I break the spec, but it's for good - column.setPropertyHolder( propertyHolder ); - column.setPropertyName( getRelativePath( propertyHolder, propertyName ) ); - column.setJoins( secondaryTables ); - column.setBuildingContext( buildingContext ); - column.setMappedBy( mappedBy ); - column.bind(); - return new AnnotatedJoinColumn[] { column }; - } - else { - final AnnotatedJoinColumn[] columns = new AnnotatedJoinColumn[joinColumns.length]; - int length = joinColumns.length; - for (int index = 0; index < length; index++) { - final JoinColumn joinColumn = joinColumns[index]; - final AnnotatedJoinColumn column = new AnnotatedJoinColumn(); - column.setImplicit( true ); - column.setPropertyHolder( propertyHolder ); - column.setPropertyName( getRelativePath( propertyHolder, propertyName ) ); - column.setJoins( secondaryTables ); - column.setBuildingContext( buildingContext ); - column.setMappedBy( mappedBy ); - column.setJoinAnnotation( joinColumn, propertyName ); - column.setNullable( false ); //I break the spec, but it's for good - //done after the annotation to override it - column.bind(); - columns[index] = column; - } - return columns; - } + MetadataBuildingContext context) { + final AnnotatedJoinColumn column = new AnnotatedJoinColumn(); + column.setImplicit( true ); + column.setNullable( false ); //I break the spec, but it's for good + column.setPropertyHolder( propertyHolder ); + column.setPropertyName( getRelativePath( propertyHolder, propertyName ) ); + column.setJoins( secondaryTables ); + column.setBuildingContext( context ); + column.bind(); + return column; + } + + static AnnotatedJoinColumn buildExplicitJoinTableJoinColumn( + Map secondaryTables, + PropertyHolder propertyHolder, + String propertyName, + MetadataBuildingContext context, + JoinColumn joinColumn) { + final AnnotatedJoinColumn column = new AnnotatedJoinColumn(); + column.setImplicit( true ); + column.setPropertyHolder(propertyHolder); + column.setPropertyName( getRelativePath( propertyHolder, propertyName ) ); + column.setJoins( secondaryTables ); + column.setBuildingContext( context ); + column.setJoinAnnotation( joinColumn, propertyName ); + column.setNullable( false ); //I break the spec, but it's for good + //done after the annotation to override it + column.bind(); + return column; } @Override public String toString() { - StringBuilder string = new StringBuilder(); + final StringBuilder string = new StringBuilder(); string.append( getClass().getSimpleName() ).append( "(" ); if ( isNotEmpty( getLogicalColumnName() ) ) { string.append( "column='" ).append( getLogicalColumnName() ).append( "'," ); @@ -940,4 +547,8 @@ public String toString() { string.append( ")" ); return string.toString(); } + + public void setParent(AnnotatedJoinColumns parent) { + this.parent = parent; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotatedJoinColumns.java b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotatedJoinColumns.java new file mode 100644 index 0000000000..8f50bf054a --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotatedJoinColumns.java @@ -0,0 +1,581 @@ +package org.hibernate.cfg; + +import jakarta.persistence.JoinColumn; +import org.hibernate.AnnotationException; +import org.hibernate.annotations.Comment; +import org.hibernate.annotations.JoinColumnOrFormula; +import org.hibernate.annotations.JoinFormula; +import org.hibernate.annotations.common.reflection.XClass; +import org.hibernate.boot.model.naming.EntityNaming; +import org.hibernate.boot.model.naming.Identifier; +import org.hibernate.boot.model.naming.ImplicitJoinColumnNameSource; +import org.hibernate.boot.model.naming.ImplicitNamingStrategy; +import org.hibernate.boot.model.naming.ImplicitPrimaryKeyJoinColumnNameSource; +import org.hibernate.boot.model.naming.PhysicalNamingStrategy; +import org.hibernate.boot.model.relational.Database; +import org.hibernate.boot.model.source.spi.AttributePath; +import org.hibernate.boot.spi.InFlightMetadataCollector; +import org.hibernate.boot.spi.MetadataBuildingContext; +import org.hibernate.boot.spi.MetadataBuildingOptions; +import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; +import org.hibernate.mapping.Column; +import org.hibernate.mapping.Join; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.Property; +import org.hibernate.mapping.Selectable; +import org.hibernate.mapping.SimpleValue; + +import java.util.Locale; +import java.util.Map; + +import static org.hibernate.cfg.BinderHelper.getRelativePath; +import static org.hibernate.cfg.BinderHelper.isEmptyOrNullAnnotationValue; +import static org.hibernate.cfg.PropertyHolderBuilder.buildPropertyHolder; +import static org.hibernate.internal.util.StringHelper.isQuoted; +import static org.hibernate.internal.util.StringHelper.qualify; + +/** + * A list of {@link jakarta.persistence.JoinColumn}s that form a single join + * condition, similar in concept to {@link jakarta.persistence.JoinColumns}, + * but not every instance of this class corresponds to an explicit annotation + * in the Java code. + *

+ * There's no exact analog of this class in the mapping model, so some + * information is lost when it's transformed into a list of {@link Column}s. + * + * @author Gavin King + */ +public class AnnotatedJoinColumns { + + private AnnotatedJoinColumn[] columns; + private PropertyHolder propertyHolder; + private String propertyName; // this is really a .-separated property path + private MetadataBuildingContext buildingContext; + + //TODO: do we really need to hang so many strings off this class? + private String mappedBy; + private String mappedByPropertyName; //property name on the owning side if any + private String mappedByTableName; //table name on the mapped by side if any + private String mappedByEntityName; + private boolean elementCollection; + private String manyToManyOwnerSideEntityName; + + public AnnotatedJoinColumns() {} + + public static AnnotatedJoinColumns buildJoinColumnsOrFormulas( + JoinColumnOrFormula[] joinColumnOrFormulas, + String mappedBy, + Map joins, + PropertyHolder propertyHolder, + String propertyName, + MetadataBuildingContext context) { + final AnnotatedJoinColumn[] columns = new AnnotatedJoinColumn[joinColumnOrFormulas.length]; + for ( int i = 0; i < joinColumnOrFormulas.length; i++ ) { + final JoinColumnOrFormula columnOrFormula = joinColumnOrFormulas[i]; + final JoinFormula formula = columnOrFormula.formula(); + final JoinColumn column = columnOrFormula.column(); + columns[i] = formula.value() != null && !formula.value().isEmpty() + ? AnnotatedJoinColumn.buildJoinFormula( formula, joins, propertyHolder, propertyName, context ) + : AnnotatedJoinColumn.buildJoinColumn( column, mappedBy, joins, propertyHolder, propertyName, context ); + } + final AnnotatedJoinColumns joinColumns = new AnnotatedJoinColumns(); + joinColumns.setBuildingContext( context ); + joinColumns.setPropertyHolder( propertyHolder ); + joinColumns.setPropertyName( getRelativePath( propertyHolder, propertyName ) ); + joinColumns.setColumns( columns ); + joinColumns.setMappedBy( mappedBy ); + return joinColumns; + } + + public static AnnotatedJoinColumns buildJoinColumns( + JoinColumn[] joinColumns, + Comment comment, + String mappedBy, + Map joins, + PropertyHolder propertyHolder, + String propertyName, + MetadataBuildingContext buildingContext) { + return buildJoinColumnsWithDefaultColumnSuffix( + joinColumns, + comment, + mappedBy, + joins, + propertyHolder, + propertyName, + "", + buildingContext + ); + } + + public static AnnotatedJoinColumns buildJoinColumnsWithDefaultColumnSuffix( + JoinColumn[] joinColumns, + Comment comment, + String mappedBy, + Map joins, + PropertyHolder propertyHolder, + String propertyName, + String defaultColumnSuffix, + MetadataBuildingContext context) { + final String path = qualify( propertyHolder.getPath(), propertyName ); + final JoinColumn[] overriddes = propertyHolder.getOverriddenJoinColumn( path ); + final JoinColumn[] actualColumns = overriddes == null ? joinColumns : overriddes; + if ( actualColumns == null || actualColumns.length == 0 ) { + final AnnotatedJoinColumn joinColumn = AnnotatedJoinColumn.buildJoinColumn( + null, + comment, + mappedBy, + joins, + propertyHolder, + propertyName, + defaultColumnSuffix, + context + ); + final AnnotatedJoinColumns annotatedJoinColumns = new AnnotatedJoinColumns(); + annotatedJoinColumns.setBuildingContext( context ); + annotatedJoinColumns.setPropertyHolder( propertyHolder ); + annotatedJoinColumns.setPropertyName( getRelativePath( propertyHolder, propertyName ) ); + annotatedJoinColumns.setColumns( new AnnotatedJoinColumn[] { joinColumn } ); + annotatedJoinColumns.setMappedBy( mappedBy ); + return annotatedJoinColumns; + } + else { + final AnnotatedJoinColumn[] result = new AnnotatedJoinColumn[actualColumns.length]; + for ( int index = 0; index < actualColumns.length; index++ ) { + result[index] = AnnotatedJoinColumn.buildJoinColumn( + actualColumns[index], + comment, + mappedBy, + joins, + propertyHolder, + propertyName, + defaultColumnSuffix, + context + ); + } + final AnnotatedJoinColumns annotatedJoinColumns = new AnnotatedJoinColumns(); + annotatedJoinColumns.setBuildingContext( context ); + annotatedJoinColumns.setPropertyHolder( propertyHolder ); + annotatedJoinColumns.setPropertyName( getRelativePath( propertyHolder, propertyName ) ); + annotatedJoinColumns.setColumns( result ); + annotatedJoinColumns.setMappedBy( mappedBy ); + return annotatedJoinColumns; + } + } + + public static AnnotatedJoinColumns buildJoinTableJoinColumns( + JoinColumn[] joinColumns, + Map secondaryTables, + PropertyHolder propertyHolder, + String propertyName, + String mappedBy, + MetadataBuildingContext context) { + final AnnotatedJoinColumn[] columns; + if ( joinColumns == null ) { + columns = new AnnotatedJoinColumn[] { AnnotatedJoinColumn.buildImplicitJoinTableJoinColumn( + secondaryTables, + propertyHolder, + propertyName, + context + ) }; + } + else { + columns = new AnnotatedJoinColumn[joinColumns.length]; + int length = joinColumns.length; + for (int index = 0; index < length; index++) { + columns[index] = AnnotatedJoinColumn.buildExplicitJoinTableJoinColumn( + secondaryTables, + propertyHolder, + propertyName, + context, + joinColumns[index] + ); + } + } + final AnnotatedJoinColumns annotatedJoinColumns = new AnnotatedJoinColumns(); + annotatedJoinColumns.setBuildingContext( context ); + annotatedJoinColumns.setPropertyHolder( propertyHolder ); + annotatedJoinColumns.setPropertyName( getRelativePath( propertyHolder, propertyName ) ); + annotatedJoinColumns.setColumns( columns ); + annotatedJoinColumns.setMappedBy( mappedBy ); + return annotatedJoinColumns; + } + + public AnnotatedJoinColumn[] getColumns() { + return columns; + } + + public void setColumns(AnnotatedJoinColumn[] columns) { + this.columns = columns; + if ( columns != null ) { + for ( AnnotatedJoinColumn column : columns ) { + column.setParent( this ); + } + } + } + + public String getMappedBy() { + return mappedBy; + } + + public void setMappedBy(String mappedBy) { + this.mappedBy = mappedBy; + } + + /** + * @return true if the association mapping annotation did specify + * {@link jakarta.persistence.OneToMany#mappedBy() mappedBy}, + * meaning that this {@code @JoinColumn} mapping belongs to an + * unowned many-valued association. + */ + public boolean hasMappedBy() { + return !isEmptyOrNullAnnotationValue( getMappedBy() ); + } + + public String getMappedByEntityName() { + return mappedByEntityName; + } + + public String getMappedByPropertyName() { + return mappedByPropertyName; + } + + public String getMappedByTableName() { + return mappedByTableName; + } + + public PropertyHolder getPropertyHolder() { + return propertyHolder; + } + + public void setPropertyHolder(PropertyHolder propertyHolder) { + this.propertyHolder = propertyHolder; + } + + /** + * Override persistent class on oneToMany Cases for late settings + * Must only be used on second level pass binding + */ + public void setPersistentClass( + PersistentClass persistentClass, + Map joins, + Map inheritanceStatePerClass) { + // TODO shouldn't we deduce the class name from the persistentClass? + propertyHolder = buildPropertyHolder( + persistentClass, + joins, + buildingContext, + inheritanceStatePerClass + ); + for ( AnnotatedJoinColumn column : columns ) { + column.setPropertyHolder( propertyHolder ); + } + } + + public void setBuildingContext(MetadataBuildingContext buildingContext) { + this.buildingContext = buildingContext; + } + + public boolean isElementCollection() { + return elementCollection; + } + + public void setElementCollection(boolean elementCollection) { + this.elementCollection = elementCollection; + } + + public void setManyToManyOwnerSideEntityName(String entityName) { + manyToManyOwnerSideEntityName = entityName; + } + + public String getManyToManyOwnerSideEntityName() { + return manyToManyOwnerSideEntityName; + } + + public void setMappedBy(String entityName, String logicalTableName, String mappedByProperty) { + mappedByEntityName = entityName; + mappedByTableName = logicalTableName; + mappedByPropertyName = mappedByProperty; + } + + String buildDefaultColumnName(PersistentClass referencedEntity, String logicalReferencedColumn) { + final MetadataBuildingOptions options = buildingContext.getBuildingOptions(); + final ImplicitNamingStrategy implicitNamingStrategy = options.getImplicitNamingStrategy(); + final PhysicalNamingStrategy physicalNamingStrategy = options.getPhysicalNamingStrategy(); + + boolean mappedBySide = getMappedByTableName() != null || getMappedByPropertyName() != null; + boolean ownerSide = getPropertyName() != null; + boolean isRefColumnQuoted = isQuoted( logicalReferencedColumn ); + + final InFlightMetadataCollector collector = buildingContext.getMetadataCollector(); + final Database database = collector.getDatabase(); + + Identifier columnIdentifier; + if ( mappedBySide ) { + // NOTE : While it is completely misleading here to allow for the combination + // of a "JPA ElementCollection" to be mappedBy, the code that uses this + // class relies on this behavior for handling the inverse side of + // many-to-many mappings + columnIdentifier = implicitNamingStrategy.determineJoinColumnName( + new UnownedImplicitJoinColumnNameSource( referencedEntity, logicalReferencedColumn ) + ); + + //one element was quoted so we quote + if ( isRefColumnQuoted || isQuoted( getMappedByTableName() ) ) { + columnIdentifier = Identifier.quote( columnIdentifier ); + } + } + else if ( ownerSide ) { + final String logicalTableName = collector.getLogicalTableName( referencedEntity.getTable() ); + + columnIdentifier = implicitNamingStrategy.determineJoinColumnName( + new OwnedImplicitJoinColumnNameSource( referencedEntity, logicalTableName, logicalReferencedColumn ) + ); + + // HHH-11826 magic. See Ejb3Column and the HHH-6005 comments + if ( columnIdentifier.getText().contains( "_collection&&element_" ) ) { + columnIdentifier = Identifier.toIdentifier( + columnIdentifier.getText().replace( "_collection&&element_", "_" ), + columnIdentifier.isQuoted() + ); + } + + //one element was quoted so we quote + if ( isRefColumnQuoted || isQuoted( logicalTableName ) ) { + columnIdentifier = Identifier.quote( columnIdentifier ); + } + } + else { + final Identifier logicalTableName = database.toIdentifier( + collector.getLogicalTableName( referencedEntity.getTable() ) + ); + + // is an intra-entity hierarchy table join so copy the name by default + columnIdentifier = implicitNamingStrategy.determinePrimaryKeyJoinColumnName( + new ImplicitPrimaryKeyJoinColumnNameSource() { + @Override + public MetadataBuildingContext getBuildingContext() { + return buildingContext; + } + + @Override + public Identifier getReferencedTableName() { + return logicalTableName; + } + + @Override + public Identifier getReferencedPrimaryKeyColumnName() { + return database.toIdentifier( logicalReferencedColumn ); + } + } + ); + + if ( !columnIdentifier.isQuoted() && ( isRefColumnQuoted || logicalTableName.isQuoted() ) ) { + columnIdentifier = Identifier.quote( columnIdentifier ); + } + } + + final JdbcEnvironment jdbcEnvironment = database.getJdbcEnvironment(); + return physicalNamingStrategy.toPhysicalColumnName( columnIdentifier, jdbcEnvironment ) + .render( jdbcEnvironment.getDialect() ); + } + + /** + * A property path relative to the {@link #getPropertyHolder() PropertyHolder}. + */ + public String getPropertyName() { + return propertyName; + } + + public void setPropertyName(String propertyName) { + this.propertyName = propertyName; + } + + private ImplicitJoinColumnNameSource.Nature getImplicitNature() { + if ( getPropertyHolder().isEntity() ) { + return ImplicitJoinColumnNameSource.Nature.ENTITY; + } + else if ( isElementCollection() ) { + return ImplicitJoinColumnNameSource.Nature.ELEMENT_COLLECTION; + } + else { + return ImplicitJoinColumnNameSource.Nature.ENTITY_COLLECTION; + } + } + + private class UnownedImplicitJoinColumnNameSource implements ImplicitJoinColumnNameSource { + final AttributePath attributePath; + final Nature implicitNamingNature; + + private final EntityNaming entityNaming; + + private final Identifier referencedTableName; + private final String logicalReferencedColumn; + + final InFlightMetadataCollector collector = buildingContext.getMetadataCollector(); + final Database database = collector.getDatabase(); + + public UnownedImplicitJoinColumnNameSource(PersistentClass referencedEntity, String logicalReferencedColumn) { + this.logicalReferencedColumn = logicalReferencedColumn; + attributePath = AttributePath.parse( getMappedByPropertyName() ); + implicitNamingNature = getImplicitNature(); + entityNaming = new EntityNaming() { + @Override + public String getClassName() { + return referencedEntity.getClassName(); + } + + @Override + public String getEntityName() { + return referencedEntity.getEntityName(); + } + + @Override + public String getJpaEntityName() { + return referencedEntity.getJpaEntityName(); + } + }; + referencedTableName = database.toIdentifier( getMappedByTableName() ); + } + + @Override + public Nature getNature() { + return implicitNamingNature; + } + + @Override + public EntityNaming getEntityNaming() { + return entityNaming; + } + + @Override + public AttributePath getAttributePath() { + return attributePath; + } + + @Override + public Identifier getReferencedTableName() { + return referencedTableName; + } + + @Override + public Identifier getReferencedColumnName() { + if ( logicalReferencedColumn != null ) { + return database.toIdentifier(logicalReferencedColumn); + } + + if ( getMappedByEntityName() == null || getMappedByPropertyName() == null ) { + return null; + } + + final Property mappedByProperty = collector.getEntityBinding( getMappedByEntityName() ) + .getProperty( getMappedByPropertyName() ); + final SimpleValue value = (SimpleValue) mappedByProperty.getValue(); + if ( value.getSelectables().isEmpty() ) { + throw new AnnotationException( + String.format( + Locale.ENGLISH, + "Association '%s' is 'mappedBy' a property '%s' of entity '%s' with no columns", + propertyHolder.getPath(), + getMappedByPropertyName(), + getMappedByEntityName() + ) + ); + } + final Selectable selectable = value.getSelectables().get(0); + if ( !(selectable instanceof Column) ) { + throw new AnnotationException( + String.format( + Locale.ENGLISH, + "Association '%s' is 'mappedBy' a property '%s' of entity '%s' which maps to a formula", + propertyHolder.getPath(), + getMappedByPropertyName(), + propertyHolder.getPath() + ) + ); + } + if ( value.getSelectables().size()>1 ) { + throw new AnnotationException( + String.format( + Locale.ENGLISH, + "Association '%s' is 'mappedBy' a property '%s' of entity '%s' with multiple columns", + propertyHolder.getPath(), + getMappedByPropertyName(), + propertyHolder.getPath() + ) + ); + } + return database.toIdentifier( ( (Column) selectable ).getQuotedName() ); + } + + @Override + public MetadataBuildingContext getBuildingContext() { + return buildingContext; + } + } + + private class OwnedImplicitJoinColumnNameSource implements ImplicitJoinColumnNameSource { + final Nature implicitNamingNature; + + private final EntityNaming entityNaming; + + private final AttributePath attributePath; + private final Identifier referencedTableName; + private final Identifier referencedColumnName; + + final InFlightMetadataCollector collector = buildingContext.getMetadataCollector(); + final Database database = collector.getDatabase(); + + public OwnedImplicitJoinColumnNameSource(PersistentClass referencedEntity, String logicalTableName, String logicalReferencedColumn) { + implicitNamingNature = getImplicitNature(); + entityNaming = new EntityNaming() { + @Override + public String getClassName() { + return referencedEntity.getClassName(); + } + + @Override + public String getEntityName() { + return referencedEntity.getEntityName(); + } + + @Override + public String getJpaEntityName() { + return referencedEntity.getJpaEntityName(); + } + }; + attributePath = AttributePath.parse( getPropertyName() ); + referencedTableName = database.toIdentifier( logicalTableName ); + referencedColumnName = database.toIdentifier( logicalReferencedColumn ); + } + + @Override + public Nature getNature() { + return implicitNamingNature; + } + + @Override + public EntityNaming getEntityNaming() { + return entityNaming; + } + + @Override + public AttributePath getAttributePath() { + return attributePath; + } + + @Override + public Identifier getReferencedTableName() { + return referencedTableName; + } + + @Override + public Identifier getReferencedColumnName() { + return referencedColumnName; + } + + @Override + public MetadataBuildingContext getBuildingContext() { + return buildingContext; + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java index a5701e26bd..d47b898210 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java @@ -152,6 +152,7 @@ import static org.hibernate.cfg.BinderHelper.getOverridableAnnotation; import static org.hibernate.cfg.BinderHelper.getPath; import static org.hibernate.cfg.BinderHelper.getPropertyOverriddenByMapperOrMapsId; +import static org.hibernate.cfg.BinderHelper.getRelativePath; import static org.hibernate.cfg.BinderHelper.hasToOneAnnotation; import static org.hibernate.cfg.BinderHelper.makeIdGenerator; import static org.hibernate.cfg.InheritanceState.getInheritanceStateOfSuperEntity; @@ -1118,7 +1119,7 @@ private static void buildProperty( context ).extractMetadata(); AnnotatedColumn[] columns = columnsBuilder.getColumns(); - AnnotatedJoinColumn[] joinColumns = columnsBuilder.getJoinColumns(); + AnnotatedJoinColumns joinColumns = columnsBuilder.getJoinColumns(); final PropertyBinder propertyBinder = new PropertyBinder(); propertyBinder.setName( inferredData.getPropertyName() ); @@ -1505,7 +1506,7 @@ private static PropertyBinder createCompositeBinder( Class> compositeUserType) { final String referencedEntityName; final String propertyName; - final AnnotatedJoinColumn[] actualColumns; + final AnnotatedJoinColumns actualColumns; if ( isOverridden ) { // careful: not always a @MapsId property, sometimes it's from an @IdClass PropertyData mapsIdProperty = getPropertyOverriddenByMapperOrMapsId( @@ -1513,7 +1514,12 @@ private static PropertyBinder createCompositeBinder( ); referencedEntityName = mapsIdProperty.getClassOrElementName(); propertyName = mapsIdProperty.getPropertyName(); - actualColumns = (AnnotatedJoinColumn[]) columns; + final AnnotatedJoinColumns joinColumns = new AnnotatedJoinColumns(); + joinColumns.setBuildingContext( context ); + joinColumns.setPropertyHolder( propertyHolder ); + joinColumns.setPropertyName( getRelativePath( propertyHolder, propertyName ) ); + joinColumns.setColumns( (AnnotatedJoinColumn[]) columns ); + actualColumns = joinColumns; } else { referencedEntityName = null; @@ -1553,7 +1559,7 @@ private static void bindAny( boolean isIdentifierMapper, MetadataBuildingContext context, XProperty property, - AnnotatedJoinColumn[] joinColumns, + AnnotatedJoinColumns joinColumns, boolean forcePersist) { //check validity @@ -1573,12 +1579,12 @@ private static void bindAny( JoinTable assocTable = propertyHolder.getJoinTable(property); if ( assocTable != null ) { Join join = propertyHolder.addJoin( assocTable, false ); - for ( AnnotatedJoinColumn joinColumn : joinColumns) { + for ( AnnotatedJoinColumn joinColumn : joinColumns.getColumns() ) { joinColumn.setExplicitTableName( join.getTable().getName() ); } } bindAny( - BinderHelper.getCascadeStrategy( null, hibernateCascade, false, forcePersist), + BinderHelper.getCascadeStrategy( null, hibernateCascade, false, forcePersist ), //@Any has not cascade attribute joinColumns, onDeleteAnn != null && OnDeleteAction.CASCADE == onDeleteAnn.action(), @@ -1595,20 +1601,20 @@ private static void addIndexes( boolean inSecondPass, XProperty property, AnnotatedColumn[] columns, - AnnotatedJoinColumn[] joinColumns) { + AnnotatedJoinColumns joinColumns) { //process indexes after everything: in second pass, many to one has to be done before indexes - Index index = property.getAnnotation( Index.class ); + final Index index = property.getAnnotation( Index.class ); if ( index != null ) { if ( joinColumns != null ) { - for ( AnnotatedColumn column : joinColumns) { + for ( AnnotatedColumn column : joinColumns.getColumns() ) { column.addIndex( index, inSecondPass); } } else { if ( columns != null ) { - for ( AnnotatedColumn column : columns) { - column.addIndex( index, inSecondPass); + for ( AnnotatedColumn column : columns ) { + column.addIndex( index, inSecondPass ); } } } @@ -1619,23 +1625,23 @@ private static void addNaturalIds( boolean inSecondPass, XProperty property, AnnotatedColumn[] columns, - AnnotatedJoinColumn[] joinColumns) { + AnnotatedJoinColumns joinColumns) { // Natural ID columns must reside in one single UniqueKey within the Table. // For now, simply ensure consistent naming. // TODO: AFAIK, there really isn't a reason for these UKs to be created - // on the secondPass. This whole area should go away... - NaturalId naturalIdAnn = property.getAnnotation( NaturalId.class ); + // on the SecondPass. This whole area should go away... + final NaturalId naturalIdAnn = property.getAnnotation( NaturalId.class ); if ( naturalIdAnn != null ) { if ( joinColumns != null ) { - for ( AnnotatedColumn column : joinColumns) { + for ( AnnotatedColumn column : joinColumns.getColumns() ) { String keyName = "UK_" + Constraint.hashedName( column.getTable().getName() + "_NaturalID" ); - column.addUniqueKey( keyName, inSecondPass); + column.addUniqueKey( keyName, inSecondPass ); } } else { for ( AnnotatedColumn column : columns) { String keyName = "UK_" + Constraint.hashedName( column.getTable().getName() + "_NaturalID" ); - column.addUniqueKey( keyName, inSecondPass); + column.addUniqueKey( keyName, inSecondPass ); } } } @@ -1854,7 +1860,7 @@ private static PropertyBinder bindComponent( String propertyName, Class customInstantiatorImpl, Class> compositeUserTypeClass, - AnnotatedJoinColumn[] columns) { + AnnotatedJoinColumns columns) { final Component component; if ( referencedEntityName != null ) { component = createComponent( @@ -2201,7 +2207,7 @@ public static PropertyData getUniqueIdPropertyFromBaseClass( private static void bindAny( String cascadeStrategy, - AnnotatedJoinColumn[] columns, + AnnotatedJoinColumns columns, boolean cascadeOnDelete, Nullability nullability, PropertyHolder propertyHolder, @@ -2241,14 +2247,15 @@ private static void bindAny( binder.setUpdatable( false ); } else { - binder.setInsertable( columns[0].isInsertable() ); - binder.setUpdatable( columns[0].isUpdatable() ); + final AnnotatedJoinColumn firstColumn = columns.getColumns()[0]; + binder.setInsertable( firstColumn.isInsertable() ); + binder.setUpdatable( firstColumn.isUpdatable() ); } binder.setAccessType( inferredData.getDefaultAccess() ); binder.setCascade( cascadeStrategy ); Property prop = binder.makeProperty(); //composite FK columns are in the same table so its OK - propertyHolder.addProperty( prop, columns, inferredData.getDeclaringClass() ); + propertyHolder.addProperty( prop, columns.getColumns(), inferredData.getDeclaringClass() ); } public static HashMap buildGenerators( diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/BinderHelper.java b/hibernate-core/src/main/java/org/hibernate/cfg/BinderHelper.java index 18bb1480d7..856b0d8384 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/BinderHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/BinderHelper.java @@ -52,7 +52,6 @@ import org.hibernate.id.IdentifierGenerator; import org.hibernate.id.PersistentIdentifierGenerator; import org.hibernate.internal.CoreLogging; -import org.hibernate.internal.util.StringHelper; import org.hibernate.mapping.Any; import org.hibernate.mapping.BasicValue; import org.hibernate.mapping.Collection; @@ -84,6 +83,7 @@ import static org.hibernate.cfg.AnnotatedJoinColumn.NON_PK_REFERENCE; import static org.hibernate.cfg.AnnotatedJoinColumn.checkReferencedColumnsType; +import static org.hibernate.internal.util.StringHelper.qualify; import static org.hibernate.property.access.spi.BuiltInPropertyAccessStrategies.EMBEDDED; import static org.hibernate.property.access.spi.BuiltInPropertyAccessStrategies.NOOP; import static org.hibernate.property.access.spi.BuiltInPropertyAccessStrategies.interpret; @@ -162,7 +162,7 @@ public static Property shallowCopy(Property property) { * the synthetic {@link Component}. */ public static void createSyntheticPropertyReference( - AnnotatedJoinColumn[] columns, + AnnotatedJoinColumns joinColumns, // the target entity of the association, to which the columns belong PersistentClass targetEntity, // the entity which declares the association (used for exception message) @@ -172,15 +172,16 @@ public static void createSyntheticPropertyReference( // true when we do the reverse side of a @ManyToMany boolean inverse, MetadataBuildingContext context) { + final AnnotatedJoinColumn[] columns = joinColumns.getColumns(); // this work is not necessary for a primary key reference - if ( checkReferencedColumnsType( columns, targetEntity, context ) == NON_PK_REFERENCE ) { // && !firstColumn.isImplicit() + if ( checkReferencedColumnsType( joinColumns, targetEntity, context ) == NON_PK_REFERENCE ) { // && !firstColumn.isImplicit() // all the columns have to belong to the same table; // figure out which table has the columns by looking // for a PersistentClass or Join in the hierarchy of // the target entity which has the first column final Object columnOwner = findReferencedColumnOwner( targetEntity, columns[0], context ); - checkColumnInSameTable( columns, targetEntity, associatedEntity, context, columnOwner ); + checkColumnInSameTable( joinColumns, targetEntity, associatedEntity, context, columnOwner ); // find all properties mapped to each column final List properties = findPropertiesByColumns( columnOwner, columns, associatedEntity, context ); // create a Property along with the new synthetic @@ -214,28 +215,28 @@ public static void createSyntheticPropertyReference( * must return the same value for all of them. */ private static void checkColumnInSameTable( - AnnotatedJoinColumn[] columns, + AnnotatedJoinColumns joinColumns, PersistentClass targetEntity, PersistentClass associatedEntity, MetadataBuildingContext context, Object columnOwner) { - final AnnotatedJoinColumn firstColumn = columns[0]; - for ( AnnotatedJoinColumn column: columns) { - if ( column.hasMappedBy() ) { - // we should only get called for owning side of association - throw new AssertionFailure("no need to create synthetic properties for unowned collections"); - } + if ( joinColumns.hasMappedBy() ) { + // we should only get called for owning side of association + throw new AssertionFailure("no need to create synthetic properties for unowned collections"); + } + for ( AnnotatedJoinColumn column: joinColumns.getColumns() ) { final Object owner = findReferencedColumnOwner( targetEntity, column, context ); if ( owner == null ) { throw new AnnotationException( "A '@JoinColumn' for association " - + associationMessage( associatedEntity, firstColumn ) + + associationMessage( associatedEntity, column ) + " references a column named '" + column.getReferencedColumn() + "' which is not mapped by the target entity '" + targetEntity.getEntityName() + "'" ); } if ( owner != columnOwner) { + final AnnotatedJoinColumn firstColumn = joinColumns.getColumns()[0]; throw new AnnotationException( "The '@JoinColumn's for association " - + associationMessage( associatedEntity, firstColumn ) + + associationMessage( associatedEntity, column ) + " reference columns of different tables mapped by the target entity '" + targetEntity.getEntityName() + "' ('" + column.getReferencedColumn() + "' belongs to a different table to '" + firstColumn.getReferencedColumn() + "'" ); @@ -483,7 +484,7 @@ else if ( columnOwner instanceof Join ) { // each column, but this means we will reject some mappings // that could be made to work for a different choice of // properties (it's also not very deterministic) - List orderedProperties = new ArrayList<>(); + final List orderedProperties = new ArrayList<>(); int lastPropertyColumnIndex = 0; Property currentProperty = null; for ( Column column : orderedColumns ) { @@ -694,13 +695,12 @@ public static String getRelativePath(PropertyHolder propertyHolder, String prope if ( propertyHolder == null ) { return propertyName; } - final String path = propertyHolder.getPath(); - final String entityName = propertyHolder.getPersistentClass().getEntityName(); - if ( path.length() == entityName.length() ) { - return propertyName; - } else { - return StringHelper.qualify( path.substring( entityName.length() + 1 ), propertyName ); + final String path = propertyHolder.getPath(); + final String entityName = propertyHolder.getPersistentClass().getEntityName(); + return path.length() == entityName.length() + ? propertyName + : qualify( path.substring(entityName.length() + 1), propertyName ); } } @@ -708,12 +708,9 @@ public static Object findReferencedColumnOwner( PersistentClass persistentClass, AnnotatedJoinColumn joinColumn, MetadataBuildingContext context) { - if ( joinColumn.isImplicit() || joinColumn.isReferenceImplicit() ) { - return persistentClass; - } - else { - return findColumnOwner( persistentClass, joinColumn.getReferencedColumn(), context ); - } + return joinColumn.isImplicit() || joinColumn.isReferenceImplicit() + ? persistentClass + : findColumnOwner( persistentClass, joinColumn.getReferencedColumn(), context ); } /** @@ -1043,7 +1040,7 @@ public static String getAnnotationValueStringOrNull(String value) { public static Any buildAnyValue( jakarta.persistence.Column discriminatorColumn, Formula discriminatorFormula, - AnnotatedJoinColumn[] keyColumns, + AnnotatedJoinColumns keyColumns, PropertyData inferredData, boolean cascadeOnDelete, boolean lazy, @@ -1053,8 +1050,9 @@ public static Any buildAnyValue( boolean optional, MetadataBuildingContext context) { final XProperty xProperty = inferredData.getProperty(); + final AnnotatedJoinColumn[] columns = keyColumns.getColumns(); - final Any value = new Any( context, keyColumns[0].getTable(), true ); + final Any value = new Any( context, columns[0].getTable(), true ); value.setLazy( lazy ); value.setCascadeDeleteEnabled( cascadeOnDelete ); @@ -1090,7 +1088,7 @@ public static Any buildAnyValue( final Map> discriminatorValueMappings = new HashMap<>(); processAnyDiscriminatorValues( inferredData.getProperty(), - (valueMapping) -> discriminatorValueMappings.put( + valueMapping -> discriminatorValueMappings.put( discriminatorJavaType.wrap( valueMapping.discriminator(), null ), valueMapping.entity() ) @@ -1098,12 +1096,12 @@ public static Any buildAnyValue( value.setDiscriminatorValueMappings( discriminatorValueMappings ); BasicValueBinder keyValueBinder = new BasicValueBinder( BasicValueBinder.Kind.ANY_KEY, context ); - assert keyColumns.length == 1; - keyColumns[0].setTable( value.getTable() ); - keyValueBinder.setColumns( keyColumns ); + assert columns.length == 1; + columns[0].setTable( value.getTable() ); + keyValueBinder.setColumns(columns); if ( !optional ) { - for (AnnotatedJoinColumn column : keyColumns) { + for ( AnnotatedJoinColumn column : columns) { column.setNullable( false ); } } @@ -1112,10 +1110,10 @@ public static Any buildAnyValue( value.setKey( keyDescriptor ); keyValueBinder.fillSimpleValue(); AnnotatedColumn.checkPropertyConsistency( - keyColumns, + columns, propertyHolder.getEntityName() + "." + inferredData.getPropertyName() ); - keyColumns[0].linkWithValue( keyDescriptor ); + columns[0].linkWithValue( keyDescriptor ); return value; } @@ -1168,7 +1166,7 @@ public static MappedSuperclass getMappedSuperclassOrNull( } public static String getPath(PropertyHolder holder, PropertyData property) { - return StringHelper.qualify( holder.getPath(), property.getPropertyName() ); + return qualify( holder.getPath(), property.getPropertyName() ); } static PropertyData getPropertyOverriddenByMapperOrMapsId( diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/ColumnsBuilder.java b/hibernate-core/src/main/java/org/hibernate/cfg/ColumnsBuilder.java index aa7194aecb..da1d65132e 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/ColumnsBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/ColumnsBuilder.java @@ -26,7 +26,6 @@ import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.cfg.annotations.EntityBinder; import org.hibernate.cfg.annotations.Nullability; -import org.hibernate.internal.util.StringHelper; import static org.hibernate.cfg.AnnotatedColumn.buildColumnFromAnnotation; import static org.hibernate.cfg.AnnotatedColumn.buildColumnFromNoAnnotation; @@ -35,6 +34,8 @@ import static org.hibernate.cfg.BinderHelper.getOverridableAnnotation; import static org.hibernate.cfg.BinderHelper.getPath; import static org.hibernate.cfg.BinderHelper.getPropertyOverriddenByMapperOrMapsId; +import static org.hibernate.cfg.BinderHelper.getRelativePath; +import static org.hibernate.internal.util.StringHelper.isEmpty; /** * Do the initial discovery of columns metadata and apply defaults. @@ -52,7 +53,7 @@ class ColumnsBuilder { private final EntityBinder entityBinder; private final MetadataBuildingContext buildingContext; private AnnotatedColumn[] columns; - private AnnotatedJoinColumn[] joinColumns; + private AnnotatedJoinColumns joinColumns; public ColumnsBuilder( PropertyHolder propertyHolder, @@ -73,7 +74,7 @@ public AnnotatedColumn[] getColumns() { return columns; } - public AnnotatedJoinColumn[] getJoinColumns() { + public AnnotatedJoinColumns getJoinColumns() { return joinColumns; } @@ -122,14 +123,14 @@ else if ( property.isAnnotationPresent( Columns.class ) ) { ( property.isAnnotationPresent( ManyToOne.class ) || property.isAnnotationPresent( OneToOne.class ) ) ) { - joinColumns = buildDefaultJoinColumnsForXToOne( property, inferredData ); + joinColumns = buildDefaultJoinColumnsForToOne( property, inferredData ); } else if ( joinColumns == null && ( property.isAnnotationPresent( OneToMany.class ) || property.isAnnotationPresent( ElementCollection.class ) ) ) { OneToMany oneToMany = property.getAnnotation( OneToMany.class ); - joinColumns = AnnotatedJoinColumn.buildJoinColumns( + joinColumns = AnnotatedJoinColumns.buildJoinColumns( null, comment, oneToMany != null ? oneToMany.mappedBy() : "", @@ -164,12 +165,18 @@ else if ( joinColumns == null && property.isAnnotationPresent( org.hibernate.ann return this; } - private AnnotatedJoinColumn[] buildDefaultJoinColumnsForXToOne(XProperty property, PropertyData inferredData) { - AnnotatedJoinColumn[] joinColumns; - JoinTable joinTableAnn = propertyHolder.getJoinTable( property ); - Comment comment = property.getAnnotation(Comment.class); + private AnnotatedJoinColumns buildDefaultJoinColumnsForToOne(XProperty property, PropertyData inferredData) { + final JoinTable joinTableAnn = propertyHolder.getJoinTable( property ); + final Comment comment = property.getAnnotation(Comment.class); if ( joinTableAnn != null ) { - joinColumns = AnnotatedJoinColumn.buildJoinColumns( + if ( isEmpty( joinTableAnn.name() ) ) { + //TODO: I don't see why this restriction makes sense (use the same defaulting rule as for many-valued) + throw new AnnotationException( + "Single-valued association " + getPath( propertyHolder, inferredData ) + + " has a '@JoinTable' annotation with no explicit 'name'" + ); + } + return AnnotatedJoinColumns.buildJoinColumns( joinTableAnn.inverseJoinColumns(), comment, null, @@ -178,17 +185,10 @@ private AnnotatedJoinColumn[] buildDefaultJoinColumnsForXToOne(XProperty propert inferredData.getPropertyName(), buildingContext ); - if ( StringHelper.isEmpty( joinTableAnn.name() ) ) { - //TODO: I don't see why this restriction makes sense (use the same defaulting rule as for many-valued) - throw new AnnotationException( - "Single-valued association " + getPath( propertyHolder, inferredData ) - + " has a '@JoinTable' annotation with no explicit 'name'" - ); - } } else { OneToOne oneToOneAnn = property.getAnnotation( OneToOne.class ); - joinColumns = AnnotatedJoinColumn.buildJoinColumns( + return AnnotatedJoinColumns.buildJoinColumns( null, comment, oneToOneAnn != null ? oneToOneAnn.mappedBy() : null, @@ -198,53 +198,63 @@ private AnnotatedJoinColumn[] buildDefaultJoinColumnsForXToOne(XProperty propert buildingContext ); } - return joinColumns; } - private AnnotatedJoinColumn[] buildExplicitJoinColumns(XProperty property, PropertyData inferredData) { + private AnnotatedJoinColumns buildExplicitJoinColumns(XProperty property, PropertyData inferredData) { // process @JoinColumns before @Columns to handle collection of entities properly - final JoinColumn[] joinColumnAnnotations = getJoinColumnAnnotations(property, inferredData); + final String propertyName = inferredData.getPropertyName(); + + final JoinColumn[] joinColumnAnnotations = getJoinColumnAnnotations( property, inferredData ); if ( joinColumnAnnotations != null ) { - return AnnotatedJoinColumn.buildJoinColumns( + return AnnotatedJoinColumns.buildJoinColumns( joinColumnAnnotations, property.getAnnotation( Comment.class ), null, entityBinder.getSecondaryTables(), propertyHolder, - inferredData.getPropertyName(), + propertyName, buildingContext ); } final JoinColumnOrFormula[] joinColumnOrFormulaAnnotations = joinColumnOrFormulaAnnotations( property, inferredData ); if ( joinColumnOrFormulaAnnotations != null ) { - return AnnotatedJoinColumn.buildJoinColumnsOrFormulas( + return AnnotatedJoinColumns.buildJoinColumnsOrFormulas( joinColumnOrFormulaAnnotations, null, entityBinder.getSecondaryTables(), propertyHolder, - inferredData.getPropertyName(), + propertyName, buildingContext ); } if ( property.isAnnotationPresent( JoinFormula.class) ) { final JoinFormula joinFormula = getOverridableAnnotation( property, JoinFormula.class, buildingContext ); - final AnnotatedJoinColumn[] annotatedJoinColumns = new AnnotatedJoinColumn[1]; - annotatedJoinColumns[0] = AnnotatedJoinColumn.buildJoinFormula( - joinFormula, - entityBinder.getSecondaryTables(), - propertyHolder, - inferredData.getPropertyName(), - buildingContext - ); - return annotatedJoinColumns; + return buildJoinColumnsWithFormula( propertyName, joinFormula ); } return null; } + private AnnotatedJoinColumns buildJoinColumnsWithFormula(String propertyName, JoinFormula joinFormula) { + final AnnotatedJoinColumn[] columns = new AnnotatedJoinColumn[1]; + columns[0] = AnnotatedJoinColumn.buildJoinFormula( + joinFormula, + entityBinder.getSecondaryTables(), + propertyHolder, + propertyName, + buildingContext + ); + final AnnotatedJoinColumns joinColumns = new AnnotatedJoinColumns(); + joinColumns.setBuildingContext( buildingContext ); + joinColumns.setPropertyHolder( propertyHolder ); + joinColumns.setPropertyName( getRelativePath( propertyHolder, propertyName) ); + joinColumns.setColumns( columns ); + return joinColumns; + } + private JoinColumnOrFormula[] joinColumnOrFormulaAnnotations(XProperty property, PropertyData inferredData) { if ( property.isAnnotationPresent( JoinColumnOrFormula.class ) ) { return new JoinColumnOrFormula[] { property.getAnnotation( JoinColumnOrFormula.class ) }; @@ -292,10 +302,13 @@ AnnotatedColumn[] overrideColumnFromMapperOrMapsIdProperty(boolean isId) { } /** - * useful to override a column either by @MapsId or by @IdClass + * Useful to override a column either by {@code @MapsId} or by {@code @IdClass} */ + //TODO: should we introduce an AnnotatedColumns type and return that here? private AnnotatedColumn[] buildExplicitOrDefaultJoinColumn(PropertyData overridingProperty) { - final AnnotatedColumn[] columns = buildExplicitJoinColumns( overridingProperty.getProperty(), overridingProperty ); - return columns == null ? buildDefaultJoinColumnsForXToOne( overridingProperty.getProperty(), overridingProperty ) : columns; + final AnnotatedJoinColumns columns = buildExplicitJoinColumns( overridingProperty.getProperty(), overridingProperty ); + return columns == null + ? buildDefaultJoinColumnsForToOne( overridingProperty.getProperty(), overridingProperty ).getColumns() + : columns.getColumns(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/CopyIdentifierComponentSecondPass.java b/hibernate-core/src/main/java/org/hibernate/cfg/CopyIdentifierComponentSecondPass.java index 242441d3df..5ad0b30c34 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/CopyIdentifierComponentSecondPass.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/CopyIdentifierComponentSecondPass.java @@ -39,12 +39,12 @@ public class CopyIdentifierComponentSecondPass extends FkSecondPass { private final String propertyName; private final Component component; private final MetadataBuildingContext buildingContext; - private final AnnotatedJoinColumn[] joinColumns; + private final AnnotatedJoinColumns joinColumns; public CopyIdentifierComponentSecondPass( Component comp, String referencedEntityName, String propertyName, - AnnotatedJoinColumn[] joinColumns, + AnnotatedJoinColumns joinColumns, MetadataBuildingContext buildingContext) { super( comp, joinColumns ); this.component = comp; @@ -91,8 +91,9 @@ public void doSecondPass(Map persistentClasses) throws //prepare column name structure boolean isExplicitReference = true; - final Map columnByReferencedName = mapOfSize( joinColumns.length ); - for ( AnnotatedJoinColumn joinColumn : joinColumns ) { + final AnnotatedJoinColumn[] cols = joinColumns.getColumns(); + final Map columnByReferencedName = mapOfSize( cols.length ); + for ( AnnotatedJoinColumn joinColumn : cols) { if ( !joinColumn.isReferenceImplicit() ) { //JPA 2 requires referencedColumnNames to be case-insensitive columnByReferencedName.put( joinColumn.getReferencedColumn().toLowerCase(Locale.ROOT), joinColumn ); @@ -101,8 +102,8 @@ public void doSecondPass(Map persistentClasses) throws //try default column orientation if ( columnByReferencedName.isEmpty() ) { isExplicitReference = false; - for ( int i = 0; i < joinColumns.length; i++ ) { - columnByReferencedName.put( String.valueOf( i ), joinColumns[i] ); + for (int i = 0; i < cols.length; i++ ) { + columnByReferencedName.put( String.valueOf( i ), cols[i] ); } } @@ -184,8 +185,9 @@ private Property createSimpleProperty( final SimpleValue referencedValue = (SimpleValue) referencedProperty.getValue(); value.copyTypeFrom( referencedValue ); - if ( joinColumns[0].isNameDeferred() ) { - joinColumns[0].copyReferencedStructureAndCreateDefaultJoinColumns( + final AnnotatedJoinColumn firstColumn = joinColumns.getColumns()[0]; + if ( firstColumn.isNameDeferred() ) { + firstColumn.copyReferencedStructureAndCreateDefaultJoinColumns( referencedPersistentClass, referencedValue, value @@ -200,17 +202,18 @@ private Property createSimpleProperty( } final Column column = (Column) selectable; final AnnotatedJoinColumn joinColumn; - String logicalColumnName = null; + final String logicalColumnName; if ( isExplicitReference ) { logicalColumnName = column.getName(); //JPA 2 requires referencedColumnNames to be case-insensitive joinColumn = columnByReferencedName.get( logicalColumnName.toLowerCase(Locale.ROOT ) ); } else { + logicalColumnName = null; joinColumn = columnByReferencedName.get( String.valueOf( index.get() ) ); index.getAndIncrement(); } - if ( joinColumn == null && !joinColumns[0].isNameDeferred() ) { + if ( joinColumn == null && !firstColumn.isNameDeferred() ) { throw new AnnotationException( "Property '" + propertyName + "' of entity '" + component.getOwner().getEntityName() @@ -225,7 +228,7 @@ private Property createSimpleProperty( final Database database = buildingContext.getMetadataCollector().getDatabase(); final PhysicalNamingStrategy physicalNamingStrategy = buildingContext.getBuildingOptions().getPhysicalNamingStrategy(); final Identifier explicitName = database.toIdentifier( columnName ); - final Identifier physicalName = physicalNamingStrategy.toPhysicalColumnName( explicitName, database.getJdbcEnvironment() ); + final Identifier physicalName = physicalNamingStrategy.toPhysicalColumnName( explicitName, database.getJdbcEnvironment() ); value.addColumn( new Column( physicalName.render( database.getDialect() ) ) ); if ( joinColumn != null ) { applyComponentColumnSizeValueToJoinColumn( column, joinColumn ); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/FkSecondPass.java b/hibernate-core/src/main/java/org/hibernate/cfg/FkSecondPass.java index 7d11080a06..9115eea291 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/FkSecondPass.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/FkSecondPass.java @@ -15,7 +15,7 @@ */ public abstract class FkSecondPass implements SecondPass { protected SimpleValue value; - protected AnnotatedJoinColumn[] columns; + protected AnnotatedJoinColumns columns; /** * unique counter is needed to differentiate 2 instances of FKSecondPass * as they are compared. @@ -25,7 +25,7 @@ public abstract class FkSecondPass implements SecondPass { private final int uniqueCounter; private static final AtomicInteger globalCounter = new AtomicInteger(); - public FkSecondPass(SimpleValue value, AnnotatedJoinColumn[] columns) { + public FkSecondPass(SimpleValue value, AnnotatedJoinColumns columns) { this.value = value; this.columns = columns; this.uniqueCounter = globalCounter.getAndIncrement(); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/JoinedSubclassFkSecondPass.java b/hibernate-core/src/main/java/org/hibernate/cfg/JoinedSubclassFkSecondPass.java index 6bbc7d9507..4af2f2f605 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/JoinedSubclassFkSecondPass.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/JoinedSubclassFkSecondPass.java @@ -24,7 +24,7 @@ public class JoinedSubclassFkSecondPass extends FkSecondPass { public JoinedSubclassFkSecondPass( JoinedSubclass entity, - AnnotatedJoinColumn[] inheritanceJoinedColumns, + AnnotatedJoinColumns inheritanceJoinedColumns, SimpleValue key, MetadataBuildingContext buildingContext) { super( key, inheritanceJoinedColumns ); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/OneToOneSecondPass.java b/hibernate-core/src/main/java/org/hibernate/cfg/OneToOneSecondPass.java index 6d21976614..dc62aa5e0b 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/OneToOneSecondPass.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/OneToOneSecondPass.java @@ -53,7 +53,7 @@ public class OneToOneSecondPass implements SecondPass { private final boolean cascadeOnDelete; private final boolean optional; private final String cascadeStrategy; - private final AnnotatedJoinColumn[] joinColumns; + private final AnnotatedJoinColumns joinColumns; //that sucks, we should read that from the property mainly public OneToOneSecondPass( @@ -67,7 +67,7 @@ public OneToOneSecondPass( boolean cascadeOnDelete, boolean optional, String cascadeStrategy, - AnnotatedJoinColumn[] columns, + AnnotatedJoinColumns columns, MetadataBuildingContext buildingContext) { this.ownerEntity = ownerEntity; this.ownerProperty = ownerProperty; diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/PkDrivenByDefaultMapsIdSecondPass.java b/hibernate-core/src/main/java/org/hibernate/cfg/PkDrivenByDefaultMapsIdSecondPass.java index 6835d2382d..cf46300f6a 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/PkDrivenByDefaultMapsIdSecondPass.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/PkDrivenByDefaultMapsIdSecondPass.java @@ -18,10 +18,10 @@ */ public class PkDrivenByDefaultMapsIdSecondPass extends FkSecondPass { private final String referencedEntityName; - private final AnnotatedJoinColumn[] columns; + private final AnnotatedJoinColumns columns; private final SimpleValue value; - public PkDrivenByDefaultMapsIdSecondPass(String referencedEntityName, AnnotatedJoinColumn[] columns, SimpleValue value) { + public PkDrivenByDefaultMapsIdSecondPass(String referencedEntityName, AnnotatedJoinColumns columns, SimpleValue value) { super( value, columns ); this.referencedEntityName = referencedEntityName; this.columns = columns; diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/ToOneBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/ToOneBinder.java index 00c49f04d7..8c2b16c675 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/ToOneBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/ToOneBinder.java @@ -69,7 +69,7 @@ static void bindManyToOne( boolean inSecondPass, MetadataBuildingContext context, XProperty property, - AnnotatedJoinColumn[] joinColumns, + AnnotatedJoinColumns joinColumns, PropertyBinder propertyBinder, boolean forcePersist) { final ManyToOne manyToOne = property.getAnnotation( ManyToOne.class ); @@ -91,7 +91,7 @@ static void bindManyToOne( final JoinTable joinTable = propertyHolder.getJoinTable( property ); if ( joinTable != null ) { final Join join = propertyHolder.addJoin( joinTable, false ); - for ( AnnotatedJoinColumn joinColumn : joinColumns ) { + for ( AnnotatedJoinColumn joinColumn : joinColumns.getColumns() ) { joinColumn.setExplicitTableName( join.getTable().getName() ); } } @@ -128,7 +128,7 @@ private static boolean isMandatory(boolean optional, XProperty property, NotFoun private static void bindManyToOne( String cascadeStrategy, - AnnotatedJoinColumn[] columns, + AnnotatedJoinColumns joinColumns, boolean optional, NotFoundAction notFoundAction, boolean cascadeOnDelete, @@ -142,7 +142,7 @@ private static void bindManyToOne( MetadataBuildingContext context) { // All FK columns should be in the same table final org.hibernate.mapping.ManyToOne value = - new org.hibernate.mapping.ManyToOne( context, columns[0].getTable() ); + new org.hibernate.mapping.ManyToOne( context, joinColumns.getColumns()[0].getTable() ); if ( unique ) { // This is a @OneToOne mapped to a physical o.h.mapping.ManyToOne value.markAsLogicalOneToOne(); @@ -155,20 +155,20 @@ private static void bindManyToOne( value.setCascadeDeleteEnabled( cascadeOnDelete ); //value.setLazy( fetchMode != FetchMode.JOIN ); if ( !optional ) { - for ( AnnotatedJoinColumn column : columns ) { + for ( AnnotatedJoinColumn column : joinColumns.getColumns() ) { column.setNullable( false ); } } if ( property.isAnnotationPresent( MapsId.class ) ) { //read only - for ( AnnotatedJoinColumn column : columns ) { + for ( AnnotatedJoinColumn column : joinColumns.getColumns() ) { column.setInsertable( false ); column.setUpdatable( false ); } } - boolean hasSpecjManyToOne = handleSpecjSyntax( columns, inferredData, context, property ); + boolean hasSpecjManyToOne = handleSpecjSyntax( joinColumns, inferredData, context, property ); value.setTypeName( inferredData.getClassOrElementName() ); final String propertyName = inferredData.getPropertyName(); value.setTypeUsingReflection( propertyHolder.getClassName(), propertyName ); @@ -179,7 +179,7 @@ private static void bindManyToOne( final FkSecondPass secondPass = new ToOneFkSecondPass( value, - columns, + joinColumns, !optional && unique, //cannot have nullable and unique on certain DBs like Derby propertyHolder.getPersistentClass(), fullPath, @@ -194,7 +194,7 @@ private static void bindManyToOne( processManyToOneProperty( cascadeStrategy, - columns, + joinColumns, optional, propertyHolder, inferredData, @@ -208,7 +208,7 @@ private static void bindManyToOne( } private static boolean handleSpecjSyntax( - AnnotatedJoinColumn[] columns, + AnnotatedJoinColumns columns, PropertyData inferredData, MetadataBuildingContext context, XProperty property) { @@ -228,7 +228,7 @@ private static boolean handleSpecjSyntax( && joinColumn.name().equals( columnName ) && !property.isAnnotationPresent( MapsId.class ) ) { hasSpecjManyToOne = true; - for ( AnnotatedJoinColumn column : columns) { + for ( AnnotatedJoinColumn column : columns.getColumns() ) { column.setInsertable( false ); column.setUpdatable( false ); } @@ -240,7 +240,7 @@ private static boolean handleSpecjSyntax( private static void processManyToOneProperty( String cascadeStrategy, - AnnotatedJoinColumn[] columns, + AnnotatedJoinColumns columns, boolean optional, PropertyHolder propertyHolder, PropertyData inferredData, @@ -250,7 +250,7 @@ private static void processManyToOneProperty( boolean hasSpecjManyToOne, String propertyName) { - checkPropertyConsistency( columns, qualify( propertyHolder.getEntityName(), propertyName ) ); + checkPropertyConsistency( columns.getColumns(), qualify( propertyHolder.getEntityName(), propertyName ) ); //PropertyBinder binder = new PropertyBinder(); propertyBinder.setName( propertyName ); @@ -265,10 +265,11 @@ else if (hasSpecjManyToOne) { propertyBinder.setUpdatable( false ); } else { - propertyBinder.setInsertable( columns[0].isInsertable() ); - propertyBinder.setUpdatable( columns[0].isUpdatable() ); + final AnnotatedJoinColumn firstColumn = columns.getColumns()[0]; + propertyBinder.setInsertable( firstColumn.isInsertable() ); + propertyBinder.setUpdatable( firstColumn.isUpdatable() ); } - propertyBinder.setColumns( columns ); + propertyBinder.setColumns( columns.getColumns() ); propertyBinder.setAccessType( inferredData.getDefaultAccess() ); propertyBinder.setCascade( cascadeStrategy ); propertyBinder.setProperty( property ); @@ -361,7 +362,7 @@ static void bindOneToOne( boolean inSecondPass, MetadataBuildingContext context, XProperty property, - AnnotatedJoinColumn[] joinColumns, + AnnotatedJoinColumns joinColumns, PropertyBinder propertyBinder, boolean forcePersist) { final OneToOne oneToOne = property.getAnnotation( OneToOne.class ); @@ -392,7 +393,7 @@ static void bindOneToOne( if ( notFoundAction != null ) { join.disableForeignKeyCreation(); } - for ( AnnotatedJoinColumn joinColumn : joinColumns) { + for ( AnnotatedJoinColumn joinColumn : joinColumns.getColumns() ) { joinColumn.setExplicitTableName( join.getTable().getName() ); } } @@ -417,7 +418,7 @@ static void bindOneToOne( private static void bindOneToOne( String cascadeStrategy, - AnnotatedJoinColumn[] joinColumns, + AnnotatedJoinColumns joinColumns, boolean optional, FetchMode fetchMode, NotFoundAction notFoundAction, @@ -478,7 +479,7 @@ private static void bindOneToOne( } } - private static boolean isMapToPK(AnnotatedJoinColumn[] joinColumns, PropertyHolder propertyHolder, boolean trueOneToOne) { + private static boolean isMapToPK(AnnotatedJoinColumns joinColumns, PropertyHolder propertyHolder, boolean trueOneToOne) { if ( trueOneToOne ) { return true; } @@ -491,15 +492,16 @@ private static boolean isMapToPK(AnnotatedJoinColumn[] joinColumns, PropertyHold return false; } else { - List idColumnNames = new ArrayList<>(); - if ( identifier.getColumnSpan() != joinColumns.length ) { + final List idColumnNames = new ArrayList<>(); + final AnnotatedJoinColumn[] columns = joinColumns.getColumns(); + if ( identifier.getColumnSpan() != columns.length ) { return false; } else { for ( org.hibernate.mapping.Column currentColumn: identifier.getColumns() ) { idColumnNames.add( currentColumn.getName() ); } - for ( AnnotatedJoinColumn col: joinColumns) { + for ( AnnotatedJoinColumn col: columns ) { if ( !idColumnNames.contains( col.getMappingColumn().getName() ) ) { return false; } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/ToOneFkSecondPass.java b/hibernate-core/src/main/java/org/hibernate/cfg/ToOneFkSecondPass.java index d4c704fddb..dbdb5fb636 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/ToOneFkSecondPass.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/ToOneFkSecondPass.java @@ -38,7 +38,7 @@ public class ToOneFkSecondPass extends FkSecondPass { public ToOneFkSecondPass( ToOne value, - AnnotatedJoinColumn[] columns, + AnnotatedJoinColumns columns, boolean unique, PersistentClass persistentClass, String path, @@ -113,7 +113,9 @@ public void doSecondPass(java.util.Map persistentClasse ); TableBinder.bindForeignKey( targetEntity, persistentClass, columns, manyToOne, unique, buildingContext ); // HbmMetadataSourceProcessorImpl does this only when property-ref != null, but IMO, it makes sense event if it is null - if ( !manyToOne.isIgnoreNotFound() ) manyToOne.createPropertyRefConstraints( persistentClasses ); + if ( !manyToOne.isIgnoreNotFound() ) { + manyToOne.createPropertyRefConstraints( persistentClasses ); + } } else if ( value instanceof OneToOne ) { value.createForeignKey(); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/BasicValueBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/BasicValueBinder.java index c6389cad65..ce41bd6bf6 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/BasicValueBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/BasicValueBinder.java @@ -51,10 +51,12 @@ import org.hibernate.annotations.common.reflection.XProperty; import org.hibernate.boot.model.TypeDefinition; import org.hibernate.boot.model.convert.spi.ConverterDescriptor; +import org.hibernate.boot.spi.InFlightMetadataCollector; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.cfg.AccessType; import org.hibernate.cfg.AnnotatedColumn; import org.hibernate.cfg.AnnotatedJoinColumn; +import org.hibernate.cfg.AnnotatedJoinColumns; import org.hibernate.cfg.PkDrivenByDefaultMapsIdSecondPass; import org.hibernate.cfg.SetBasicValueTypeSecondPass; import org.hibernate.dialect.Dialect; @@ -1122,11 +1124,15 @@ public BasicValue make() { } public void linkWithValue() { - if ( columns[0].isNameDeferred() && !buildingContext.getMetadataCollector().isInSecondPass() && referencedEntityName != null ) { - buildingContext.getMetadataCollector().addSecondPass( - new PkDrivenByDefaultMapsIdSecondPass( - referencedEntityName, (AnnotatedJoinColumn[]) columns, basicValue - ) + final InFlightMetadataCollector collector = buildingContext.getMetadataCollector(); + if ( !collector.isInSecondPass() && columns[0].isNameDeferred() && referencedEntityName != null ) { + final AnnotatedJoinColumns joinColumns = new AnnotatedJoinColumns(); + joinColumns.setBuildingContext( buildingContext ); + joinColumns.setPropertyHolder( columns[0].getPropertyHolder() ); + joinColumns.setPropertyName( columns[0].getPropertyName() ); + joinColumns.setColumns( (AnnotatedJoinColumn[]) columns ); + collector.addSecondPass( + new PkDrivenByDefaultMapsIdSecondPass( referencedEntityName, joinColumns, basicValue ) ); } else { @@ -1139,13 +1145,9 @@ public void linkWithValue() { public void fillSimpleValue() { LOG.debugf( "Starting `BasicValueBinder#fillSimpleValue` for %s", propertyName ); - final String explicitBasicTypeName; - if ( this.explicitBasicTypeName != null ) { - explicitBasicTypeName = this.explicitBasicTypeName; - } - else { - explicitBasicTypeName = this.timeStampVersionType; - } + final String explicitBasicTypeName = this.explicitBasicTypeName != null + ? this.explicitBasicTypeName + : this.timeStampVersionType; basicValue.setExplicitTypeName( explicitBasicTypeName ); basicValue.setExplicitTypeParams( explicitLocalTypeParams ); @@ -1188,10 +1190,9 @@ else if ( isLob || isSerializable() ) { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - if ( ( explicitCustomType != null && DynamicParameterizedType.class.isAssignableFrom( explicitCustomType ) ) - || ( typeClass != null && DynamicParameterizedType.class.isAssignableFrom( typeClass ) ) ) { - final Map parameters = createDynamicParameterizedTypeParameters(); - basicValue.setTypeParameters( (Map) parameters ); + if ( explicitCustomType != null && DynamicParameterizedType.class.isAssignableFrom( explicitCustomType ) + || typeClass != null && DynamicParameterizedType.class.isAssignableFrom( typeClass ) ) { + basicValue.setTypeParameters( createDynamicParameterizedTypeParameters() ); } if ( converterDescriptor != null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java index 702ef8b124..c38811fe02 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java @@ -101,6 +101,7 @@ import org.hibernate.cfg.AnnotatedClassType; import org.hibernate.cfg.AnnotatedColumn; import org.hibernate.cfg.AnnotatedJoinColumn; +import org.hibernate.cfg.AnnotatedJoinColumns; import org.hibernate.cfg.AnnotationBinder; import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.CollectionPropertyHolder; @@ -135,6 +136,7 @@ import org.hibernate.mapping.Selectable; import org.hibernate.mapping.SimpleValue; import org.hibernate.mapping.Table; +import org.hibernate.mapping.Value; import org.hibernate.metamodel.CollectionClassification; import org.hibernate.metamodel.spi.EmbeddableInstantiator; import org.hibernate.persister.collection.CollectionPersister; @@ -152,8 +154,8 @@ import static org.hibernate.cfg.AnnotatedColumn.buildColumnsFromAnnotations; import static org.hibernate.cfg.AnnotatedColumn.buildFormulaFromAnnotation; import static org.hibernate.cfg.AnnotatedColumn.checkPropertyConsistency; -import static org.hibernate.cfg.AnnotatedJoinColumn.buildJoinColumnsWithDefaultColumnSuffix; -import static org.hibernate.cfg.AnnotatedJoinColumn.buildJoinTableJoinColumns; +import static org.hibernate.cfg.AnnotatedJoinColumns.buildJoinColumnsWithDefaultColumnSuffix; +import static org.hibernate.cfg.AnnotatedJoinColumns.buildJoinTableJoinColumns; import static org.hibernate.cfg.AnnotationBinder.fillComponent; import static org.hibernate.cfg.BinderHelper.PRIMITIVE_NAMES; import static org.hibernate.cfg.BinderHelper.buildAnyValue; @@ -201,7 +203,6 @@ public abstract class CollectionBinder { private String mappedBy; private XClass collectionElementType; private XClass targetEntity; - protected AnnotatedJoinColumn[] inverseJoinColumns; private String cascadeStrategy; private String cacheConcurrencyStrategy; private String cacheRegionName; @@ -211,8 +212,9 @@ public abstract class CollectionBinder { protected String mapKeyPropertyName; private boolean insertable = true; private boolean updatable = true; - protected AnnotatedJoinColumn[] foreignJoinColumns; - private AnnotatedJoinColumn[] joinColumns; + protected AnnotatedJoinColumns inverseJoinColumns; + protected AnnotatedJoinColumns foreignJoinColumns; + private AnnotatedJoinColumns joinColumns; private boolean isExplicitAssociationTable; private AnnotatedColumn[] elementColumns; protected boolean isEmbedded; @@ -220,7 +222,7 @@ public abstract class CollectionBinder { protected NotFoundAction notFoundAction; private TableBinder tableBinder; protected AnnotatedColumn[] mapKeyColumns; - protected AnnotatedJoinColumn[] mapKeyManyToManyColumns; + protected AnnotatedJoinColumns mapKeyManyToManyColumns; protected Map localGenerators; protected Map inheritanceStatePerClass; private XClass declaringClass; @@ -258,7 +260,7 @@ public static void bindCollection( MetadataBuildingContext context, Map inheritanceStatePerClass, XProperty property, - AnnotatedJoinColumn[] joinColumns) { + AnnotatedJoinColumns joinColumns) { final OneToMany oneToManyAnn = property.getAnnotation( OneToMany.class ); final ManyToMany manyToManyAnn = property.getAnnotation( ManyToMany.class ); @@ -302,8 +304,8 @@ && isToManyAssociationWithinEmbeddableCollection( propertyHolder ) ) { collectionBinder.setCache( property.getAnnotation( Cache.class ) ); collectionBinder.setPropertyHolder(propertyHolder); - Cascade hibernateCascade = property.getAnnotation( Cascade.class ); - NotFound notFound = property.getAnnotation( NotFound.class ); + final Cascade hibernateCascade = property.getAnnotation( Cascade.class ); + final NotFound notFound = property.getAnnotation( NotFound.class ); if ( notFound != null ) { if ( manyToManyAnn == null ) { throw new AnnotationException( "Collection '" + getPath( propertyHolder, inferredData ) @@ -337,7 +339,7 @@ && isToManyAssociationWithinEmbeddableCollection( propertyHolder ) ) { collectionBinder, comment ); - final AnnotatedJoinColumn[] mapJoinColumns = buildJoinColumnsWithDefaultColumnSuffix( + final AnnotatedJoinColumns mapJoinColumns = buildJoinColumnsWithDefaultColumnSuffix( joinKeyColumns, comment, null, @@ -377,8 +379,8 @@ && isToManyAssociationWithinEmbeddableCollection( propertyHolder ) ) { mappedBy ); - OnDelete onDeleteAnn = property.getAnnotation( OnDelete.class ); - boolean onDeleteCascade = onDeleteAnn != null && OnDeleteAction.CASCADE == onDeleteAnn.action(); + final OnDelete onDeleteAnn = property.getAnnotation( OnDelete.class ); + final boolean onDeleteCascade = onDeleteAnn != null && OnDeleteAction.CASCADE == onDeleteAnn.action(); collectionBinder.setCascadeDeleteEnabled( onDeleteCascade ); if ( isIdentifierMapper ) { collectionBinder.setInsertable( false ); @@ -400,7 +402,7 @@ private static String handleTargetEntity( PropertyData inferredData, MetadataBuildingContext context, XProperty property, - AnnotatedJoinColumn[] joinColumns, + AnnotatedJoinColumns joinColumns, OneToMany oneToManyAnn, ManyToMany manyToManyAnn, ElementCollection elementCollectionAnn, @@ -412,15 +414,15 @@ private static String handleTargetEntity( throw new AnnotationException( "Property '" + getPath( propertyHolder, inferredData ) + "' is annotated both '@OneToMany' and '@ManyToMany'" ); } - String mappedBy = null; - ReflectionManager reflectionManager = context.getBootstrapContext().getReflectionManager(); + final String mappedBy; + final ReflectionManager reflectionManager = context.getBootstrapContext().getReflectionManager(); if ( oneToManyAnn != null ) { - for ( AnnotatedJoinColumn column : joinColumns) { + for ( AnnotatedJoinColumn column : joinColumns.getColumns() ) { if ( column.isSecondary() ) { throw new NotYetImplementedException( "Collections having FK in secondary table" ); } } - collectionBinder.setFkJoinColumns(joinColumns); + collectionBinder.setFkJoinColumns( joinColumns ); mappedBy = oneToManyAnn.mappedBy(); //noinspection unchecked collectionBinder.setTargetEntity( reflectionManager.toXClass( oneToManyAnn.targetEntity() ) ); @@ -430,7 +432,7 @@ private static String handleTargetEntity( collectionBinder.setOneToMany( true ); } else if ( elementCollectionAnn != null ) { - for ( AnnotatedJoinColumn column : joinColumns ) { + for ( AnnotatedJoinColumn column : joinColumns.getColumns() ) { if ( column.isSecondary() ) { throw new NotYetImplementedException( "Collections having FK in secondary table" ); } @@ -459,6 +461,9 @@ else if ( property.isAnnotationPresent( ManyToAny.class ) ) { ); collectionBinder.setOneToMany( false ); } + else { + mappedBy = null; + } collectionBinder.setMappedBy( mappedBy ); return mappedBy; } @@ -588,11 +593,11 @@ private static void bindJoinedTableAssociation( PropertyHolder propertyHolder, PropertyData inferredData, String mappedBy) { - TableBinder associationTableBinder = new TableBinder(); - JoinColumn[] annJoins; - JoinColumn[] annInverseJoins; - JoinTable assocTable = propertyHolder.getJoinTable( property ); - CollectionTable collectionTable = property.getAnnotation( CollectionTable.class ); + final TableBinder associationTableBinder = new TableBinder(); + final JoinTable assocTable = propertyHolder.getJoinTable( property ); + final CollectionTable collectionTable = property.getAnnotation( CollectionTable.class ); + final JoinColumn[] annJoins; + final JoinColumn[] annInverseJoins; if ( assocTable != null || collectionTable != null ) { final String catalog; @@ -603,7 +608,6 @@ private static void bindJoinedTableAssociation( final JoinColumn[] inverseJoins; final Index[] jpaIndexes; - //JPA 2 has priority if ( collectionTable != null ) { catalog = collectionTable.catalog(); @@ -647,7 +651,7 @@ private static void bindJoinedTableAssociation( annJoins = null; annInverseJoins = null; } - final AnnotatedJoinColumn[] joinColumns = buildJoinTableJoinColumns( + final AnnotatedJoinColumns joinColumns = buildJoinTableJoinColumns( annJoins, entityBinder.getSecondaryTables(), propertyHolder, @@ -655,7 +659,7 @@ private static void bindJoinedTableAssociation( mappedBy, buildingContext ); - final AnnotatedJoinColumn[] inverseJoinColumns = buildJoinTableJoinColumns( + final AnnotatedJoinColumns inverseJoinColumns = buildJoinTableJoinColumns( annInverseJoins, entityBinder.getSecondaryTables(), propertyHolder, @@ -709,11 +713,11 @@ public void setAccessType(AccessType accessType) { this.accessType = accessType; } - public void setInverseJoinColumns(AnnotatedJoinColumn[] inverseJoinColumns) { + public void setInverseJoinColumns(AnnotatedJoinColumns inverseJoinColumns) { this.inverseJoinColumns = inverseJoinColumns; } - public void setJoinColumns(AnnotatedJoinColumn[] joinColumns) { + public void setJoinColumns(AnnotatedJoinColumns joinColumns) { this.joinColumns = joinColumns; } @@ -1491,8 +1495,8 @@ protected boolean bindStarToManySecondPass(Map persiste && !reversePropertyInJoin && oneToMany && !isExplicitAssociationTable - && ( joinColumns[0].isImplicit() && hasMappedBy() //implicit @JoinColumn - || !foreignJoinColumns[0].isImplicit() ) //this is an explicit @JoinColumn + && ( joinColumns.getColumns()[0].isImplicit() && hasMappedBy() //implicit @JoinColumn + || !foreignJoinColumns.getColumns()[0].isImplicit() ) //this is an explicit @JoinColumn ) { //this is a foreign key bindOneToManySecondPass( persistentClasses ); @@ -1517,7 +1521,7 @@ protected void bindOneToManySecondPass(Map persistentCl throw new AssertionFailure( "null was passed for argument property" ); } - logOneToManySeconPass(); + logOneToManySecondPass(); final org.hibernate.mapping.OneToMany oneToMany = new org.hibernate.mapping.OneToMany( buildingContext, getCollection().getOwner() ); @@ -1525,27 +1529,28 @@ protected void bindOneToManySecondPass(Map persistentCl oneToMany.setReferencedEntityName( getElementType().getName() ); oneToMany.setNotFoundAction( notFoundAction ); - final InFlightMetadataCollector collector = buildingContext.getMetadataCollector(); - - final String assocClass = oneToMany.getReferencedEntityName(); - final PersistentClass associatedClass = persistentClasses.get( assocClass ); + final String referencedEntityName = oneToMany.getReferencedEntityName(); + final PersistentClass associatedClass = persistentClasses.get( referencedEntityName ); handleJpaOrderBy( collection, associatedClass ); - final Map joins = collector.getJoins( assocClass ); if ( associatedClass == null ) { throw new MappingException( String.format( "Association [%s] for entity [%s] references unmapped class [%s]", - propertyName, propertyHolder.getClassName(), assocClass ) + propertyName, propertyHolder.getClassName(), referencedEntityName ) ); } oneToMany.setAssociatedClass( associatedClass ); - for ( AnnotatedJoinColumn column : foreignJoinColumns ) { - column.setPersistentClass( associatedClass, joins, inheritanceStatePerClass ); + + final Map joins = buildingContext.getMetadataCollector().getJoins( referencedEntityName ); + foreignJoinColumns.setPersistentClass( associatedClass, joins, inheritanceStatePerClass ); + for ( AnnotatedJoinColumn column : foreignJoinColumns.getColumns() ) { column.setJoins( joins ); + //TODO: this is lame, should get the table from foreignJoinColumns collection.setCollectionTable( column.getTable() ); } if ( LOG.isDebugEnabled() ) { LOG.debugf( "Mapping collection: %s -> %s", collection.getRole(), collection.getCollectionTable().getName() ); } + bindFilters( false ); handleWhere( false ); @@ -1553,21 +1558,28 @@ protected void bindOneToManySecondPass(Map persistentCl bindCollectionSecondPass( targetEntity, foreignJoinColumns, cascadeDeleteEnabled, buildingContext ); if ( !collection.isInverse() && !collection.getKey().isNullable() ) { - // for non-inverse one-to-many, with a not-null fk, add a backref! - final String entityName = oneToMany.getReferencedEntityName(); - final PersistentClass referenced = collector.getEntityBinding( entityName ); - final Backref backref = new Backref(); - final AnnotatedJoinColumn column = foreignJoinColumns[0]; - backref.setName( '_' + column.getPropertyName() + '_' + column.getLogicalColumnName() + "Backref" ); - backref.setUpdateable( false ); - backref.setSelectable( false ); - backref.setCollectionRole( collection.getRole() ); - backref.setEntityName( collection.getOwner().getEntityName() ); - backref.setValue( collection.getKey() ); - referenced.addProperty( backref ); + createOneToManyBackref( oneToMany ); } } + private void createOneToManyBackref(org.hibernate.mapping.OneToMany oneToMany) { + final InFlightMetadataCollector collector = buildingContext.getMetadataCollector(); + // for non-inverse one-to-many, with a not-null fk, add a backref! + final String entityName = oneToMany.getReferencedEntityName(); + final PersistentClass referenced = collector.getEntityBinding( entityName ); + final Backref backref = new Backref(); + final String backrefName = '_' + foreignJoinColumns.getPropertyName() + + '_' + foreignJoinColumns.getColumns()[0].getLogicalColumnName() + + "Backref"; + backref.setName( backrefName ); + backref.setUpdateable( false); + backref.setSelectable( false ); + backref.setCollectionRole( collection.getRole() ); + backref.setEntityName( collection.getOwner().getEntityName() ); + backref.setValue( collection.getKey() ); + referenced.addProperty( backref ); + } + private void handleJpaOrderBy(Collection collection, PersistentClass associatedClass) { if ( jpaOrderBy != null ) { final String orderByFragment = buildOrderByClauseFromHql( jpaOrderBy.value(), associatedClass ); @@ -1578,24 +1590,24 @@ private void handleJpaOrderBy(Collection collection, PersistentClass associatedC } private void bindFilters(boolean hasAssociationTable) { - Filter simpleFilter = property.getAnnotation( Filter.class ); + final Filter simpleFilter = property.getAnnotation( Filter.class ); //set filtering //test incompatible choices //if ( StringHelper.isNotEmpty( where ) ) collection.setWhere( where ); if ( simpleFilter != null ) { addFilter( hasAssociationTable, simpleFilter ); } - Filters filters = getOverridableAnnotation( property, Filters.class, buildingContext ); + final Filters filters = getOverridableAnnotation( property, Filters.class, buildingContext ); if ( filters != null ) { for ( Filter filter : filters.value() ) { addFilter( hasAssociationTable, filter ); } } - FilterJoinTable simpleFilterJoinTable = property.getAnnotation( FilterJoinTable.class ); + final FilterJoinTable simpleFilterJoinTable = property.getAnnotation( FilterJoinTable.class ); if ( simpleFilterJoinTable != null ) { addFilter( hasAssociationTable, simpleFilterJoinTable ); } - FilterJoinTables filterJoinTables = property.getAnnotation( FilterJoinTables.class ); + final FilterJoinTables filterJoinTables = property.getAnnotation( FilterJoinTables.class ); if ( filterJoinTables != null ) { for ( FilterJoinTable filter : filterJoinTables.value() ) { addFilter( hasAssociationTable, filter ); @@ -1821,8 +1833,8 @@ else if ( orderByFragment.equalsIgnoreCase( "desc" ) ) { } private DependantValue buildCollectionKey( - Collection collValue, - AnnotatedJoinColumn[] joinColumns, + Collection collection, + AnnotatedJoinColumns joinColumns, boolean cascadeDeleteEnabled, boolean noConstraintByDefault, XProperty property, @@ -1831,21 +1843,23 @@ private DependantValue buildCollectionKey( // give a chance to override the referenced property name // has to do that here because the referencedProperty creation happens in a FKSecondPass for ManyToOne yuk! - overrideReferencedPropertyName( collValue, joinColumns, buildingContext ); + overrideReferencedPropertyName( collection, joinColumns, buildingContext ); - String propRef = collValue.getReferencedPropertyName(); + final String referencedPropertyName = collection.getReferencedPropertyName(); //binding key reference using column - KeyValue keyVal = propRef == null - ? collValue.getOwner().getIdentifier() - : (KeyValue) collValue.getOwner().getReferencedProperty(propRef).getValue(); + final PersistentClass owner = collection.getOwner(); + final KeyValue keyValue = referencedPropertyName == null + ? owner.getIdentifier() + : (KeyValue) owner.getReferencedProperty( referencedPropertyName ).getValue(); - DependantValue key = new DependantValue( buildingContext, collValue.getCollectionTable(), keyVal ); + final DependantValue key = new DependantValue( buildingContext, collection.getCollectionTable(), keyValue ); key.setTypeName( null ); - checkPropertyConsistency( joinColumns, collValue.getOwnerEntityName() ); - key.setNullable( joinColumns.length == 0 || joinColumns[0].isNullable() ); - key.setUpdateable( joinColumns.length == 0 || joinColumns[0].isUpdatable() ); + final AnnotatedJoinColumn[] columns = joinColumns.getColumns(); + checkPropertyConsistency( columns, collection.getOwnerEntityName() ); + key.setNullable( columns.length == 0 || columns[0].isNullable() ); + key.setUpdateable( columns.length == 0 || columns[0].isUpdatable() ); key.setCascadeDeleteEnabled( cascadeDeleteEnabled ); - collValue.setKey( key ); + collection.setKey( key ); if ( property != null ) { final org.hibernate.annotations.ForeignKey fk = property.getAnnotation( org.hibernate.annotations.ForeignKey.class ); @@ -1855,17 +1869,18 @@ private DependantValue buildCollectionKey( else { final CollectionTable collectionTableAnn = property.getAnnotation( CollectionTable.class ); if ( collectionTableAnn != null ) { - if ( collectionTableAnn.foreignKey().value() == ConstraintMode.NO_CONSTRAINT - || collectionTableAnn.foreignKey().value() == ConstraintMode.PROVIDER_DEFAULT && noConstraintByDefault ) { + final ForeignKey foreignKey = collectionTableAnn.foreignKey(); + if ( foreignKey.value() == ConstraintMode.NO_CONSTRAINT + || foreignKey.value() == ConstraintMode.PROVIDER_DEFAULT && noConstraintByDefault ) { key.disableForeignKey(); } else { - key.setForeignKeyName( nullIfEmpty( collectionTableAnn.foreignKey().name() ) ); - key.setForeignKeyDefinition( nullIfEmpty( collectionTableAnn.foreignKey().foreignKeyDefinition() ) ); - if ( key.getForeignKeyName() == null && - key.getForeignKeyDefinition() == null && - collectionTableAnn.joinColumns().length == 1 ) { - JoinColumn joinColumn = collectionTableAnn.joinColumns()[0]; + key.setForeignKeyName( nullIfEmpty( foreignKey.name() ) ); + key.setForeignKeyDefinition( nullIfEmpty( foreignKey.foreignKeyDefinition() ) ); + if ( key.getForeignKeyName() == null + && key.getForeignKeyDefinition() == null + && collectionTableAnn.joinColumns().length == 1 ) { + final JoinColumn joinColumn = collectionTableAnn.joinColumns()[0]; key.setForeignKeyName( nullIfEmpty( joinColumn.foreignKey().name() ) ); key.setForeignKeyDefinition( nullIfEmpty( joinColumn.foreignKey().foreignKeyDefinition() ) ); } @@ -1874,9 +1889,10 @@ private DependantValue buildCollectionKey( else { final JoinTable joinTableAnn = property.getAnnotation( JoinTable.class ); if ( joinTableAnn != null ) { - String foreignKeyName = joinTableAnn.foreignKey().name(); - String foreignKeyDefinition = joinTableAnn.foreignKey().foreignKeyDefinition(); - ConstraintMode foreignKeyValue = joinTableAnn.foreignKey().value(); + final ForeignKey foreignKey = joinTableAnn.foreignKey(); + String foreignKeyName = foreignKey.name(); + String foreignKeyDefinition = foreignKey.foreignKeyDefinition(); + ConstraintMode foreignKeyValue = foreignKey.value(); if ( joinTableAnn.joinColumns().length != 0 ) { final JoinColumn joinColumnAnn = joinTableAnn.joinColumns()[0]; if ( foreignKeyName != null && foreignKeyName.isEmpty() ) { @@ -1897,22 +1913,16 @@ private DependantValue buildCollectionKey( } } else { - final String propertyPath = qualify(propertyHolder.getPath(), property.getName()); + final String propertyPath = qualify( propertyHolder.getPath(), property.getName() ); final ForeignKey foreignKey = propertyHolder.getOverriddenForeignKey( propertyPath ); if ( foreignKey != null ) { - if ( foreignKey.value() == ConstraintMode.NO_CONSTRAINT - || foreignKey.value() == ConstraintMode.PROVIDER_DEFAULT && noConstraintByDefault ) { - key.disableForeignKey(); - } - else { - key.setForeignKeyName( nullIfEmpty( foreignKey.name() ) ); - key.setForeignKeyDefinition( nullIfEmpty( foreignKey.foreignKeyDefinition() ) ); - } + handleForeignKeyConstraint( noConstraintByDefault, key, foreignKey ); } else { final OneToMany oneToManyAnn = property.getAnnotation( OneToMany.class ); final OnDelete onDeleteAnn = property.getAnnotation( OnDelete.class ); - if ( oneToManyAnn != null && !oneToManyAnn.mappedBy().isEmpty() + if ( oneToManyAnn != null + && !oneToManyAnn.mappedBy().isEmpty() && ( onDeleteAnn == null || onDeleteAnn.action() != OnDeleteAction.CASCADE ) ) { // foreign key should be up to @ManyToOne side // @OnDelete generate "on delete cascade" foreign key @@ -1921,14 +1931,7 @@ private DependantValue buildCollectionKey( else { final JoinColumn joinColumnAnn = property.getAnnotation( JoinColumn.class ); if ( joinColumnAnn != null ) { - if ( joinColumnAnn.foreignKey().value() == ConstraintMode.NO_CONSTRAINT - || joinColumnAnn.foreignKey().value() == ConstraintMode.PROVIDER_DEFAULT && noConstraintByDefault ) { - key.disableForeignKey(); - } - else { - key.setForeignKeyName( nullIfEmpty( joinColumnAnn.foreignKey().name() ) ); - key.setForeignKeyDefinition( nullIfEmpty( joinColumnAnn.foreignKey().foreignKeyDefinition() ) ); - } + handleForeignKeyConstraint( noConstraintByDefault, key, joinColumnAnn.foreignKey() ); } } } @@ -1940,22 +1943,31 @@ private DependantValue buildCollectionKey( return key; } + private static void handleForeignKeyConstraint(boolean noConstraintByDefault, DependantValue key, ForeignKey foreignKey) { + final ConstraintMode constraintMode = foreignKey.value(); + if ( constraintMode == ConstraintMode.NO_CONSTRAINT + || constraintMode == ConstraintMode.PROVIDER_DEFAULT && noConstraintByDefault) { + key.disableForeignKey(); + } + else { + key.setForeignKeyName( nullIfEmpty( foreignKey.name() ) ); + key.setForeignKeyDefinition( nullIfEmpty( foreignKey.foreignKeyDefinition() ) ); + } + } + private void overrideReferencedPropertyName( Collection collection, - AnnotatedJoinColumn[] joinColumns, + AnnotatedJoinColumns joinColumns, MetadataBuildingContext context) { - if ( joinColumns.length > 0 ) { - AnnotatedJoinColumn joinColumn = joinColumns[0]; - if ( hasMappedBy() ) { - final String entityName = joinColumn.getManyToManyOwnerSideEntityName() != null - ? "inverse__" + joinColumn.getManyToManyOwnerSideEntityName() - : joinColumn.getPropertyHolder().getEntityName(); - final InFlightMetadataCollector collector = context.getMetadataCollector(); - final String referencedProperty = collector.getPropertyReferencedAssociation( entityName, mappedBy ); - if ( referencedProperty != null ) { - collection.setReferencedPropertyName( referencedProperty ); - collector.addPropertyReference( collection.getOwnerEntityName(), referencedProperty ); - } + if ( hasMappedBy() && joinColumns.getColumns().length > 0 ) { + final String entityName = joinColumns.getManyToManyOwnerSideEntityName() != null + ? "inverse__" + joinColumns.getManyToManyOwnerSideEntityName() + : joinColumns.getPropertyHolder().getEntityName(); + final InFlightMetadataCollector collector = context.getMetadataCollector(); + final String referencedProperty = collector.getPropertyReferencedAssociation( entityName, mappedBy ); + if ( referencedProperty != null ) { + collection.setReferencedPropertyName( referencedProperty ); + collector.addPropertyReference( collection.getOwnerEntityName(), referencedProperty ); } } } @@ -2195,15 +2207,15 @@ else if ( owner.getIdentifierMapper() != null && owner.getIdentifierMapper().get } private ManyToOne handleCollectionOfEntities( - Collection collValue, + Collection collection, XClass elementType, NotFoundAction notFoundAction, XProperty property, MetadataBuildingContext buildingContext, PersistentClass collectionEntity, String hqlOrderBy) { - ManyToOne element = new ManyToOne( buildingContext, collValue.getCollectionTable() ); - collValue.setElement( element ); + ManyToOne element = new ManyToOne( buildingContext, collection.getCollectionTable() ); + collection.setElement( element ); element.setReferencedEntityName( elementType.getName() ); //element.setFetchMode( fetchMode ); //element.setLazy( fetchMode != FetchMode.JOIN ); @@ -2213,7 +2225,7 @@ private ManyToOne handleCollectionOfEntities( element.setNotFoundAction( notFoundAction ); // as per 11.1.38 of JPA 2.0 spec, default to primary key if no column is specified by @OrderBy. if ( hqlOrderBy != null ) { - collValue.setManyToManyOrdering( buildOrderByClauseFromHql( hqlOrderBy, collectionEntity ) ); + collection.setManyToManyOrdering( buildOrderByClauseFromHql( hqlOrderBy, collectionEntity ) ); } final org.hibernate.annotations.ForeignKey fk = property.getAnnotation( org.hibernate.annotations.ForeignKey.class ); @@ -2247,8 +2259,8 @@ private ManyToOne handleCollectionOfEntities( } private void handleManyToAny( - Collection collValue, - AnnotatedJoinColumn[] inverseJoinColumns, + Collection collection, + AnnotatedJoinColumns inverseJoinColumns, boolean cascadeDeleteEnabled, XProperty property, MetadataBuildingContext buildingContext) { @@ -2266,8 +2278,8 @@ private void handleManyToAny( final Formula discriminatorFormulaAnn = getOverridableAnnotation( prop, Formula.class, buildingContext); //override the table - for (AnnotatedColumn column : inverseJoinColumns) { - column.setTable( collValue.getCollectionTable() ); + for ( AnnotatedColumn column : inverseJoinColumns.getColumns() ) { + column.setTable( collection.getCollectionTable() ); } ManyToAny anyAnn = property.getAnnotation( ManyToAny.class ); @@ -2284,33 +2296,33 @@ private void handleManyToAny( true, buildingContext ); - collValue.setElement( any ); + collection.setElement( any ); } private PropertyData getSpecialMembers(XClass elementClass) { if ( isMap() ) { //"value" is the JPA 2 prefix for map values (used to be "element") if ( isHibernateExtensionMapping() ) { - return new PropertyPreloadedData( AccessType.PROPERTY, "element", elementClass); + return new PropertyPreloadedData( AccessType.PROPERTY, "element", elementClass ); } else { - return new PropertyPreloadedData( AccessType.PROPERTY, "value", elementClass); + return new PropertyPreloadedData( AccessType.PROPERTY, "value", elementClass ); } } else { if ( isHibernateExtensionMapping() ) { - return new PropertyPreloadedData( AccessType.PROPERTY, "element", elementClass); + return new PropertyPreloadedData( AccessType.PROPERTY, "element", elementClass ); } else { //"collection&&element" is not a valid property name => placeholder - return new PropertyPreloadedData( AccessType.PROPERTY, "collection&&element", elementClass); + return new PropertyPreloadedData( AccessType.PROPERTY, "collection&&element", elementClass ); } } } private void handleOwnedManyToMany( Collection collection, - AnnotatedJoinColumn[] joinColumns, + AnnotatedJoinColumns joinColumns, TableBinder associationTableBinder, XProperty property, MetadataBuildingContext context, @@ -2320,15 +2332,11 @@ private void handleOwnedManyToMany( //FIXME NamingStrategy final InFlightMetadataCollector collector = context.getMetadataCollector(); final PersistentClass owner = collection.getOwner(); - for ( AnnotatedJoinColumn column : joinColumns ) { - column.setMappedBy( - owner.getEntityName(), - collector.getLogicalTableName( owner.getTable() ), - collector.getFromMappedBy( owner.getEntityName(), column.getPropertyName() ) - ); -// String header = ( mappedByProperty == null ) ? mappings.getLogicalTableName( ownerTable ) : mappedByProperty; -// column.setDefaultColumnHeader( header ); - } + joinColumns.setMappedBy( + owner.getEntityName(), + collector.getLogicalTableName( owner.getTable() ), + collector.getFromMappedBy( owner.getEntityName(), joinColumns.getPropertyName() ) + ); if ( isEmpty( associationTableBinder.getName() ) ) { //default value associationTableBinder.setDefaultName( @@ -2340,7 +2348,7 @@ private void handleOwnedManyToMany( collectionEntity != null ? collectionEntity.getEntityName() : null, collectionEntity != null ? collectionEntity.getJpaEntityName() : null, collectionEntity != null ? collector.getLogicalTableName( collectionEntity.getTable() ) : null, - joinColumns[0].getPropertyName() + joinColumns.getPropertyName() ); } associationTableBinder.setJPA2ElementCollection( @@ -2350,8 +2358,8 @@ private void handleOwnedManyToMany( } private void handleUnownedManyToMany( - Collection collValue, - AnnotatedJoinColumn[] joinColumns, + Collection collection, + AnnotatedJoinColumns joinColumns, XClass elementType, PersistentClass collectionEntity, boolean isCollectionOfEntities) { @@ -2361,7 +2369,9 @@ private void handleUnownedManyToMany( + "' targets the type '" + elementType.getName() + "' which is not an '@Entity' type" ); } - Property otherSideProperty; + joinColumns.setManyToManyOwnerSideEntityName( collectionEntity.getEntityName() ); + + final Property otherSideProperty; try { otherSideProperty = collectionEntity.getRecursiveProperty( mappedBy ); } @@ -2370,17 +2380,13 @@ private void handleUnownedManyToMany( "is 'mappedBy' a property named '" + mappedBy + "' which does not exist in the target entity '" + elementType.getName() + "'" ); } - Table table = otherSideProperty.getValue() instanceof Collection - ? ( (Collection) otherSideProperty.getValue() ).getCollectionTable() - : otherSideProperty.getValue().getTable(); - //this is a collection on the other side - //This is a ToOne with a @JoinTable or a regular property - collValue.setCollectionTable( table ); - String entityName = collectionEntity.getEntityName(); - for (AnnotatedJoinColumn column : joinColumns) { - //column.setDefaultColumnHeader( joinColumns[0].getMappedBy() ); //seems not to be used, make sense - column.setManyToManyOwnerSideEntityName( entityName ); - } + final Value otherSidePropertyValue = otherSideProperty.getValue(); + final Table table = otherSidePropertyValue instanceof Collection + // this is a collection on the other side + ? ( (Collection) otherSidePropertyValue ).getCollectionTable() + // this is a ToOne with a @JoinTable or a regular property + : otherSidePropertyValue.getTable(); + collection.setCollectionTable( table ); } private void detectManyToManyProblems( @@ -2402,7 +2408,7 @@ else if (isManyToAny) { } } else { - JoinTable joinTableAnn = parentPropertyHolder.getJoinTable( property ); + final JoinTable joinTableAnn = parentPropertyHolder.getJoinTable( property ); if ( joinTableAnn != null && joinTableAnn.inverseJoinColumns().length > 0 ) { throw new AnnotationException( "Association '" + safeCollectionRole() + " has a '@JoinTable' with 'inverseJoinColumns' and targets the type '" @@ -2479,7 +2485,7 @@ private static void checkFilterConditions(Collection collection) { private void bindCollectionSecondPass( PersistentClass targetEntity, - AnnotatedJoinColumn[] joinColumns, + AnnotatedJoinColumns joinColumns, boolean cascadeDeleteEnabled, MetadataBuildingContext context) { @@ -2505,9 +2511,8 @@ private void bindCollectionSecondPass( context ); - //TODO: this is really warty - if ( property.isAnnotationPresent( ElementCollection.class ) && joinColumns.length > 0 ) { - joinColumns[0].setJPA2ElementCollection( true ); + if ( property.isAnnotationPresent( ElementCollection.class ) ) { + joinColumns.setElementCollection( true ); } TableBinder.bindForeignKey( @@ -2529,7 +2534,6 @@ String safeCollectionRole() { return propertyHolder != null ? propertyHolder.getEntityName() + "." + propertyName : ""; } - /** * Bind the inverse foreign key of a {@link ManyToMany}, that is, the columns * specified by {@code @JoinTable(inverseJoinColumns=...)}, which are the @@ -2539,15 +2543,16 @@ String safeCollectionRole() { */ public void bindManyToManyInverseForeignKey( PersistentClass targetEntity, - AnnotatedJoinColumn[] columns, + AnnotatedJoinColumns joinColumns, SimpleValue value, boolean unique, MetadataBuildingContext context) { if ( hasMappedBy() ) { final Property property = targetEntity.getRecursiveProperty( mappedBy ); final List mappedByColumns = mappedByColumns( targetEntity, property ); + final AnnotatedJoinColumn firstColumn = joinColumns.getColumns()[0]; for ( Selectable selectable: mappedByColumns ) { - columns[0].linkValueUsingAColumnCopy( (Column) selectable, value ); + firstColumn.linkValueUsingAColumnCopy( (Column) selectable, value ); } final String referencedPropertyName = context.getMetadataCollector() .getPropertyReferencedAssociation( targetEntity.getEntityName(), mappedBy ); @@ -2562,7 +2567,7 @@ public void bindManyToManyInverseForeignKey( } else { createSyntheticPropertyReference( - columns, + joinColumns, targetEntity, collection.getOwner(), value, @@ -2576,7 +2581,7 @@ public void bindManyToManyInverseForeignKey( TableBinder.bindForeignKey( targetEntity, collection.getOwner(), - columns, + joinColumns, value, unique, context @@ -2604,7 +2609,7 @@ private static List mappedByColumns(PersistentClass referencedEntity } } - public void setFkJoinColumns(AnnotatedJoinColumn[] annotatedJoinColumns) { + public void setFkJoinColumns(AnnotatedJoinColumns annotatedJoinColumns) { this.foreignJoinColumns = annotatedJoinColumns; } @@ -2636,7 +2641,7 @@ public void setMapKeyColumns(AnnotatedColumn[] mapKeyColumns) { this.mapKeyColumns = mapKeyColumns; } - public void setMapKeyManyToManyColumns(AnnotatedJoinColumn[] mapJoinColumns) { + public void setMapKeyManyToManyColumns(AnnotatedJoinColumns mapJoinColumns) { this.mapKeyManyToManyColumns = mapJoinColumns; } @@ -2644,7 +2649,7 @@ public void setLocalGenerators(Map localG this.localGenerators = localGenerators; } - private void logOneToManySeconPass() { + private void logOneToManySecondPass() { if ( LOG.isDebugEnabled() ) { LOG.debugf( "Binding a OneToMany: %s through a foreign key", safeCollectionRole() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/EntityBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/EntityBinder.java index 9afd464c89..4fe6f5b2bb 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/EntityBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/EntityBinder.java @@ -90,6 +90,7 @@ import org.hibernate.cfg.AccessType; import org.hibernate.cfg.AnnotatedClassType; import org.hibernate.cfg.AnnotatedDiscriminatorColumn; +import org.hibernate.cfg.AnnotatedJoinColumns; import org.hibernate.cfg.AnnotationBinder; import org.hibernate.cfg.AnnotatedJoinColumn; import org.hibernate.cfg.AvailableSettings; @@ -217,7 +218,7 @@ public static void bindEntityClass( final PersistentClass persistentClass = makePersistentClass( inheritanceState, superEntity, context); final EntityBinder entityBinder = new EntityBinder( clazzToProcess, persistentClass, context ); - final AnnotatedJoinColumn[] inheritanceJoinedColumns = + final AnnotatedJoinColumns inheritanceJoinedColumns = makeInheritanceJoinColumns( clazzToProcess, context, inheritanceState, superEntity ); final AnnotatedDiscriminatorColumn discriminatorColumn = handleDiscriminatorColumn( clazzToProcess, context, inheritanceState, entityBinder ); @@ -673,7 +674,7 @@ private static void handleInheritance( InheritanceState inheritanceState, PersistentClass persistentClass, EntityBinder entityBinder, - AnnotatedJoinColumn[] inheritanceJoinedColumns, + AnnotatedJoinColumns inheritanceJoinedColumns, AnnotatedDiscriminatorColumn discriminatorColumn, PropertyHolder propertyHolder) { @@ -984,7 +985,7 @@ private static PersistentClass makePersistentClass( } } - private static AnnotatedJoinColumn[] makeInheritanceJoinColumns( + private static AnnotatedJoinColumns makeInheritanceJoinColumns( XClass clazzToProcess, MetadataBuildingContext context, InheritanceState inheritanceState, @@ -995,7 +996,7 @@ private static AnnotatedJoinColumn[] makeInheritanceJoinColumns( && InheritanceType.JOINED == inheritanceState.getType(); if ( hasJoinedColumns ) { //@Inheritance(JOINED) subclass need to link back to the super entity - PrimaryKeyJoinColumns jcsAnn = clazzToProcess.getAnnotation( PrimaryKeyJoinColumns.class ); + final PrimaryKeyJoinColumns jcsAnn = clazzToProcess.getAnnotation( PrimaryKeyJoinColumns.class ); boolean explicitInheritanceJoinedColumns = jcsAnn != null && jcsAnn.value().length != 0; if ( explicitInheritanceJoinedColumns ) { int nbrOfInhJoinedColumns = jcsAnn.value().length; @@ -1014,7 +1015,7 @@ private static AnnotatedJoinColumn[] makeInheritanceJoinColumns( } } else { - PrimaryKeyJoinColumn jcAnn = clazzToProcess.getAnnotation( PrimaryKeyJoinColumn.class ); + final PrimaryKeyJoinColumn jcAnn = clazzToProcess.getAnnotation( PrimaryKeyJoinColumn.class ); inheritanceJoinedColumns = new AnnotatedJoinColumn[1]; inheritanceJoinedColumns[0] = buildJoinColumn( jcAnn, @@ -1033,7 +1034,15 @@ private static AnnotatedJoinColumn[] makeInheritanceJoinColumns( LOG.invalidPrimaryKeyJoinColumnAnnotation( clazzToProcess.getName() ); } } - return inheritanceJoinedColumns; + if ( inheritanceJoinedColumns == null ) { + return null; + } + else { + final AnnotatedJoinColumns joinColumns = new AnnotatedJoinColumns(); + joinColumns.setBuildingContext( context ); + joinColumns.setColumns( inheritanceJoinedColumns ); + return joinColumns; + } } private static PersistentClass getSuperEntity( @@ -1746,24 +1755,23 @@ public void finalSecondaryTableFromAnnotationBinding(PropertyHolder propertyHold } private void createPrimaryColumnsToSecondaryTable(Object column, PropertyHolder propertyHolder, Join join) { - final AnnotatedJoinColumn[] annotatedJoinColumns; final PrimaryKeyJoinColumn[] pkColumnsAnn = column instanceof PrimaryKeyJoinColumn[] ? (PrimaryKeyJoinColumn[]) column : null; final JoinColumn[] joinColumnsAnn = column instanceof JoinColumn[] ? (JoinColumn[]) column : null; - annotatedJoinColumns = pkColumnsAnn == null && joinColumnsAnn == null + final AnnotatedJoinColumns annotatedJoinColumns = pkColumnsAnn == null && joinColumnsAnn == null ? createDefaultJoinColumn( propertyHolder ) : createJoinColumns( propertyHolder, pkColumnsAnn, joinColumnsAnn ); - for (AnnotatedJoinColumn joinColumn : annotatedJoinColumns) { + for ( AnnotatedJoinColumn joinColumn : annotatedJoinColumns.getColumns() ) { joinColumn.forceNotNull(); } bindJoinToPersistentClass( join, annotatedJoinColumns, context ); } - private AnnotatedJoinColumn[] createDefaultJoinColumn(PropertyHolder propertyHolder) { + private AnnotatedJoinColumns createDefaultJoinColumn(PropertyHolder propertyHolder) { final AnnotatedJoinColumn[] annotatedJoinColumns = new AnnotatedJoinColumn[1]; annotatedJoinColumns[0] = buildJoinColumn( null, @@ -1773,10 +1781,14 @@ private AnnotatedJoinColumn[] createDefaultJoinColumn(PropertyHolder propertyHol propertyHolder, context ); - return annotatedJoinColumns; + final AnnotatedJoinColumns joinColumns = new AnnotatedJoinColumns(); + joinColumns.setBuildingContext( context ); + joinColumns.setPropertyHolder( propertyHolder ); + joinColumns.setColumns( annotatedJoinColumns ); + return joinColumns; } - private AnnotatedJoinColumn[] createJoinColumns( + private AnnotatedJoinColumns createJoinColumns( PropertyHolder propertyHolder, PrimaryKeyJoinColumn[] pkColumnsAnn, JoinColumn[] joinColumnsAnn) { @@ -1787,8 +1799,8 @@ private AnnotatedJoinColumn[] createJoinColumns( else { final AnnotatedJoinColumn[] annotatedJoinColumns = new AnnotatedJoinColumn[joinColumnCount]; for (int colIndex = 0; colIndex < joinColumnCount; colIndex++) { - PrimaryKeyJoinColumn pkJoinAnn = pkColumnsAnn != null ? pkColumnsAnn[colIndex] : null; - JoinColumn joinAnn = joinColumnsAnn != null ? joinColumnsAnn[colIndex] : null; + final PrimaryKeyJoinColumn pkJoinAnn = pkColumnsAnn != null ? pkColumnsAnn[colIndex] : null; + final JoinColumn joinAnn = joinColumnsAnn != null ? joinColumnsAnn[colIndex] : null; annotatedJoinColumns[colIndex] = buildJoinColumn( pkJoinAnn, joinAnn, @@ -1798,11 +1810,15 @@ private AnnotatedJoinColumn[] createJoinColumns( context ); } - return annotatedJoinColumns; + final AnnotatedJoinColumns joinColumns = new AnnotatedJoinColumns(); + joinColumns.setBuildingContext( context ); + joinColumns.setPropertyHolder( propertyHolder ); + joinColumns.setColumns( annotatedJoinColumns ); + return joinColumns; } } - private void bindJoinToPersistentClass(Join join, AnnotatedJoinColumn[] joinColumns, MetadataBuildingContext context) { + private void bindJoinToPersistentClass(Join join, AnnotatedJoinColumns joinColumns, MetadataBuildingContext context) { DependantValue key = new DependantValue( context, join.getTable(), persistentClass.getIdentifier() ); join.setKey( key ); setForeignKeyNameIfDefined( join ); @@ -1822,7 +1838,7 @@ private void setForeignKeyNameIfDefined(Join join) { key.setForeignKeyName( matchingTable.foreignKey().name() ); } else { - SecondaryTable jpaSecondaryTable = findMatchingSecondaryTable( join ); + final SecondaryTable jpaSecondaryTable = findMatchingSecondaryTable( join ); if ( jpaSecondaryTable != null ) { final boolean noConstraintByDefault = context.getBuildingOptions().isNoConstraintByDefault(); if ( jpaSecondaryTable.foreignKey().value() == ConstraintMode.NO_CONSTRAINT @@ -1839,11 +1855,11 @@ private void setForeignKeyNameIfDefined(Join join) { private SecondaryTable findMatchingSecondaryTable(Join join) { final String nameToMatch = join.getTable().getQuotedName(); - SecondaryTable secondaryTable = annotatedClass.getAnnotation( SecondaryTable.class ); + final SecondaryTable secondaryTable = annotatedClass.getAnnotation( SecondaryTable.class ); if ( secondaryTable != null && nameToMatch.equals( secondaryTable.name() ) ) { return secondaryTable; } - SecondaryTables secondaryTables = annotatedClass.getAnnotation( SecondaryTables.class ); + final SecondaryTables secondaryTables = annotatedClass.getAnnotation( SecondaryTables.class ); if ( secondaryTables != null ) { for ( SecondaryTable secondaryTablesEntry : secondaryTables.value() ) { if ( secondaryTablesEntry != null && nameToMatch.equals( secondaryTablesEntry.name() ) ) { @@ -1856,7 +1872,7 @@ private SecondaryTable findMatchingSecondaryTable(Join join) { private org.hibernate.annotations.Table findMatchingComplementaryTableAnnotation(Join join) { final String tableName = join.getTable().getQuotedName(); - org.hibernate.annotations.Table table = annotatedClass.getAnnotation( org.hibernate.annotations.Table.class ); + final org.hibernate.annotations.Table table = annotatedClass.getAnnotation( org.hibernate.annotations.Table.class ); if ( table != null && tableName.equals( table.appliesTo() ) ) { return table; } @@ -1875,7 +1891,7 @@ private org.hibernate.annotations.Table findMatchingComplementaryTableAnnotation private SecondaryRow findMatchingComplementarySecondaryRowAnnotation(Join join) { final String tableName = join.getTable().getQuotedName(); - SecondaryRow row = annotatedClass.getAnnotation( SecondaryRow.class ); + final SecondaryRow row = annotatedClass.getAnnotation( SecondaryRow.class ); if ( row != null && ( row.table().isEmpty() || tableName.equals( row.table() ) ) ) { return row; } @@ -1907,7 +1923,7 @@ private Join addJoin( PropertyHolder propertyHolder, boolean noDelayInPkColumnCreation) { // A non-null propertyHolder means than we process the Pk creation without delay - Join join = new Join(); + final Join join = new Join(); join.setPersistentClass( persistentClass ); final String schema; @@ -1979,8 +1995,8 @@ else if ( joinTable != null ) { //Has to do the work later because it needs persistentClass id! LOG.debugf( "Adding secondary table to entity %s -> %s", persistentClass.getEntityName(), join.getTable().getName() ); - SecondaryRow matchingRow = findMatchingComplementarySecondaryRowAnnotation( join ); - org.hibernate.annotations.Table matchingTable = findMatchingComplementaryTableAnnotation( join ); + final SecondaryRow matchingRow = findMatchingComplementarySecondaryRowAnnotation( join ); + final org.hibernate.annotations.Table matchingTable = findMatchingComplementaryTableAnnotation( join ); if ( matchingRow != null ) { join.setInverse( !matchingRow.owned() ); join.setOptional( matchingRow.optional() ); @@ -2042,7 +2058,7 @@ public java.util.Map getSecondaryTables() { } public static String getCacheConcurrencyStrategy(CacheConcurrencyStrategy strategy) { - org.hibernate.cache.spi.access.AccessType accessType = strategy.toAccessType(); + final org.hibernate.cache.spi.access.AccessType accessType = strategy.toAccessType(); return accessType == null ? null : accessType.getExternalName(); } @@ -2100,9 +2116,10 @@ public void processComplementaryTableDefinitions(org.hibernate.annotations.Table } public void processComplementaryTableDefinitions(Tables tables) { - if ( tables == null ) return; - for (org.hibernate.annotations.Table table : tables.value()) { - processComplementaryTableDefinitions( table ); + if ( tables != null ) { + for ( org.hibernate.annotations.Table table : tables.value() ) { + processComplementaryTableDefinitions( table ); + } } } @@ -2128,7 +2145,7 @@ public AccessType getPropertyAccessor(XAnnotatedElement element) { public AccessType getExplicitAccessType(XAnnotatedElement element) { AccessType accessType = null; - Access access = element.getAnnotation( Access.class ); + final Access access = element.getAnnotation( Access.class ); if ( access != null ) { accessType = AccessType.getAccessStrategy( access.value() ); } @@ -2160,14 +2177,14 @@ public static void bindFiltersAndFilterDefs( } private static void bindFilters(XAnnotatedElement annotatedElement, EntityBinder entityBinder, MetadataBuildingContext context) { - Filters filtersAnn = getOverridableAnnotation( annotatedElement, Filters.class, context ); + final Filters filtersAnn = getOverridableAnnotation( annotatedElement, Filters.class, context ); if ( filtersAnn != null ) { for ( Filter filter : filtersAnn.value() ) { entityBinder.addFilter(filter); } } - Filter filterAnn = annotatedElement.getAnnotation( Filter.class ); + final Filter filterAnn = annotatedElement.getAnnotation( Filter.class ); if ( filterAnn != null ) { entityBinder.addFilter(filterAnn); } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java index f46f107e96..3c41e79ee7 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java @@ -22,6 +22,7 @@ import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.cfg.AccessType; import org.hibernate.cfg.AnnotatedClassType; +import org.hibernate.cfg.AnnotatedJoinColumns; import org.hibernate.cfg.AnnotationBinder; import org.hibernate.cfg.CollectionPropertyHolder; import org.hibernate.cfg.CollectionSecondPass; @@ -102,7 +103,7 @@ public void secondPass(Map persistentClasses) buildingContext, mapKeyColumns, mapKeyManyToManyColumns, - inverseJoinColumns != null ? inverseJoinColumns[0].getPropertyName() : null + inverseJoinColumns != null ? inverseJoinColumns.getPropertyName() : null ); makeOneToManyMapKeyColumnNullableIfNotInProperty( property ); } @@ -159,7 +160,7 @@ private void bindKeyFromAssociationTable( boolean isEmbedded, MetadataBuildingContext buildingContext, AnnotatedColumn[] mapKeyColumns, - AnnotatedJoinColumn[] mapKeyManyToManyColumns, + AnnotatedJoinColumns mapKeyManyToManyColumns, String targetPropertyName) { if ( mapKeyPropertyName != null ) { //this is an EJB3 @MapKey @@ -340,7 +341,7 @@ else if ( owner.getIdentifierMapper() != null && owner.getIdentifierMapper().get //FIXME pass the Index Entity JoinColumns if ( !collection.isOneToMany() ) { //index column should not be null - for (AnnotatedJoinColumn col : mapKeyManyToManyColumns) { + for ( AnnotatedJoinColumn col : mapKeyManyToManyColumns.getColumns() ) { col.forceNotNull(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/TableBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/TableBinder.java index 12381f1d74..68906b763d 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/TableBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/TableBinder.java @@ -24,6 +24,7 @@ import org.hibernate.boot.spi.InFlightMetadataCollector; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.cfg.AnnotatedJoinColumn; +import org.hibernate.cfg.AnnotatedJoinColumns; import org.hibernate.cfg.IndexOrUniqueKeySecondPass; import org.hibernate.cfg.JPAIndexHolder; import org.hibernate.cfg.ObjectNameSource; @@ -533,26 +534,25 @@ public static Table buildAndFillTable( public static void bindForeignKey( PersistentClass referencedEntity, PersistentClass destinationEntity, - AnnotatedJoinColumn[] joinColumns, + AnnotatedJoinColumns joinColumns, SimpleValue value, boolean unique, MetadataBuildingContext buildingContext) { - final AnnotatedJoinColumn firstColumn = joinColumns[0]; - final PersistentClass associatedClass; if ( destinationEntity != null ) { //overridden destination associatedClass = destinationEntity; } else { - final PropertyHolder holder = firstColumn.getPropertyHolder(); + final PropertyHolder holder = joinColumns.getPropertyHolder(); associatedClass = holder == null ? null : holder.getPersistentClass(); } - if ( firstColumn.hasMappedBy() ) { + final AnnotatedJoinColumn firstColumn = joinColumns.getColumns()[0]; + if ( joinColumns.hasMappedBy() ) { // use the columns of the property referenced by mappedBy // copy them and link the copy to the actual value - bindUnownedAssociation( joinColumns, value, associatedClass, firstColumn.getMappedBy() ); + bindUnownedAssociation( joinColumns, value, associatedClass, joinColumns.getMappedBy() ); } else if ( firstColumn.isImplicit() ) { // if columns are implicit, then create the columns based @@ -570,7 +570,7 @@ else if ( firstColumn.isImplicit() ) { private static void bindExplicitColumns( PersistentClass referencedEntity, - AnnotatedJoinColumn[] joinColumns, + AnnotatedJoinColumns joinColumns, SimpleValue value, MetadataBuildingContext buildingContext, PersistentClass associatedClass) { @@ -588,16 +588,16 @@ private static void bindExplicitColumns( private static void bindImplicitPrimaryKeyReference( PersistentClass referencedEntity, - AnnotatedJoinColumn[] joinColumns, + AnnotatedJoinColumns joinColumns, SimpleValue value, PersistentClass associatedClass) { //implicit case, we hope PK and FK columns are in the same order - if ( joinColumns.length != referencedEntity.getIdentifier().getColumnSpan() ) { + if ( joinColumns.getColumns().length != referencedEntity.getIdentifier().getColumnSpan() ) { // TODO: what about secondary tables?? associatedClass is null? throw new AnnotationException( "An association that targets entity '" + referencedEntity.getEntityName() + "' from entity '" + associatedClass.getEntityName() - + "' has " + joinColumns.length + " '@JoinColumn's but the primary key has " + + "' has " + joinColumns.getColumns().length + " '@JoinColumn's but the primary key has " + referencedEntity.getIdentifier().getColumnSpan() + " columns" ); } @@ -614,7 +614,7 @@ private static void bindImplicitPrimaryKeyReference( private static void bindPrimaryKeyReference( PersistentClass referencedEntity, - AnnotatedJoinColumn[] joinColumns, + AnnotatedJoinColumns joinColumns, SimpleValue value, PersistentClass associatedClass, MetadataBuildingContext buildingContext) { @@ -631,7 +631,7 @@ private static void bindPrimaryKeyReference( boolean match = false; // for each PK column, find the associated FK column. final String quotedName = column.getQuotedName( dialect ); - for ( AnnotatedJoinColumn joinColumn : joinColumns ) { + for ( AnnotatedJoinColumn joinColumn : joinColumns.getColumns() ) { final String referencedColumn = buildingContext.getMetadataCollector() .getPhysicalColumnName( referencedEntity.getTable(), joinColumn.getReferencedColumn() ); // in JPA 2 referencedColumnName is case-insensitive @@ -664,14 +664,14 @@ private static void bindPrimaryKeyReference( private static void bindNonPrimaryKeyReference( PersistentClass referencedEntity, - AnnotatedJoinColumn[] joinColumns, + AnnotatedJoinColumns joinColumns, SimpleValue value) { final String referencedPropertyName; if ( value instanceof ToOne ) { referencedPropertyName = ( (ToOne) value).getReferencedPropertyName(); } else if ( value instanceof DependantValue ) { - final String propertyName = joinColumns[0].getPropertyName(); + final String propertyName = joinColumns.getPropertyName(); if ( propertyName != null ) { Collection collection = (Collection) referencedEntity.getRecursiveProperty( propertyName ).getValue(); referencedPropertyName = collection.getReferencedPropertyName(); @@ -698,25 +698,27 @@ else if ( value instanceof DependantValue ) { private static void bindImplicitColumns( PersistentClass referencedEntity, - AnnotatedJoinColumn[] joinColumns, + AnnotatedJoinColumns joinColumns, SimpleValue value) { final List idColumns = referencedEntity instanceof JoinedSubclass ? referencedEntity.getKey().getColumns() : referencedEntity.getIdentifier().getColumns(); for ( Column column: idColumns ) { - joinColumns[0].linkValueUsingDefaultColumnNaming( column, referencedEntity, value); - joinColumns[0].overrideFromReferencedColumnIfNecessary( column ); + final AnnotatedJoinColumn firstColumn = joinColumns.getColumns()[0]; + firstColumn.linkValueUsingDefaultColumnNaming( column, referencedEntity, value); + firstColumn.overrideFromReferencedColumnIfNecessary( column ); } } private static void bindUnownedAssociation( - AnnotatedJoinColumn[] joinColumns, + AnnotatedJoinColumns joinColumns, SimpleValue value, PersistentClass associatedClass, String mappedByProperty) { + final AnnotatedJoinColumn firstColumn = joinColumns.getColumns()[0]; for ( Column column: mappedByColumns( associatedClass, mappedByProperty ) ) { - joinColumns[0].overrideFromReferencedColumnIfNecessary( column ); - joinColumns[0].linkValueUsingAColumnCopy( column, value); + firstColumn.overrideFromReferencedColumnIfNecessary( column ); + firstColumn.linkValueUsingAColumnCopy( column, value); } } @@ -739,19 +741,20 @@ private static List mappedByColumns(PersistentClass associatedClass, Str public static void linkJoinColumnWithValueOverridingNameIfImplicit( PersistentClass referencedEntity, Value value, - AnnotatedJoinColumn[] joinColumns, + AnnotatedJoinColumns joinColumns, SimpleValue simpleValue) { final List valueColumns = value.getColumns(); - for ( int i = 0; i < joinColumns.length; i++ ) { - final AnnotatedJoinColumn joinCol = joinColumns[i]; + final AnnotatedJoinColumn[] columns = joinColumns.getColumns(); + for (int i = 0; i < columns.length; i++ ) { + final AnnotatedJoinColumn joinColumn = columns[i]; final Column synthCol = valueColumns.get(i); - if ( joinCol.isNameDeferred() ) { + if ( joinColumn.isNameDeferred() ) { //this has to be the default value - joinCol.linkValueUsingDefaultColumnNaming( synthCol, referencedEntity, simpleValue ); + joinColumn.linkValueUsingDefaultColumnNaming( synthCol, referencedEntity, simpleValue ); } else { - joinCol.linkWithValue( simpleValue ); - joinCol.overrideFromReferencedColumnIfNecessary( synthCol ); + joinColumn.linkWithValue( simpleValue ); + joinColumn.overrideFromReferencedColumnIfNecessary( synthCol ); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Collection.java b/hibernate-core/src/main/java/org/hibernate/mapping/Collection.java index 2cc3284d86..8dc065acb1 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Collection.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Collection.java @@ -415,7 +415,7 @@ private void checkColumnDuplication(java.util.Set distinctColumns, Value } private void checkColumnDuplication() throws MappingException { - HashSet cols = new HashSet<>(); + final HashSet cols = new HashSet<>(); checkColumnDuplication( cols, getKey() ); if ( isIndexed() ) { checkColumnDuplication( diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/ManyToOne.java b/hibernate-core/src/main/java/org/hibernate/mapping/ManyToOne.java index 194d8141a1..8fa3c2afc1 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/ManyToOne.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/ManyToOne.java @@ -76,10 +76,10 @@ public void createUniqueKey() { } public void createPropertyRefConstraints(Map persistentClasses) { - if (referencedPropertyName!=null) { + if ( referencedPropertyName != null ) { // Ensure properties are sorted before we create a foreign key sortProperties(); - PersistentClass pc = persistentClasses.get(getReferencedEntityName() ); + PersistentClass pc = persistentClasses.get( getReferencedEntityName() ); Property property = pc.getReferencedProperty( getReferencedPropertyName() ); diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java b/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java index 57b081fa61..cd221b2f6e 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java @@ -886,7 +886,7 @@ public void setTypeParameters(Properties parameterMap) { this.typeParameters = parameterMap; } - public void setTypeParameters(Map parameters) { + public void setTypeParameters(Map parameters) { if ( parameters != null ) { Properties properties = new Properties(); properties.putAll( parameters ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/manytomany/defaults/ManyToManyImplicitNamingTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/manytomany/defaults/ManyToManyImplicitNamingTest.java index b2b9f7b882..88681e18c8 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/manytomany/defaults/ManyToManyImplicitNamingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/manytomany/defaults/ManyToManyImplicitNamingTest.java @@ -185,7 +185,7 @@ protected void checkDefaultJoinTablAndJoinColumnNames( ); // The default owner and inverse join columns can only be computed if they have PK with 1 column. assertEquals ( 1, ownerCollection.getOwner().getKey().getColumnSpan() ); - assertEquals( ownerForeignKeyNameExpected, ownerCollection.getKey().getColumnIterator().next().getText() ); + assertEquals( ownerForeignKeyNameExpected, ownerCollection.getKey().getColumns().get(0).getText() ); final EntityType associatedEntityType = (EntityType) ownerCollection.getElement().getType(); final PersistentClass associatedPersistentClass =