diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotatedDiscriminatorColumn.java b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotatedDiscriminatorColumn.java index 97f0470b6b..a4889f69e6 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotatedDiscriminatorColumn.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotatedDiscriminatorColumn.java @@ -46,31 +46,31 @@ public void setDiscriminatorTypeName(String discriminatorTypeName) { public static AnnotatedDiscriminatorColumn buildDiscriminatorColumn( DiscriminatorType type, - DiscriminatorColumn discAnn, - DiscriminatorFormula discFormulaAnn, + DiscriminatorColumn discriminatorColumn, + DiscriminatorFormula discriminatorFormula, MetadataBuildingContext context) { - final AnnotatedDiscriminatorColumn discriminatorColumn = new AnnotatedDiscriminatorColumn(); - discriminatorColumn.setBuildingContext( context ); - if ( discFormulaAnn != null ) { - discriminatorColumn.setImplicit( false ); - discriminatorColumn.setFormula( discFormulaAnn.value() ); + final AnnotatedDiscriminatorColumn column = new AnnotatedDiscriminatorColumn(); + column.setBuildingContext( context ); + if ( discriminatorFormula != null ) { + column.setImplicit( false ); + column.setFormula( discriminatorFormula.value() ); } - else if ( discAnn != null ) { - discriminatorColumn.setImplicit( false ); - if ( !isEmptyAnnotationValue( discAnn.columnDefinition() ) ) { - discriminatorColumn.setSqlType( discAnn.columnDefinition() ); + else if ( discriminatorColumn != null ) { + column.setImplicit( false ); + if ( !isEmptyAnnotationValue( discriminatorColumn.columnDefinition() ) ) { + column.setSqlType( discriminatorColumn.columnDefinition() ); } - if ( !isEmptyAnnotationValue( discAnn.name() ) ) { - discriminatorColumn.setLogicalColumnName( discAnn.name() ); + if ( !isEmptyAnnotationValue( discriminatorColumn.name() ) ) { + column.setLogicalColumnName( discriminatorColumn.name() ); } - discriminatorColumn.setNullable( false ); + column.setNullable( false ); } else { - discriminatorColumn.setImplicit( true ); + column.setImplicit( true ); } - setDiscriminatorType( type, discAnn, discriminatorColumn ); - discriminatorColumn.bind(); - return discriminatorColumn; + setDiscriminatorType( type, discriminatorColumn, column ); + column.bind(); + return column; } private static void setDiscriminatorType( 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 74bc5a51ec..8971a92728 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java @@ -505,7 +505,7 @@ else if ( generatorAnn instanceof GenericGenerator ) { } /** - * Bind a class having JSR175 annotations. Subclasses have to be bound after its parent class. + * Bind an annotated class. A subclass must be bound after its superclass. * * @param clazzToProcess entity to bind as {@code XClass} instance * @param inheritanceStatePerClass Metadata about the inheritance relationships for all mapped classes @@ -1048,10 +1048,9 @@ private static boolean hasIdAnnotation(XAnnotatedElement element) { || element.isAnnotationPresent(EmbeddedId.class); } - /* - * Process annotation of a particular property + /** + * Process annotation of a particular property or field. */ - public static void processElementAnnotations( PropertyHolder propertyHolder, Nullability nullability, 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 19f6d9f4fa..c1227ca2aa 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/BinderHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/BinderHelper.java @@ -164,6 +164,7 @@ public static void createSyntheticPropertyReference( // the entity which declares the association (used for exception message) PersistentClass associatedEntity, Value value, + String propertyName, // true when we do the reverse side of a @ManyToMany boolean inverse, MetadataBuildingContext context) { @@ -174,8 +175,6 @@ public static void createSyntheticPropertyReference( // Property instance final AnnotatedJoinColumn firstColumn = columns[0]; if ( !firstColumn.isImplicit() - // only necessary for owning side of association - && !firstColumn.hasMappedBy() // not necessary for a primary key reference && checkReferencedColumnsType( columns, targetEntity, context ) == NON_PK_REFERENCE ) { @@ -184,29 +183,21 @@ && checkReferencedColumnsType( columns, targetEntity, context ) == NON_PK_REFERE // for a PersistentClass or Join in the hierarchy of // the target entity which has the first column final Object columnOwner = findColumnOwner( targetEntity, firstColumn.getReferencedColumn(), context ); - for ( AnnotatedJoinColumn col: columns ) { - final Object owner = findColumnOwner( targetEntity, col.getReferencedColumn(), context ); - if ( owner == null ) { - throw new AnnotationException( "A '@JoinColumn' for association " - + associationMessage( associatedEntity, columns[0] ) - + " references a column named '" + col.getReferencedColumn() - + "' which is not mapped by the target entity '" - + targetEntity.getEntityName() + "'" ); - } - if ( owner != columnOwner ) { - throw new AnnotationException( "The '@JoinColumn's for association " - + associationMessage( associatedEntity, columns[0] ) - + " reference columns of different tables mapped by the target entity '" - + targetEntity.getEntityName() + "' ('" + col.getReferencedColumn() + - "' belongs to a different table to '" + firstColumn.getReferencedColumn() + "'" ); - } - } + checkColumnInSameTable( columns, 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 // Component if necessary (or reuse the existing // Property that matches exactly) - final Property property = referencedProperty( targetEntity, inverse, columns, columnOwner, properties, context ); + final Property property = referencedProperty( + targetEntity, + associatedEntity, + propertyName, + inverse, + columnOwner, + properties, + context + ); // register the mapping with the InFlightMetadataCollector registerSyntheticProperty( targetEntity, @@ -220,6 +211,41 @@ && checkReferencedColumnsType( columns, targetEntity, context ) == NON_PK_REFERE } } + /** + * All the referenced columns must belong to the same table, that is + * {@link #findColumnOwner(PersistentClass, String, MetadataBuildingContext)} + * must return the same value for all of them. + */ + private static void checkColumnInSameTable( + AnnotatedJoinColumn[] columns, + 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"); + } + final Object owner = findColumnOwner( targetEntity, column.getReferencedColumn(), context ); + if ( owner == null ) { + throw new AnnotationException( "A '@JoinColumn' for association " + + associationMessage(associatedEntity, firstColumn) + + " references a column named '" + column.getReferencedColumn() + + "' which is not mapped by the target entity '" + + targetEntity.getEntityName() + "'" ); + } + if ( owner != columnOwner) { + throw new AnnotationException( "The '@JoinColumn's for association " + + associationMessage(associatedEntity, firstColumn) + + " reference columns of different tables mapped by the target entity '" + + targetEntity.getEntityName() + "' ('" + column.getReferencedColumn() + + "' belongs to a different table to '" + firstColumn.getReferencedColumn() + "'" ); + } + } + } + /** * If the referenced columns correspond to exactly one property * of the primary table of the exact target entity subclass, @@ -232,8 +258,9 @@ && checkReferencedColumnsType( columns, targetEntity, context ) == NON_PK_REFERE */ private static Property referencedProperty( PersistentClass ownerEntity, + PersistentClass associatedEntity, + String propertyName, boolean inverse, - AnnotatedJoinColumn[] columns, Object columnOwner, List properties, MetadataBuildingContext context) { @@ -253,9 +280,7 @@ private static Property referencedProperty( // mapped to the referenced columns. We need to shallow // clone those properties to mark them as non-insertable // and non-updatable - final AnnotatedJoinColumn firstColumn = columns[0]; - final PersistentClass associatedClass = firstColumn.getPropertyHolder().getPersistentClass(); - final String syntheticPropertyName = syntheticPropertyName( firstColumn.getPropertyName(), inverse, associatedClass ); + final String syntheticPropertyName = syntheticPropertyName( propertyName, inverse, associatedEntity ); return makeSyntheticComponentProperty( ownerEntity, columnOwner, context, syntheticPropertyName, properties ); } } @@ -826,7 +851,7 @@ private static IdentifierGeneratorDefinition getIdentifierGenerator( final GeneratedValue generatedValueAnn = idXProperty.getAnnotation( GeneratedValue.class ); if ( generatedValueAnn == null ) { - // this should really never happen, but its easy to protect against it... + // this should really never happen, but it's easy to protect against it... return new IdentifierGeneratorDefinition( "assigned", "assigned" ); } 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 8db1cade08..d4c704fddb 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/ToOneFkSecondPass.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/ToOneFkSecondPass.java @@ -107,6 +107,7 @@ public void doSecondPass(java.util.Map persistentClasse targetEntity, persistentClass, manyToOne, + path, false, buildingContext ); 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 43d19eb291..c320c60c70 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 @@ -94,7 +94,6 @@ import org.hibernate.boot.BootLogging; import org.hibernate.boot.model.IdentifierGeneratorDefinition; import org.hibernate.boot.model.TypeDefinition; -import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.spi.InFlightMetadataCollector; import org.hibernate.boot.spi.InFlightMetadataCollector.CollectionTypeRegistrationDescriptor; import org.hibernate.boot.spi.MetadataBuildingContext; @@ -245,6 +244,9 @@ protected CollectionBinder( this.buildingContext = buildingContext; } + /** + * The first pass at binding a collection. + */ public static void bindCollection( PropertyHolder propertyHolder, Nullability nullability, @@ -1506,8 +1508,10 @@ private boolean hasMappedBy() { return isNotEmpty( mappedBy ); } + /** + * Bind a {@link OneToMany} association. + */ protected void bindOneToManySecondPass(Map persistentClasses) { - if ( property == null ) { throw new AssertionFailure( "null was passed for argument property" ); } @@ -1544,7 +1548,7 @@ protected void bindOneToManySecondPass(Map persistentCl bindFilters( false ); handleWhere( false ); - PersistentClass targetEntity = persistentClasses.get( getElementType().getName() ); + final PersistentClass targetEntity = persistentClasses.get( getElementType().getName() ); bindCollectionSecondPass( targetEntity, foreignJoinColumns, cascadeDeleteEnabled, buildingContext ); if ( !collection.isInverse() && !collection.getKey().isNullable() ) { @@ -1955,14 +1959,16 @@ private void overrideReferencedPropertyName( } } + /** + * Bind a {@link ManyToMany} association or {@link ElementCollection}. + */ private void bindManyToManySecondPass(Map persistentClasses) throws MappingException { - if ( property == null ) { throw new AssertionFailure( "null was passed for argument property" ); } final XClass elementType = getElementType(); - final PersistentClass targetEntity = persistentClasses.get( elementType.getName() ); + final PersistentClass targetEntity = persistentClasses.get( elementType.getName() ); //null if this is an @ElementCollection final String hqlOrderBy = extractHqlOrderBy( jpaOrderBy ); final boolean isCollectionOfEntities = targetEntity != null; @@ -2482,14 +2488,17 @@ private void bindCollectionSecondPass( boolean cascadeDeleteEnabled, MetadataBuildingContext context) { - createSyntheticPropertyReference( - joinColumns, - collection.getOwner(), - collection.getOwner(), - collection, - false, - context - ); + if ( !hasMappedBy() ) { + createSyntheticPropertyReference( + joinColumns, + collection.getOwner(), + collection.getOwner(), + collection, + propertyName, + false, + context + ); + } final DependantValue key = buildCollectionKey( collection, @@ -2527,9 +2536,11 @@ String safeCollectionRole() { /** - * Bind the inverse foreign key of a {@link ManyToMany}. - * If we are in a mappedBy case, read the columns from the associated - * collection element. Otherwise, delegate to the usual algorithm. + * Bind the inverse foreign key of a {@link ManyToMany}, that is, the columns + * specified by {@code @JoinTable(inverseJoinColumns=...)}, which are the + * columns that reference the target entity of the many-to-many association. + * If we are in a {@code mappedBy} case, read the columns from the associated + * collection element in the target entity. */ public void bindManyToManyInverseForeignKey( PersistentClass targetEntity, @@ -2560,6 +2571,7 @@ public void bindManyToManyInverseForeignKey( targetEntity, collection.getOwner(), value, + propertyName, true, context ); 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 19aff56469..9afd464c89 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 @@ -197,6 +197,9 @@ public class EntityBinder { private boolean cacheLazyProperty; private String naturalIdCacheRegion; + /** + * Bind an entity class. This can be done in a single pass. + */ public static void bindEntityClass( XClass clazzToProcess, Map inheritanceStatePerClass, @@ -1555,7 +1558,6 @@ private static Cache buildCacheMock(String region, MetadataBuildingContext conte return new LocalCacheAnnotationStub( region, determineCacheConcurrencyStrategy( context ) ); } - @SuppressWarnings("ClassExplicitlyAnnotation") private static class LocalCacheAnnotationStub implements Cache { private final String region; private final CacheConcurrencyStrategy usage; diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/PropertyBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/PropertyBinder.java index 1e9bca93e1..6f67c39e7f 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/PropertyBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/PropertyBinder.java @@ -93,10 +93,8 @@ public void setEntityBinder(EntityBinder entityBinder) { this.entityBinder = entityBinder; } - /* - * property can be null - * prefer propertyName to property.getName() since some are overloaded - */ + // property can be null + // prefer propertyName to property.getName() since some are overloaded private XProperty property; private XClass returnedClass; private boolean isId; @@ -234,7 +232,7 @@ public void setXToMany(boolean xToMany) { } private Property bind(Property prop) { - if (isId) { + if ( isId ) { final RootClass rootClass = ( RootClass ) holder.getPersistentClass(); //if an xToMany, it has to be wrapped today. //FIXME this poses a problem as the PK is the class instead of the associated class which is not really compliant with the spec