avoid passing property-level information via the first AnnotatedJoinColumn

This commit is contained in:
Gavin King 2022-10-28 20:59:57 +02:00
parent d9392d6601
commit 8b3030aa8b
7 changed files with 106 additions and 69 deletions

View File

@ -46,31 +46,31 @@ public class AnnotatedDiscriminatorColumn extends AnnotatedColumn {
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(

View File

@ -505,7 +505,7 @@ public final class AnnotationBinder {
}
/**
* Bind a class having JSR175 annotations. Subclasses <b>have to</b> be bound after its parent class.
* Bind an annotated class. A subclass must be bound <em>after</em> 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 @@ public final class AnnotationBinder {
|| 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,

View File

@ -164,6 +164,7 @@ public class BinderHelper {
// 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 class BinderHelper {
// 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 @@ public class BinderHelper {
// 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<Property> 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 @@ public class BinderHelper {
}
}
/**
* 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 @@ public class BinderHelper {
*/
private static Property referencedProperty(
PersistentClass ownerEntity,
PersistentClass associatedEntity,
String propertyName,
boolean inverse,
AnnotatedJoinColumn[] columns,
Object columnOwner,
List<Property> properties,
MetadataBuildingContext context) {
@ -253,9 +280,7 @@ public class BinderHelper {
// 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 @@ public class BinderHelper {
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" );
}

View File

@ -107,6 +107,7 @@ public class ToOneFkSecondPass extends FkSecondPass {
targetEntity,
persistentClass,
manyToOne,
path,
false,
buildingContext
);

View File

@ -94,7 +94,6 @@ import org.hibernate.annotations.common.reflection.XProperty;
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 @@ public abstract class CollectionBinder {
this.buildingContext = buildingContext;
}
/**
* The first pass at binding a collection.
*/
public static void bindCollection(
PropertyHolder propertyHolder,
Nullability nullability,
@ -1506,8 +1508,10 @@ public abstract class CollectionBinder {
return isNotEmpty( mappedBy );
}
/**
* Bind a {@link OneToMany} association.
*/
protected void bindOneToManySecondPass(Map<String, PersistentClass> persistentClasses) {
if ( property == null ) {
throw new AssertionFailure( "null was passed for argument property" );
}
@ -1544,7 +1548,7 @@ public abstract class CollectionBinder {
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 @@ public abstract class CollectionBinder {
}
}
/**
* Bind a {@link ManyToMany} association or {@link ElementCollection}.
*/
private void bindManyToManySecondPass(Map<String, PersistentClass> 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 @@ public abstract class CollectionBinder {
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 @@ public abstract class CollectionBinder {
/**
* 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 abstract class CollectionBinder {
targetEntity,
collection.getOwner(),
value,
propertyName,
true,
context
);

View File

@ -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<XClass, InheritanceState> inheritanceStatePerClass,
@ -1555,7 +1558,6 @@ public class EntityBinder {
return new LocalCacheAnnotationStub( region, determineCacheConcurrencyStrategy( context ) );
}
@SuppressWarnings("ClassExplicitlyAnnotation")
private static class LocalCacheAnnotationStub implements Cache {
private final String region;
private final CacheConcurrencyStrategy usage;

View File

@ -93,10 +93,8 @@ public class PropertyBinder {
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 class PropertyBinder {
}
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