From 865c159bff91c55bf9dfb708eada681b25dcf5e1 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Sat, 4 Mar 2017 00:52:14 +0100 Subject: [PATCH] HHH-11544 - Joins over type variable defined relations is non-deterministic Fix single table inheritance issues and improve polymorphic join condition --- .../engine/internal/JoinSequence.java | 61 +- .../hql/internal/ast/tree/DotNode.java | 32 +- .../ast/util/SessionFactoryHelper.java | 18 + .../LoadQueryJoinAndFetchProcessor.java | 38 +- .../entity/AbstractEntityPersister.java | 31 + .../entity/AbstractPropertyMapping.java | 221 ++++++- .../org/hibernate/sql/ANSIJoinFragment.java | 57 ++ .../java/org/hibernate/sql/JoinFragment.java | 17 + .../org/hibernate/sql/OracleJoinFragment.java | 48 ++ .../org/hibernate/sql/QueryJoinFragment.java | 48 ++ .../hibernate/sql/Sybase11JoinFragment.java | 54 ++ .../java/org/hibernate/type/EntityType.java | 9 + .../org/hibernate/type/ManyToOneType.java | 6 + .../java/org/hibernate/type/OneToOneType.java | 7 + .../hibernate/type/SpecialOneToOneType.java | 4 + .../MultiInheritanceImplicitDowncastTest.java | 614 ++++++++++++++++++ .../MultiSingleTableLoadTest.java | 21 +- 17 files changed, 1247 insertions(+), 39 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/inheritance/discriminator/MultiInheritanceImplicitDowncastTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/JoinSequence.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/JoinSequence.java index c08cbf8bc2..bf2d699ea8 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/JoinSequence.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/JoinSequence.java @@ -129,7 +129,28 @@ public class JoinSequence { String alias, JoinType joinType, String[] referencingKey) throws MappingException { - joins.add( new Join( factory, associationType, alias, joinType, referencingKey ) ); + joins.add( new Join( factory, associationType, alias, joinType, new String[][] { referencingKey } ) ); + return this; + } + + /** + * Add a join to this sequence + * + * @param associationType The type of the association representing the join + * @param alias The RHS alias for the join + * @param joinType The type of join (INNER, etc) + * @param referencingKeys The LHS columns for the join condition + * + * @return The Join memento + * + * @throws MappingException Generally indicates a problem resolving the associationType to a {@link Joinable} + */ + public JoinSequence addJoin( + AssociationType associationType, + String alias, + JoinType joinType, + String[][] referencingKeys) throws MappingException { + joins.add( new Join( factory, associationType, alias, joinType, referencingKeys ) ); return this; } @@ -242,8 +263,7 @@ public class JoinSequence { join.getAlias(), join.getLHSColumns(), JoinHelper.getRHSColumnNames( join.getAssociationType(), factory ), - join.joinType, - "" + join.joinType ); } addSubclassJoins( @@ -263,17 +283,28 @@ public class JoinSequence { joinFragment.addFromFragmentString( " on " ); final String rhsAlias = first.getAlias(); - final String[] lhsColumns = first.getLHSColumns(); + final String[][] lhsColumns = first.getLHSColumns(); final String[] rhsColumns = JoinHelper.getRHSColumnNames( first.getAssociationType(), factory ); - for ( int j=0; j < lhsColumns.length; j++) { - joinFragment.addFromFragmentString( lhsColumns[j] ); - joinFragment.addFromFragmentString( "=" ); - joinFragment.addFromFragmentString( rhsAlias ); - joinFragment.addFromFragmentString( "." ); - joinFragment.addFromFragmentString( rhsColumns[j] ); - if ( j < lhsColumns.length - 1 ) { - joinFragment.addFromFragmentString( " and " ); + if ( lhsColumns.length > 1 ) { + joinFragment.addFromFragmentString( "(" ); + } + for ( int i = 0; i < lhsColumns.length; i++ ) { + for ( int j = 0; j < lhsColumns[i].length; j++ ) { + joinFragment.addFromFragmentString( lhsColumns[i][j] ); + joinFragment.addFromFragmentString( "=" ); + joinFragment.addFromFragmentString( rhsAlias ); + joinFragment.addFromFragmentString( "." ); + joinFragment.addFromFragmentString( rhsColumns[j] ); + if ( j < lhsColumns[i].length - 1 ) { + joinFragment.addFromFragmentString( " and " ); + } } + if ( i < lhsColumns.length - 1 ) { + joinFragment.addFromFragmentString( " or " ); + } + } + if ( lhsColumns.length > 1 ) { + joinFragment.addFromFragmentString( ")" ); } joinFragment.addFromFragmentString( " and " ); @@ -568,14 +599,14 @@ public class JoinSequence { private final Joinable joinable; private final JoinType joinType; private final String alias; - private final String[] lhsColumns; + private final String[][] lhsColumns; Join( SessionFactoryImplementor factory, AssociationType associationType, String alias, JoinType joinType, - String[] lhsColumns) throws MappingException { + String[][] lhsColumns) throws MappingException { this.associationType = associationType; this.joinable = associationType.getAssociatedJoinable( factory ); this.alias = alias; @@ -599,7 +630,7 @@ public class JoinSequence { return joinType; } - public String[] getLHSColumns() { + public String[][] getLHSColumns() { return lhsColumns; } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/DotNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/DotNode.java index e7e425e0c9..03b6ce5d47 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/DotNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/DotNode.java @@ -6,6 +6,11 @@ */ package org.hibernate.hql.internal.ast.tree; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + import org.hibernate.QueryException; import org.hibernate.engine.internal.JoinSequence; import org.hibernate.hql.internal.CollectionProperties; @@ -16,6 +21,7 @@ import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.log.DeprecationLogger; import org.hibernate.internal.util.StringHelper; +import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.persister.collection.QueryableCollection; import org.hibernate.persister.entity.AbstractEntityPersister; import org.hibernate.persister.entity.EntityPersister; @@ -483,11 +489,6 @@ public class DotNode extends FromReferenceNode implements DisplayableNode, Selec boolean useFoundFromElement = found && canReuse( classAlias, elem ); if ( !useFoundFromElement ) { - // If this is an implied join in a from element, then use the impled join type which is part of the - // tree parser's state (set by the gramamar actions). - JoinSequence joinSequence = getSessionFactoryHelper() - .createJoinSequence( impliedJoin, propertyType, tableAlias, joinType, joinColumns ); - // If the lhs of the join is a "component join", we need to go back to the // first non-component-join as the origin to properly link aliases and // join columns @@ -501,6 +502,27 @@ public class DotNode extends FromReferenceNode implements DisplayableNode, Selec String role = lhsFromElement.getClassName() + "." + propertyName; + JoinSequence joinSequence; + + if ( joinColumns.length == 0 ) { + // When no columns are available, this is a special join that involves multiple subtypes + String lhsTableAlias = getLhs().getFromElement().getTableAlias(); + + AbstractEntityPersister persister = (AbstractEntityPersister) lhsFromElement.getEntityPersister(); + + String[][] polyJoinColumns = persister.getPolymorphicJoinColumns(lhsTableAlias, propertyPath); + + // Special join sequence that uses the poly join columns + joinSequence = getSessionFactoryHelper() + .createJoinSequence( impliedJoin, propertyType, tableAlias, joinType, polyJoinColumns ); + } + else { + // If this is an implied join in a from element, then use the implied join type which is part of the + // tree parser's state (set by the grammar actions). + joinSequence = getSessionFactoryHelper() + .createJoinSequence( impliedJoin, propertyType, tableAlias, joinType, joinColumns ); + } + FromElementFactory factory = new FromElementFactory( currentFromClause, lhsFromElement, diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/SessionFactoryHelper.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/SessionFactoryHelper.java index 632c36d451..383af44f97 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/SessionFactoryHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/SessionFactoryHelper.java @@ -273,6 +273,24 @@ public class SessionFactoryHelper { return joinSequence; } + /** + * Generate a join sequence representing the given association type. + * + * @param implicit Should implicit joins (theta-style) or explicit joins (ANSI-style) be rendered + * @param associationType The type representing the thing to be joined into. + * @param tableAlias The table alias to use in qualifying the join conditions + * @param joinType The type of join to render (inner, outer, etc); see {@link org.hibernate.sql.JoinFragment} + * @param columns The columns making up the condition of the join. + * + * @return The generated join sequence. + */ + public JoinSequence createJoinSequence(boolean implicit, AssociationType associationType, String tableAlias, JoinType joinType, String[][] columns) { + JoinSequence joinSequence = createJoinSequence(); + joinSequence.setUseThetaStyle( implicit ); // Implicit joins use theta style (WHERE pk = fk), explicit joins use JOIN (afterQuery from) + joinSequence.addJoin( associationType, tableAlias, joinType, columns ); + return joinSequence; + } + /** * Create a join sequence rooted at the given collection. * diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/LoadQueryJoinAndFetchProcessor.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/LoadQueryJoinAndFetchProcessor.java index 59d4362c3f..67633cbe00 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/LoadQueryJoinAndFetchProcessor.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/LoadQueryJoinAndFetchProcessor.java @@ -38,6 +38,7 @@ import org.hibernate.loader.plan.spi.QuerySpace; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.collection.CollectionPropertyNames; import org.hibernate.persister.collection.QueryableCollection; +import org.hibernate.persister.entity.AbstractEntityPersister; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.Joinable; import org.hibernate.persister.entity.OuterJoinLoadable; @@ -257,14 +258,35 @@ public class LoadQueryJoinAndFetchProcessor { getJoinedAssociationTypeOrNull( join ) ); - joinFragment.addJoin( - joinable.getTableName(), - rhsTableAlias, - join.resolveAliasedLeftHandSideJoinConditionColumns( lhsTableAlias ), - join.resolveNonAliasedRightHandSideJoinConditionColumns(), - join.isRightHandSideRequired() ? JoinType.INNER_JOIN : JoinType.LEFT_OUTER_JOIN, - additionalJoinConditions - ); + String[] joinColumns = join.resolveAliasedLeftHandSideJoinConditionColumns( lhsTableAlias ); + if ( joinColumns.length == 0 ) { + // When no columns are available, this is a special join that involves multiple subtypes + AbstractEntityPersister persister = (AbstractEntityPersister) ( (EntityQuerySpace) join.getLeftHandSide() ).getEntityPersister(); + + String[][] polyJoinColumns = persister.getPolymorphicJoinColumns( + lhsTableAlias, + ( (JoinDefinedByMetadata) join ).getJoinedPropertyName() + ); + + joinFragment.addJoin( + joinable.getTableName(), + rhsTableAlias, + polyJoinColumns, + join.resolveNonAliasedRightHandSideJoinConditionColumns(), + join.isRightHandSideRequired() ? JoinType.INNER_JOIN : JoinType.LEFT_OUTER_JOIN, + additionalJoinConditions + ); + } + else { + joinFragment.addJoin( + joinable.getTableName(), + rhsTableAlias, + joinColumns, + join.resolveNonAliasedRightHandSideJoinConditionColumns(), + join.isRightHandSideRequired() ? JoinType.INNER_JOIN : JoinType.LEFT_OUTER_JOIN, + additionalJoinConditions + ); + } joinFragment.addJoins( joinable.fromJoinFragment( rhsTableAlias, false, true ), joinable.whereJoinFragment( rhsTableAlias, false, true ) diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index 94715d9a8e..8511cb241c 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -5492,6 +5492,37 @@ public abstract class AbstractEntityPersister return attributeDefinitions; } + public String[][] getPolymorphicJoinColumns(String lhsTableAlias, String propertyPath) { + Set subclassEntityNames = (Set) getEntityMetamodel() + .getSubclassEntityNames(); + // We will collect all the join columns from the LHS subtypes here + List polymorphicJoinColumns = new ArrayList<>( subclassEntityNames.size() ); + + String[] joinColumns = null; + + OUTER: + for ( String subclassEntityName : subclassEntityNames ) { + AbstractEntityPersister subclassPersister = (AbstractEntityPersister) getFactory() + .getMetamodel() + .entityPersister( subclassEntityName ); + joinColumns = subclassPersister.toColumns( lhsTableAlias, propertyPath ); + + if ( joinColumns.length == 0 ) { + // The subtype does not have a "concrete" mapping for the property path + continue; + } + + // Check for duplicates like this since we will mostly have just a few candidates + for ( String[] existingColumns : polymorphicJoinColumns ) { + if ( Arrays.deepEquals( existingColumns, joinColumns ) ) { + continue OUTER; + } + } + polymorphicJoinColumns.add( joinColumns ); + } + + return ArrayHelper.to2DStringArray( polymorphicJoinColumns ); + } private void prepareEntityIdentifierDefinition() { if ( entityIdentifierDefinition != null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractPropertyMapping.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractPropertyMapping.java index 2df0f8f541..b468a6d2f9 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractPropertyMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractPropertyMapping.java @@ -11,15 +11,24 @@ import java.util.Map; import org.hibernate.MappingException; import org.hibernate.QueryException; +import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.engine.spi.Mapping; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.collections.ArrayHelper; +import org.hibernate.mapping.Collection; +import org.hibernate.mapping.MappedSuperclass; +import org.hibernate.mapping.PersistentClass; import org.hibernate.sql.Template; +import org.hibernate.type.AnyType; import org.hibernate.type.AssociationType; +import org.hibernate.type.CollectionType; import org.hibernate.type.CompositeType; import org.hibernate.type.EntityType; +import org.hibernate.type.ManyToOneType; +import org.hibernate.type.OneToOneType; +import org.hibernate.type.SpecialOneToOneType; import org.hibernate.type.Type; /** @@ -109,6 +118,23 @@ public abstract class AbstractPropertyMapping implements PropertyMapping { return result; } + private void logDuplicateRegistration(String path, Type existingType, Type type) { + if ( LOG.isTraceEnabled() ) { + LOG.tracev( + "Skipping duplicate registration of path [{0}], existing type = [{1}], incoming type = [{2}]", + path, + existingType, + type + ); + } + } + + /** + * Only kept around for compatibility reasons since this seems to be API. + * + * @deprecated Use {@link #addPropertyPath(String, Type, String[], String[], String[], String[], Mapping)} instead + */ + @Deprecated protected void addPropertyPath( String path, Type type, @@ -116,15 +142,96 @@ public abstract class AbstractPropertyMapping implements PropertyMapping { String[] columnReaders, String[] columnReaderTemplates, String[] formulaTemplates) { - // TODO : not quite sure yet of the difference, but this is only needed from annotations for @Id @ManyToOne support - if ( typesByPropertyPath.containsKey( path ) ) { - if ( LOG.isTraceEnabled() ) { - LOG.tracev( - "Skipping duplicate registration of path [{0}], existing type = [{1}], incoming type = [{2}]", + addPropertyPath( path, type, columns, columnReaders, columnReaderTemplates, formulaTemplates, null ); + } + + protected void addPropertyPath( + String path, + Type type, + String[] columns, + String[] columnReaders, + String[] columnReaderTemplates, + String[] formulaTemplates, + Mapping factory) { + Type existingType = typesByPropertyPath.get( path ); + if ( existingType != null ) { + // If types match or the new type is not an association type, there is nothing for us to do + if ( type == existingType || !( type instanceof AssociationType ) ) { + logDuplicateRegistration( path, - typesByPropertyPath.get( path ), + existingType, type ); + return; + } + + // Workaround for org.hibernate.cfg.annotations.PropertyBinder.bind() adding a component for *ToOne ids + if ( !( existingType instanceof AssociationType ) ) { + logDuplicateRegistration( + path, + existingType, + type + ); + return; + } + + Type newType; + MetadataImplementor metadata = (MetadataImplementor) factory; + + if ( type instanceof AnyType ) { + // TODO: not sure how to handle any types + throw new UnsupportedOperationException( "Not yet implemented!" ); + } + else if ( type instanceof CollectionType ) { + Collection thisCollection = metadata.getCollectionBinding( ( (CollectionType) existingType ).getRole() ); + Collection otherCollection = metadata.getCollectionBinding( ( (CollectionType) type ).getRole() ); + + if ( thisCollection == otherCollection ) { + logDuplicateRegistration( + path, + existingType, + type + ); + return; + } + + Collection commonCollection = getSuperCollection( + metadata, + thisCollection.getOwner(), + otherCollection.getOwner(), + thisCollection.getReferencedPropertyName() + ); + + newType = commonCollection.getType(); + } + else if ( type instanceof EntityType ) { + EntityType entityType1 = (EntityType) existingType; + EntityType entityType2 = (EntityType) type; + + if ( entityType1.getAssociatedEntityName().equals( entityType2.getAssociatedEntityName() ) ) { + logDuplicateRegistration( + path, + existingType, + type + ); + return; + } + + newType = getCommonType( metadata, entityType1, entityType2 ); + } + else { + throw new IllegalStateException( "Unexpected association type: " + type ); + } + + typesByPropertyPath.put( path, newType ); + // Set everything to empty to signal action has to be taken! + // org.hibernate.hql.internal.ast.tree.DotNode.dereferenceEntityJoin() is reacting to this + String[] empty = new String[0]; + columnsByPropertyPath.put( path, empty ); + columnReadersByPropertyPath.put( path, empty ); + columnReaderTemplatesByPropertyPath.put( path, empty ); + if ( formulaTemplates != null ) { + formulaTemplatesByPropertyPath.put( path, empty ); } return; } @@ -137,6 +244,102 @@ public abstract class AbstractPropertyMapping implements PropertyMapping { } } + private Type getCommonType(MetadataImplementor metadata, EntityType entityType1, EntityType entityType2) { + PersistentClass thisClass = metadata.getEntityBinding( entityType1.getAssociatedEntityName() ); + PersistentClass otherClass = metadata.getEntityBinding( entityType2.getAssociatedEntityName() ); + PersistentClass commonClass = getCommonPersistentClass( thisClass, otherClass ); + + // Create a copy of the type but with the common class + if ( entityType1 instanceof ManyToOneType ) { + ManyToOneType t = (ManyToOneType) entityType1; + return new ManyToOneType( t, commonClass.getEntityName() ); + } + else if ( entityType1 instanceof SpecialOneToOneType ) { + SpecialOneToOneType t = (SpecialOneToOneType) entityType1; + return new SpecialOneToOneType( t, commonClass.getEntityName() ); + } + else if ( entityType1 instanceof OneToOneType ) { + OneToOneType t = (OneToOneType) entityType1; + return new OneToOneType( t, commonClass.getEntityName() ); + } + else { + throw new IllegalStateException( "Unexpected entity type: " + entityType1 ); + } + } + + private PersistentClass getCommonPersistentClass(PersistentClass clazz1, PersistentClass clazz2) { + while ( !clazz2.getMappedClass().isAssignableFrom( clazz1.getMappedClass() ) ) { + clazz2 = clazz2.getSuperclass(); + } + return clazz2; + } + + private Collection getSuperCollection(MetadataImplementor metadata, PersistentClass clazz1, PersistentClass commonPersistentClass, String propertyName) { + Class c1 = clazz1.getMappedClass(); + Class c2 = commonPersistentClass.getMappedClass(); + MappedSuperclass commonMappedSuperclass = null; + + // First we traverse up the clazz2/commonPersistentClass super types until we find a common type + while ( !c2.isAssignableFrom( c1 ) ) { + if ( commonPersistentClass == null) { + if ( commonMappedSuperclass.getSuperPersistentClass() == null ) { + commonMappedSuperclass = commonMappedSuperclass.getSuperMappedSuperclass(); + commonPersistentClass = null; + } + else { + commonPersistentClass = commonMappedSuperclass.getSuperPersistentClass(); + commonMappedSuperclass = null; + } + } + else { + if ( commonPersistentClass.getSuperclass() == null ) { + commonMappedSuperclass = commonPersistentClass.getSuperMappedSuperclass(); + commonPersistentClass = null; + } + else { + commonPersistentClass = commonPersistentClass.getSuperclass(); + commonMappedSuperclass = null; + } + } + } + + // Then we traverse it's types up as long as possible until we find a type that has a collection binding + while ( c2 != Object.class ) { + if ( commonMappedSuperclass != null ) { + Collection collection = metadata.getCollectionBinding( commonMappedSuperclass.getMappedClass().getName() + "." + propertyName ); + if ( collection != null ) { + return collection; + } + + if ( commonMappedSuperclass.getSuperPersistentClass() == null ) { + commonMappedSuperclass = commonMappedSuperclass.getSuperMappedSuperclass(); + commonPersistentClass = null; + } + else { + commonPersistentClass = commonMappedSuperclass.getSuperPersistentClass(); + commonMappedSuperclass = null; + } + } + else { + Collection collection = metadata.getCollectionBinding( commonPersistentClass.getEntityName() + "." + propertyName ); + if ( collection != null ) { + return collection; + } + + if ( commonPersistentClass.getSuperclass() == null ) { + commonMappedSuperclass = commonPersistentClass.getSuperMappedSuperclass(); + commonPersistentClass = null; + } + else { + commonPersistentClass = commonPersistentClass.getSuperclass(); + commonMappedSuperclass = null; + } + } + } + + return null; + } + /*protected void initPropertyPaths( final String path, final Type type, @@ -189,7 +392,7 @@ public abstract class AbstractPropertyMapping implements PropertyMapping { } if ( path != null ) { - addPropertyPath( path, type, columns, columnReaders, columnReaderTemplates, formulaTemplates ); + addPropertyPath( path, type, columns, columnReaders, columnReaderTemplates, formulaTemplates, factory ); } if ( type.isComponentType() ) { @@ -242,14 +445,14 @@ public abstract class AbstractPropertyMapping implements PropertyMapping { if ( etype.isReferenceToPrimaryKey() ) { if ( !hasNonIdentifierPropertyNamedId ) { String idpath1 = extendPath( path, EntityPersister.ENTITY_ID ); - addPropertyPath( idpath1, idtype, columns, columnReaders, columnReaderTemplates, null ); + addPropertyPath( idpath1, idtype, columns, columnReaders, columnReaderTemplates, null, factory ); initPropertyPaths( idpath1, idtype, columns, columnReaders, columnReaderTemplates, null, factory ); } } if ( idPropName != null ) { String idpath2 = extendPath( path, idPropName ); - addPropertyPath( idpath2, idtype, columns, columnReaders, columnReaderTemplates, null ); + addPropertyPath( idpath2, idtype, columns, columnReaders, columnReaderTemplates, null, factory ); initPropertyPaths( idpath2, idtype, columns, columnReaders, columnReaderTemplates, null, factory ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ANSIJoinFragment.java b/hibernate-core/src/main/java/org/hibernate/sql/ANSIJoinFragment.java index f32f945c0f..2aa6f77235 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ANSIJoinFragment.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ANSIJoinFragment.java @@ -89,6 +89,63 @@ public class ANSIJoinFragment extends JoinFragment { } + public void addJoin( + String rhsTableName, + String rhsAlias, + String[][] lhsColumns, + String[] rhsColumns, + JoinType joinType, + String on) { + final String joinString; + switch (joinType) { + case INNER_JOIN: + joinString = " inner join "; + break; + case LEFT_OUTER_JOIN: + joinString = " left outer join "; + break; + case RIGHT_OUTER_JOIN: + joinString = " right outer join "; + break; + case FULL_JOIN: + joinString = " full outer join "; + break; + default: + throw new AssertionFailure("undefined join type"); + } + + this.buffer.append(joinString) + .append(rhsTableName) + .append(' ') + .append(rhsAlias) + .append(" on "); + + + if ( lhsColumns.length > 1 ) { + this.buffer.append( "(" ); + } + for ( int i = 0; i < lhsColumns.length; i++ ) { + for ( int j=0; j 1 ) { + this.buffer.append( ")" ); + } + + addCondition( buffer, on ); + } + @Override public String toFromFragmentString() { return this.buffer.toString(); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/JoinFragment.java b/hibernate-core/src/main/java/org/hibernate/sql/JoinFragment.java index 36c6e258ef..ed1e5df4c5 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/JoinFragment.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/JoinFragment.java @@ -76,6 +76,23 @@ public abstract class JoinFragment { */ public abstract void addJoin(String tableName, String alias, String[] fkColumns, String[] pkColumns, JoinType joinType, String on); + /** + * Adds a join, with an additional ON clause fragment + * + * @param tableName The name of the table to be joined + * @param alias The alias to apply to the joined table + * @param fkColumns The names of the columns which reference the joined table + * @param pkColumns The columns in the joined table being referenced + * @param joinType The type of join + * @param on The additional ON fragment + */ + public void addJoin(String tableName, String alias, String[][] fkColumns, String[] pkColumns, JoinType joinType, String on) { + if ( fkColumns.length > 1 ) { + throw new UnsupportedOperationException( "The join fragment does not support multiple foreign key columns: " + getClass() ); + } + addJoin( tableName, alias, fkColumns[0], pkColumns, joinType, on ); + } + /** * Adds a cross join to the specified table. * diff --git a/hibernate-core/src/main/java/org/hibernate/sql/OracleJoinFragment.java b/hibernate-core/src/main/java/org/hibernate/sql/OracleJoinFragment.java index c2b1ca2d8d..ee21fb2231 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/OracleJoinFragment.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/OracleJoinFragment.java @@ -38,6 +38,40 @@ public class OracleJoinFragment extends JoinFragment { } } + public void addJoin(String tableName, String alias, String[][] fkColumns, String[] pkColumns, JoinType joinType) { + addCrossJoin( tableName, alias ); + + if ( fkColumns.length > 1 ) { + afterWhere.append( "(" ); + } + for ( int i = 0; i < fkColumns.length; i++ ) { + afterWhere.append( " and " ); + for ( int j = 0; j < fkColumns[i].length; j++ ) { + setHasThetaJoins( true ); + afterWhere.append( fkColumns[i][j] ); + if ( joinType == JoinType.RIGHT_OUTER_JOIN || joinType == JoinType.FULL_JOIN ) { + afterWhere.append( "(+)" ); + } + afterWhere.append( '=' ) + .append( alias ) + .append( '.' ) + .append( pkColumns[j] ); + if ( joinType == JoinType.LEFT_OUTER_JOIN || joinType == JoinType.FULL_JOIN ) { + afterWhere.append( "(+)" ); + } + if ( j < fkColumns[i].length - 1 ) { + afterWhere.append( " and " ); + } + } + if ( i < fkColumns.length - 1 ) { + afterWhere.append( " or " ); + } + } + if ( fkColumns.length > 1 ) { + afterWhere.append( ")" ); + } + } + public String toFromFragmentString() { return afterFrom.toString(); } @@ -101,6 +135,20 @@ public class OracleJoinFragment extends JoinFragment { } } + public void addJoin(String tableName, String alias, String[][] fkColumns, String[] pkColumns, JoinType joinType, String on) { + //arbitrary on clause ignored!! + addJoin( tableName, alias, fkColumns, pkColumns, joinType ); + if ( joinType == JoinType.INNER_JOIN ) { + addCondition( on ); + } + else if ( joinType == JoinType.LEFT_OUTER_JOIN ) { + addLeftOuterJoinCondition( on ); + } + else { + throw new UnsupportedOperationException( "join type not supported by OracleJoinFragment (use Oracle9iDialect/Oracle10gDialect)" ); + } + } + /** * This method is a bit of a hack, and assumes * that the column on the "right" side of the diff --git a/hibernate-core/src/main/java/org/hibernate/sql/QueryJoinFragment.java b/hibernate-core/src/main/java/org/hibernate/sql/QueryJoinFragment.java index ecb76ac54e..1fe427c931 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/QueryJoinFragment.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/QueryJoinFragment.java @@ -6,6 +6,7 @@ */ package org.hibernate.sql; import org.hibernate.dialect.Dialect; +import org.hibernate.internal.util.StringHelper; /** * A join that appears in a translated HQL query @@ -32,6 +33,14 @@ public class QueryJoinFragment extends JoinFragment { addJoin( tableName, alias, alias, fkColumns, pkColumns, joinType, on ); } + public void addJoin(String tableName, String alias, String[][] fkColumns, String[] pkColumns, JoinType joinType) { + addJoin( tableName, alias, alias, fkColumns, pkColumns, joinType, null ); + } + + public void addJoin(String tableName, String alias, String[][] fkColumns, String[] pkColumns, JoinType joinType, String on) { + addJoin( tableName, alias, alias, fkColumns, pkColumns, joinType, on ); + } + private void addJoin(String tableName, String alias, String concreteAlias, String[] fkColumns, String[] pkColumns, JoinType joinType, String on) { if ( !useThetaStyleInnerJoins || joinType != JoinType.INNER_JOIN ) { JoinFragment jf = dialect.createOuterJoinFragment(); @@ -45,6 +54,19 @@ public class QueryJoinFragment extends JoinFragment { } } + private void addJoin(String tableName, String alias, String concreteAlias, String[][] fkColumns, String[] pkColumns, JoinType joinType, String on) { + if ( !useThetaStyleInnerJoins || joinType != JoinType.INNER_JOIN ) { + JoinFragment jf = dialect.createOuterJoinFragment(); + jf.addJoin( tableName, alias, fkColumns, pkColumns, joinType, on ); + addFragment( jf ); + } + else { + addCrossJoin( tableName, alias ); + addCondition( concreteAlias, fkColumns, pkColumns ); + addCondition( on ); + } + } + public String toFromFragmentString() { return afterFrom.toString(); } @@ -94,6 +116,31 @@ public class QueryJoinFragment extends JoinFragment { } } + public void addCondition(String alias, String[][] fkColumns, String[] pkColumns) { + afterWhere.append( " and " ); + if ( fkColumns.length > 1 ) { + afterWhere.append( "(" ); + } + for ( int i = 0; i < fkColumns.length; i++ ) { + for ( int j = 0; j < fkColumns[i].length; j++ ) { + afterWhere.append( fkColumns[i][j] ) + .append( '=' ) + .append( alias ) + .append( '.' ) + .append( pkColumns[j] ); + if ( j < fkColumns[i].length - 1 ) { + afterWhere.append( " and " ); + } + } + if ( i < fkColumns.length - 1 ) { + afterWhere.append( " or " ); + } + } + if ( fkColumns.length > 1 ) { + afterWhere.append( ")" ); + } + } + /** * Add the condition string to the join fragment. * @@ -103,6 +150,7 @@ public class QueryJoinFragment extends JoinFragment { public boolean addCondition(String condition) { // if the condition is not already there... if ( + !StringHelper.isEmpty( condition ) && afterFrom.toString().indexOf( condition.trim() ) < 0 && afterWhere.toString().indexOf( condition.trim() ) < 0 ) { diff --git a/hibernate-core/src/main/java/org/hibernate/sql/Sybase11JoinFragment.java b/hibernate-core/src/main/java/org/hibernate/sql/Sybase11JoinFragment.java index 0a8575dd0c..8b9623afde 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/Sybase11JoinFragment.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/Sybase11JoinFragment.java @@ -47,6 +47,49 @@ public class Sybase11JoinFragment extends JoinFragment { } } + public void addJoin(String tableName, String alias, String[][] fkColumns, String[] pkColumns, JoinType joinType) { + + addCrossJoin( tableName, alias ); + + if ( fkColumns.length > 1 ) { + afterWhere.append( "(" ); + } + for ( int i = 0; i < fkColumns.length; i++ ) { + afterWhere.append( " and " ); + for ( int j = 0; j < fkColumns[i].length; j++ ) { + //full joins are not supported.. yet! + if ( joinType == JoinType.FULL_JOIN ) { + throw new UnsupportedOperationException(); + } + + afterWhere.append( fkColumns[i][j] ) + .append( " " ); + + if ( joinType == JoinType.LEFT_OUTER_JOIN ) { + afterWhere.append( '*' ); + } + afterWhere.append( '=' ); + if ( joinType == JoinType.RIGHT_OUTER_JOIN ) { + afterWhere.append( "*" ); + } + + afterWhere.append( " " ) + .append( alias ) + .append( '.' ) + .append( pkColumns[j] ); + if ( j < fkColumns[i].length - 1 ) { + afterWhere.append( " and " ); + } + } + if ( i < fkColumns.length - 1 ) { + afterWhere.append( " or " ); + } + } + if ( fkColumns.length > 1 ) { + afterWhere.append( ")" ); + } + } + public String toFromFragmentString() { return afterFrom.toString(); } @@ -109,4 +152,15 @@ public class Sybase11JoinFragment extends JoinFragment { addJoin( tableName, alias, fkColumns, pkColumns, joinType ); addCondition( on ); } + + public void addJoin( + String tableName, + String alias, + String[][] fkColumns, + String[] pkColumns, + JoinType joinType, + String on) { + addJoin( tableName, alias, fkColumns, pkColumns, joinType ); + addCondition( on ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/EntityType.java b/hibernate-core/src/main/java/org/hibernate/type/EntityType.java index b988f5c213..6e42421a53 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/EntityType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/EntityType.java @@ -111,6 +111,15 @@ public abstract class EntityType extends AbstractType implements AssociationType this.referenceToPrimaryKey = referenceToPrimaryKey; } + protected EntityType(EntityType original, String superTypeEntityName) { + this.scope = original.scope; + this.associatedEntityName = superTypeEntityName; + this.uniqueKeyPropertyName = original.uniqueKeyPropertyName; + this.eager = original.eager; + this.unwrapProxy = original.unwrapProxy; + this.referenceToPrimaryKey = original.referenceToPrimaryKey; + } + protected TypeFactory.TypeScope scope() { return scope; } diff --git a/hibernate-core/src/main/java/org/hibernate/type/ManyToOneType.java b/hibernate-core/src/main/java/org/hibernate/type/ManyToOneType.java index 722d4b7ee4..d44b8baf4f 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/ManyToOneType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/ManyToOneType.java @@ -83,6 +83,12 @@ public class ManyToOneType extends EntityType { this.isLogicalOneToOne = isLogicalOneToOne; } + public ManyToOneType(ManyToOneType original, String superTypeEntityName) { + super( original, superTypeEntityName ); + this.ignoreNotFound = original.ignoreNotFound; + this.isLogicalOneToOne = original.isLogicalOneToOne; + } + @Override protected boolean isNullable() { return ignoreNotFound; diff --git a/hibernate-core/src/main/java/org/hibernate/type/OneToOneType.java b/hibernate-core/src/main/java/org/hibernate/type/OneToOneType.java index b458592f5f..511416994d 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/OneToOneType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/OneToOneType.java @@ -63,6 +63,13 @@ public class OneToOneType extends EntityType { this.entityName = entityName; } + public OneToOneType(OneToOneType original, String superTypeEntityName) { + super( original, superTypeEntityName ); + this.foreignKeyType = original.foreignKeyType; + this.propertyName = original.propertyName; + this.entityName = original.entityName; + } + @Override public String getPropertyName() { return propertyName; diff --git a/hibernate-core/src/main/java/org/hibernate/type/SpecialOneToOneType.java b/hibernate-core/src/main/java/org/hibernate/type/SpecialOneToOneType.java index 4a53600528..bd66bfa280 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/SpecialOneToOneType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/SpecialOneToOneType.java @@ -64,6 +64,10 @@ public class SpecialOneToOneType extends OneToOneType { propertyName ); } + + public SpecialOneToOneType(SpecialOneToOneType original, String superTypeEntityName) { + super( original, superTypeEntityName ); + } public int getColumnSpan(Mapping mapping) throws MappingException { return super.getIdentifierOrUniqueKeyType( mapping ).getColumnSpan( mapping ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/inheritance/discriminator/MultiInheritanceImplicitDowncastTest.java b/hibernate-core/src/test/java/org/hibernate/test/inheritance/discriminator/MultiInheritanceImplicitDowncastTest.java new file mode 100644 index 0000000000..652e0b490a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/inheritance/discriminator/MultiInheritanceImplicitDowncastTest.java @@ -0,0 +1,614 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2014, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.inheritance.discriminator; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.persistence.AssociationOverride; +import javax.persistence.AssociationOverrides; +import javax.persistence.Basic; +import javax.persistence.DiscriminatorColumn; +import javax.persistence.Embeddable; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToOne; +import javax.persistence.MapKeyColumn; +import javax.persistence.MappedSuperclass; +import javax.persistence.OneToMany; +import javax.persistence.OrderColumn; + +import org.hibernate.Session; +import org.hibernate.engine.query.spi.HQLQueryPlan; +import org.hibernate.hql.spi.QueryTranslator; + +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +/** + * @author Christian Beikov + */ +public class MultiInheritanceImplicitDowncastTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + IntIdEntity.class, + NameObject.class, + PolymorphicBase.class, + PolymorphicPropertyBase.class, + PolymorphicPropertyMapBase.class, + PolymorphicPropertySub1.class, + PolymorphicPropertySub2.class, + PolymorphicSub1.class, + PolymorphicSub2.class + }; + } + + @Test + public void testQueryingSingle() { + doInHibernate( this::sessionFactory, s -> { + final String base = "from PolymorphicPropertyBase p left join "; + s.createQuery( base + "p.base b left join b.relation1 " ).getResultList(); + s.createQuery( base + "p.base b left join b.relation2 " ).getResultList(); + s.createQuery( base + "p.baseEmbeddable.embeddedRelation1 b left join b.relation1" ).getResultList(); + s.createQuery( base + "p.baseEmbeddable.embeddedRelation2 b left join b.relation2" ).getResultList(); + s.createQuery( base + "p.baseEmbeddable.embeddedBase b left join b.relation1" ).getResultList(); + s.createQuery( base + "p.baseEmbeddable.embeddedBase b left join b.relation2" ).getResultList(); + } ); + } + + @Test + public void testQueryingMultiple() { + doInHibernate( this::sessionFactory, s -> { + final String base = "from PolymorphicPropertyBase p left join "; + s.createQuery( base + "p.base b left join b.relation1 left join b.relation2" ).getResultList(); + s.createQuery( base + "p.base b left join b.relation2 left join b.relation1" ).getResultList(); + s.createQuery( base + "p.baseEmbeddable.embeddedBase b left join b.relation1 left join b.relation2" ).getResultList(); + s.createQuery( base + "p.baseEmbeddable.embeddedBase b left join b.relation2 left join b.relation1" ).getResultList(); + } ); + } + + @Test + public void testMultiJoinAddition1() { + testMultiJoinAddition( "from PolymorphicPropertyBase p left join p.base b left join b.relation1" ); + } + + @Test + public void testMultiJoinAddition2() { + testMultiJoinAddition( "from PolymorphicPropertyBase p left join p.base b left join b.relation2" ); + } + + private void testMultiJoinAddition(String hql) { + final HQLQueryPlan plan = sessionFactory().getQueryPlanCache().getHQLQueryPlan( + hql, + false, + Collections.EMPTY_MAP + ); + assertEquals( 1, plan.getTranslators().length ); + final QueryTranslator translator = plan.getTranslators()[0]; + final String generatedSql = translator.getSQLString(); + + int sub1JoinColumnIndex = generatedSql.indexOf( ".base_sub_1" ); + assertNotEquals( + "Generated SQL doesn't contain a join for 'base' with 'PolymorphicSub1' via 'base_sub_1':\n" + generatedSql, + -1, + sub1JoinColumnIndex + ); + int sub2JoinColumnIndex = generatedSql.indexOf( ".base_sub_2" ); + assertNotEquals( + "Generated SQL doesn't contain a join for 'base' with 'PolymorphicSub2' via 'base_sub_2':\n" + generatedSql, + -1, + sub2JoinColumnIndex + ); + } + + @MappedSuperclass + public abstract static class BaseEmbeddable implements Serializable { + private static final long serialVersionUID = 1L; + + private String someName; + private T embeddedBase; + + public BaseEmbeddable() { + } + + public String getSomeName() { + return someName; + } + + public void setSomeName(String someName) { + this.someName = someName; + } + + @ManyToOne(fetch = FetchType.LAZY) + public T getEmbeddedBase() { + return embeddedBase; + } + + public void setEmbeddedBase(T embeddedBase) { + this.embeddedBase = embeddedBase; + } + } + + @Embeddable + public abstract static class Embeddable1 extends BaseEmbeddable { + private static final long serialVersionUID = 1L; + + private String someName1; + private PolymorphicSub1 embeddedRelation1; + + public Embeddable1() { + } + + public String getSomeName1() { + return someName1; + } + + public void setSomeName1(String someName1) { + this.someName1 = someName1; + } + + @ManyToOne(fetch = FetchType.LAZY) + public PolymorphicSub1 getEmbeddedRelation1() { + return embeddedRelation1; + } + + public void setEmbeddedRelation1(PolymorphicSub1 embeddedRelation1) { + this.embeddedRelation1 = embeddedRelation1; + } + } + + @Embeddable + public abstract static class Embeddable2 extends BaseEmbeddable { + private static final long serialVersionUID = 1L; + + private String someName2; + private PolymorphicSub2 embeddedRelation2; + + public Embeddable2() { + } + + public String getSomeName2() { + return someName2; + } + + public void setSomeName2(String someName2) { + this.someName2 = someName2; + } + + @ManyToOne(fetch = FetchType.LAZY) + public PolymorphicSub2 getEmbeddedRelation2() { + return embeddedRelation2; + } + + public void setEmbeddedRelation2(PolymorphicSub2 embeddedRelation2) { + this.embeddedRelation2 = embeddedRelation2; + } + } + + @Entity(name = "IntIdEntity") + public static class IntIdEntity implements Serializable { + private static final long serialVersionUID = 1L; + + private Integer id; + private String name; + + public IntIdEntity() { + } + + public IntIdEntity(String name) { + this.name = name; + } + + @Id + @GeneratedValue + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + @Basic(optional = false) + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ( ( id == null ) ? 0 : id.hashCode() ); + return result; + } + + @Override + public boolean equals(Object obj) { + if ( this == obj ) { + return true; + } + if ( obj == null ) { + return false; + } + if ( getClass() != obj.getClass() ) { + return false; + } + IntIdEntity other = (IntIdEntity) obj; + if ( id == null ) { + if ( other.id != null ) { + return false; + } + } + else if ( !id.equals( other.id ) ) { + return false; + } + return true; + } + } + + @Embeddable + public static class NameObject implements Serializable { + + private String primaryName; + private String secondaryName; + private IntIdEntity intIdEntity; + + public NameObject() { + } + + public NameObject(String primaryName, String secondaryName) { + this.primaryName = primaryName; + this.secondaryName = secondaryName; + } + + public String getPrimaryName() { + return primaryName; + } + + public void setPrimaryName(String primaryName) { + this.primaryName = primaryName; + } + + public String getSecondaryName() { + return secondaryName; + } + + public void setSecondaryName(String secondaryName) { + this.secondaryName = secondaryName; + } + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "name_object_int_id_entity") + public IntIdEntity getIntIdEntity() { + return intIdEntity; + } + + public void setIntIdEntity(IntIdEntity intIdEntity) { + this.intIdEntity = intIdEntity; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( !( o instanceof NameObject ) ) { + return false; + } + + NameObject that = (NameObject) o; + + if ( primaryName != null ? !primaryName.equals( that.primaryName ) : that.primaryName != null ) { + return false; + } + return secondaryName != null ? secondaryName.equals( that.secondaryName ) : that.secondaryName == null; + + } + + @Override + public int hashCode() { + int result = primaryName != null ? primaryName.hashCode() : 0; + result = 31 * result + ( secondaryName != null ? secondaryName.hashCode() : 0 ); + return result; + } + } + + @Entity(name = "PolymorphicBase") + @Inheritance(strategy = InheritanceType.JOINED) + public abstract static class PolymorphicBase implements Serializable { + private static final long serialVersionUID = 1L; + + private Long id; + private String name; + private PolymorphicBase parent; + private List list = new ArrayList(); + private Set children = new HashSet(); + private Map map = new HashMap(); + + public PolymorphicBase() { + } + + @Id + @GeneratedValue + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @ManyToOne(fetch = FetchType.LAZY, optional = true) + public PolymorphicBase getParent() { + return parent; + } + + public void setParent(PolymorphicBase parent) { + this.parent = parent; + } + + @OneToMany + @OrderColumn(name = "list_idx", nullable = false) + @JoinTable(name = "polymorphic_list") + public List getList() { + return list; + } + + public void setList(List list) { + this.list = list; + } + + @OneToMany(mappedBy = "parent") + public Set getChildren() { + return children; + } + + public void setChildren(Set children) { + this.children = children; + } + + @OneToMany + @JoinTable(name = "polymorphic_map") + @MapKeyColumn(length = 20, nullable = false) + public Map getMap() { + return map; + } + + public void setMap(Map map) { + this.map = map; + } + } + + @Entity(name = "PolymorphicPropertyBase") + @Inheritance(strategy = InheritanceType.SINGLE_TABLE) + @DiscriminatorColumn(name = "PROP_TYPE") + public abstract static class PolymorphicPropertyBase implements Serializable { + + private static final long serialVersionUID = 1L; + + private Long id; + private String name; + + public PolymorphicPropertyBase() { + } + + @Id + @GeneratedValue + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + @MappedSuperclass + public abstract static class PolymorphicPropertyMapBase extends + PolymorphicPropertyBase { + + private static final long serialVersionUID = 1L; + + private T base; + private E baseEmbeddable; + + public PolymorphicPropertyMapBase() { + } + + @ManyToOne(fetch = FetchType.LAZY) + public T getBase() { + return base; + } + + public void setBase(T base) { + this.base = base; + } + + @Embedded + public E getBaseEmbeddable() { + return baseEmbeddable; + } + + public void setBaseEmbeddable(E baseEmbeddable) { + this.baseEmbeddable = baseEmbeddable; + } + } + + @Entity(name = "PolymorphicPropertySub1") + @AssociationOverrides({ + @AssociationOverride(name = "base", joinColumns = @JoinColumn(name = "base_sub_1")) + }) + public static class PolymorphicPropertySub1 extends PolymorphicPropertyMapBase { + private static final long serialVersionUID = 1L; + + public PolymorphicPropertySub1() { + } + } + + @Entity(name = "PolymorphicPropertySub2") + @AssociationOverrides({ + @AssociationOverride(name = "base", joinColumns = @JoinColumn(name = "base_sub_2")) + }) + public static class PolymorphicPropertySub2 extends PolymorphicPropertyMapBase { + private static final long serialVersionUID = 1L; + + public PolymorphicPropertySub2() { + } + } + + @Entity(name = "PolymorphicSub1") + public static class PolymorphicSub1 extends PolymorphicBase { + private static final long serialVersionUID = 1L; + + private IntIdEntity relation1; + private PolymorphicBase parent1; + private NameObject embeddable1; + private Integer sub1Value; + + public PolymorphicSub1() { + } + + @ManyToOne(fetch = FetchType.LAZY) + public IntIdEntity getRelation1() { + return relation1; + } + + public void setRelation1(IntIdEntity relation1) { + this.relation1 = relation1; + } + + @ManyToOne(fetch = FetchType.LAZY) + public PolymorphicBase getParent1() { + return parent1; + } + + public void setParent1(PolymorphicBase parent1) { + this.parent1 = parent1; + } + + @Embedded + public NameObject getEmbeddable1() { + return embeddable1; + } + + public void setEmbeddable1(NameObject embeddable1) { + this.embeddable1 = embeddable1; + } + + public Integer getSub1Value() { + return sub1Value; + } + + public void setSub1Value(Integer sub1Value) { + this.sub1Value = sub1Value; + } + } + + @Entity(name = "PolymorphicSub2") + public static class PolymorphicSub2 extends PolymorphicBase { + private static final long serialVersionUID = 1L; + + private IntIdEntity relation2; + private PolymorphicBase parent2; + private NameObject embeddable2; + private Integer sub2Value; + + public PolymorphicSub2() { + } + + @ManyToOne(fetch = FetchType.LAZY) + public IntIdEntity getRelation2() { + return relation2; + } + + public void setRelation2(IntIdEntity relation2) { + this.relation2 = relation2; + } + + @ManyToOne(fetch = FetchType.LAZY) + public PolymorphicBase getParent2() { + return parent2; + } + + public void setParent2(PolymorphicBase parent1) { + this.parent2 = parent1; + } + + @Embedded + public NameObject getEmbeddable2() { + return embeddable2; + } + + public void setEmbeddable2(NameObject embeddable1) { + this.embeddable2 = embeddable1; + } + + public Integer getSub2Value() { + return sub2Value; + } + + public void setSub2Value(Integer sub2Value) { + this.sub2Value = sub2Value; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/inheritance/discriminator/MultiSingleTableLoadTest.java b/hibernate-core/src/test/java/org/hibernate/test/inheritance/discriminator/MultiSingleTableLoadTest.java index a722539860..30aa3c9687 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/inheritance/discriminator/MultiSingleTableLoadTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/inheritance/discriminator/MultiSingleTableLoadTest.java @@ -10,6 +10,7 @@ import java.io.Serializable; import javax.persistence.CascadeType; import javax.persistence.DiscriminatorValue; import javax.persistence.Entity; +import javax.persistence.FetchType; import javax.persistence.Id; import javax.persistence.Inheritance; import javax.persistence.InheritanceType; @@ -23,6 +24,7 @@ import org.junit.Test; import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; /** * @author Christian Beikov @@ -50,7 +52,7 @@ public class MultiSingleTableLoadTest extends BaseCoreFunctionalTestCase { @Test @TestForIssue(jiraKey = "HHH-5954") - public void testLoadMultipleHoldersWithDifferentSubtypes() { + public void testEagerLoadMultipleHoldersWithDifferentSubtypes() { createTestData(); doInHibernate( this::sessionFactory, session -> { Holder task1 = session.find( Holder.class, 1L ); @@ -60,12 +62,27 @@ public class MultiSingleTableLoadTest extends BaseCoreFunctionalTestCase { } ); } + @Test + public void testFetchJoinLoadMultipleHoldersWithDifferentSubtypes() { + createTestData(); + doInHibernate( this::sessionFactory, session -> { + Holder task1 = session.createQuery( "FROM Holder h JOIN FETCH h.a WHERE h.id = :id", Holder.class ) + .setParameter( "id", 1L ).getSingleResult(); + Holder task2 = session.createQuery( "FROM Holder h JOIN FETCH h.a WHERE h.id = :id", Holder.class ) + .setParameter( "id", 2L ).getSingleResult(); + assertNotNull( task1 ); + assertNotNull( task2 ); + assertTrue( task1.a instanceof B ); + assertTrue( task2.a instanceof C ); + } ); + } + @Override protected boolean isCleanupTestDataRequired() { return true; } - @Entity + @Entity(name = "Holder") @Table(name = "holder") public static class Holder implements Serializable { @Id