HHH-11544 - Joins over type variable defined relations is non-deterministic

Fix single table inheritance issues and improve polymorphic join condition
This commit is contained in:
Christian Beikov 2017-03-04 00:52:14 +01:00 committed by Vlad Mihalcea
parent fb0957fa12
commit 865c159bff
17 changed files with 1247 additions and 39 deletions

View File

@ -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;
}

View File

@ -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,

View File

@ -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.
*

View File

@ -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 )

View File

@ -5492,6 +5492,37 @@ public abstract class AbstractEntityPersister
return attributeDefinitions;
}
public String[][] getPolymorphicJoinColumns(String lhsTableAlias, String propertyPath) {
Set<String> subclassEntityNames = (Set<String>) getEntityMetamodel()
.getSubclassEntityNames();
// We will collect all the join columns from the LHS subtypes here
List<String[]> 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 ) {

View File

@ -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 );
}
}

View File

@ -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<lhsColumns[i].length; j++) {
this.buffer.append( lhsColumns[i][j] )
.append('=')
.append(rhsAlias)
.append('.')
.append( rhsColumns[j] );
if ( j < lhsColumns[i].length-1 ) {
this.buffer.append( " and " );
}
}
if ( i < lhsColumns.length - 1 ) {
this.buffer.append( " or " );
}
}
if ( lhsColumns.length > 1 ) {
this.buffer.append( ")" );
}
addCondition( buffer, on );
}
@Override
public String toFromFragmentString() {
return this.buffer.toString();

View File

@ -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.
*

View File

@ -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

View File

@ -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
) {

View File

@ -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 );
}
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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;

View File

@ -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 );

View File

@ -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<T extends PolymorphicBase> 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<PolymorphicSub1> {
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<PolymorphicSub2> {
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<PolymorphicBase> list = new ArrayList<PolymorphicBase>();
private Set<PolymorphicBase> children = new HashSet<PolymorphicBase>();
private Map<String, PolymorphicBase> map = new HashMap<String, PolymorphicBase>();
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<PolymorphicBase> getList() {
return list;
}
public void setList(List<PolymorphicBase> list) {
this.list = list;
}
@OneToMany(mappedBy = "parent")
public Set<PolymorphicBase> getChildren() {
return children;
}
public void setChildren(Set<PolymorphicBase> children) {
this.children = children;
}
@OneToMany
@JoinTable(name = "polymorphic_map")
@MapKeyColumn(length = 20, nullable = false)
public Map<String, PolymorphicBase> getMap() {
return map;
}
public void setMap(Map<String, PolymorphicBase> 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<T extends PolymorphicBase, E extends BaseEmbeddable> 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<PolymorphicSub1, Embeddable1> {
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<PolymorphicSub2, Embeddable2> {
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;
}
}
}

View File

@ -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