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:
parent
fb0957fa12
commit
865c159bff
|
@ -129,7 +129,28 @@ public class JoinSequence {
|
||||||
String alias,
|
String alias,
|
||||||
JoinType joinType,
|
JoinType joinType,
|
||||||
String[] referencingKey) throws MappingException {
|
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;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,8 +263,7 @@ public class JoinSequence {
|
||||||
join.getAlias(),
|
join.getAlias(),
|
||||||
join.getLHSColumns(),
|
join.getLHSColumns(),
|
||||||
JoinHelper.getRHSColumnNames( join.getAssociationType(), factory ),
|
JoinHelper.getRHSColumnNames( join.getAssociationType(), factory ),
|
||||||
join.joinType,
|
join.joinType
|
||||||
""
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
addSubclassJoins(
|
addSubclassJoins(
|
||||||
|
@ -263,18 +283,29 @@ public class JoinSequence {
|
||||||
joinFragment.addFromFragmentString( " on " );
|
joinFragment.addFromFragmentString( " on " );
|
||||||
|
|
||||||
final String rhsAlias = first.getAlias();
|
final String rhsAlias = first.getAlias();
|
||||||
final String[] lhsColumns = first.getLHSColumns();
|
final String[][] lhsColumns = first.getLHSColumns();
|
||||||
final String[] rhsColumns = JoinHelper.getRHSColumnNames( first.getAssociationType(), factory );
|
final String[] rhsColumns = JoinHelper.getRHSColumnNames( first.getAssociationType(), factory );
|
||||||
for ( int j=0; j < lhsColumns.length; j++) {
|
if ( lhsColumns.length > 1 ) {
|
||||||
joinFragment.addFromFragmentString( lhsColumns[j] );
|
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( "=" );
|
||||||
joinFragment.addFromFragmentString( rhsAlias );
|
joinFragment.addFromFragmentString( rhsAlias );
|
||||||
joinFragment.addFromFragmentString( "." );
|
joinFragment.addFromFragmentString( "." );
|
||||||
joinFragment.addFromFragmentString( rhsColumns[j] );
|
joinFragment.addFromFragmentString( rhsColumns[j] );
|
||||||
if ( j < lhsColumns.length - 1 ) {
|
if ( j < lhsColumns[i].length - 1 ) {
|
||||||
joinFragment.addFromFragmentString( " and " );
|
joinFragment.addFromFragmentString( " and " );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if ( i < lhsColumns.length - 1 ) {
|
||||||
|
joinFragment.addFromFragmentString( " or " );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( lhsColumns.length > 1 ) {
|
||||||
|
joinFragment.addFromFragmentString( ")" );
|
||||||
|
}
|
||||||
|
|
||||||
joinFragment.addFromFragmentString( " and " );
|
joinFragment.addFromFragmentString( " and " );
|
||||||
joinFragment.addFromFragmentString( withClauseFragment );
|
joinFragment.addFromFragmentString( withClauseFragment );
|
||||||
|
@ -568,14 +599,14 @@ public class JoinSequence {
|
||||||
private final Joinable joinable;
|
private final Joinable joinable;
|
||||||
private final JoinType joinType;
|
private final JoinType joinType;
|
||||||
private final String alias;
|
private final String alias;
|
||||||
private final String[] lhsColumns;
|
private final String[][] lhsColumns;
|
||||||
|
|
||||||
Join(
|
Join(
|
||||||
SessionFactoryImplementor factory,
|
SessionFactoryImplementor factory,
|
||||||
AssociationType associationType,
|
AssociationType associationType,
|
||||||
String alias,
|
String alias,
|
||||||
JoinType joinType,
|
JoinType joinType,
|
||||||
String[] lhsColumns) throws MappingException {
|
String[][] lhsColumns) throws MappingException {
|
||||||
this.associationType = associationType;
|
this.associationType = associationType;
|
||||||
this.joinable = associationType.getAssociatedJoinable( factory );
|
this.joinable = associationType.getAssociatedJoinable( factory );
|
||||||
this.alias = alias;
|
this.alias = alias;
|
||||||
|
@ -599,7 +630,7 @@ public class JoinSequence {
|
||||||
return joinType;
|
return joinType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String[] getLHSColumns() {
|
public String[][] getLHSColumns() {
|
||||||
return lhsColumns;
|
return lhsColumns;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,11 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.hql.internal.ast.tree;
|
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.QueryException;
|
||||||
import org.hibernate.engine.internal.JoinSequence;
|
import org.hibernate.engine.internal.JoinSequence;
|
||||||
import org.hibernate.hql.internal.CollectionProperties;
|
import org.hibernate.hql.internal.CollectionProperties;
|
||||||
|
@ -16,6 +21,7 @@ import org.hibernate.internal.CoreLogging;
|
||||||
import org.hibernate.internal.CoreMessageLogger;
|
import org.hibernate.internal.CoreMessageLogger;
|
||||||
import org.hibernate.internal.log.DeprecationLogger;
|
import org.hibernate.internal.log.DeprecationLogger;
|
||||||
import org.hibernate.internal.util.StringHelper;
|
import org.hibernate.internal.util.StringHelper;
|
||||||
|
import org.hibernate.internal.util.collections.ArrayHelper;
|
||||||
import org.hibernate.persister.collection.QueryableCollection;
|
import org.hibernate.persister.collection.QueryableCollection;
|
||||||
import org.hibernate.persister.entity.AbstractEntityPersister;
|
import org.hibernate.persister.entity.AbstractEntityPersister;
|
||||||
import org.hibernate.persister.entity.EntityPersister;
|
import org.hibernate.persister.entity.EntityPersister;
|
||||||
|
@ -483,11 +489,6 @@ public class DotNode extends FromReferenceNode implements DisplayableNode, Selec
|
||||||
boolean useFoundFromElement = found && canReuse( classAlias, elem );
|
boolean useFoundFromElement = found && canReuse( classAlias, elem );
|
||||||
|
|
||||||
if ( !useFoundFromElement ) {
|
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
|
// 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
|
// first non-component-join as the origin to properly link aliases and
|
||||||
// join columns
|
// join columns
|
||||||
|
@ -501,6 +502,27 @@ public class DotNode extends FromReferenceNode implements DisplayableNode, Selec
|
||||||
|
|
||||||
String role = lhsFromElement.getClassName() + "." + propertyName;
|
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(
|
FromElementFactory factory = new FromElementFactory(
|
||||||
currentFromClause,
|
currentFromClause,
|
||||||
lhsFromElement,
|
lhsFromElement,
|
||||||
|
|
|
@ -273,6 +273,24 @@ public class SessionFactoryHelper {
|
||||||
return joinSequence;
|
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.
|
* Create a join sequence rooted at the given collection.
|
||||||
*
|
*
|
||||||
|
|
|
@ -38,6 +38,7 @@ import org.hibernate.loader.plan.spi.QuerySpace;
|
||||||
import org.hibernate.persister.collection.CollectionPersister;
|
import org.hibernate.persister.collection.CollectionPersister;
|
||||||
import org.hibernate.persister.collection.CollectionPropertyNames;
|
import org.hibernate.persister.collection.CollectionPropertyNames;
|
||||||
import org.hibernate.persister.collection.QueryableCollection;
|
import org.hibernate.persister.collection.QueryableCollection;
|
||||||
|
import org.hibernate.persister.entity.AbstractEntityPersister;
|
||||||
import org.hibernate.persister.entity.EntityPersister;
|
import org.hibernate.persister.entity.EntityPersister;
|
||||||
import org.hibernate.persister.entity.Joinable;
|
import org.hibernate.persister.entity.Joinable;
|
||||||
import org.hibernate.persister.entity.OuterJoinLoadable;
|
import org.hibernate.persister.entity.OuterJoinLoadable;
|
||||||
|
@ -257,14 +258,35 @@ public class LoadQueryJoinAndFetchProcessor {
|
||||||
getJoinedAssociationTypeOrNull( join )
|
getJoinedAssociationTypeOrNull( join )
|
||||||
);
|
);
|
||||||
|
|
||||||
|
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(
|
joinFragment.addJoin(
|
||||||
joinable.getTableName(),
|
joinable.getTableName(),
|
||||||
rhsTableAlias,
|
rhsTableAlias,
|
||||||
join.resolveAliasedLeftHandSideJoinConditionColumns( lhsTableAlias ),
|
polyJoinColumns,
|
||||||
join.resolveNonAliasedRightHandSideJoinConditionColumns(),
|
join.resolveNonAliasedRightHandSideJoinConditionColumns(),
|
||||||
join.isRightHandSideRequired() ? JoinType.INNER_JOIN : JoinType.LEFT_OUTER_JOIN,
|
join.isRightHandSideRequired() ? JoinType.INNER_JOIN : JoinType.LEFT_OUTER_JOIN,
|
||||||
additionalJoinConditions
|
additionalJoinConditions
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
joinFragment.addJoin(
|
||||||
|
joinable.getTableName(),
|
||||||
|
rhsTableAlias,
|
||||||
|
joinColumns,
|
||||||
|
join.resolveNonAliasedRightHandSideJoinConditionColumns(),
|
||||||
|
join.isRightHandSideRequired() ? JoinType.INNER_JOIN : JoinType.LEFT_OUTER_JOIN,
|
||||||
|
additionalJoinConditions
|
||||||
|
);
|
||||||
|
}
|
||||||
joinFragment.addJoins(
|
joinFragment.addJoins(
|
||||||
joinable.fromJoinFragment( rhsTableAlias, false, true ),
|
joinable.fromJoinFragment( rhsTableAlias, false, true ),
|
||||||
joinable.whereJoinFragment( rhsTableAlias, false, true )
|
joinable.whereJoinFragment( rhsTableAlias, false, true )
|
||||||
|
|
|
@ -5492,6 +5492,37 @@ public abstract class AbstractEntityPersister
|
||||||
return attributeDefinitions;
|
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() {
|
private void prepareEntityIdentifierDefinition() {
|
||||||
if ( entityIdentifierDefinition != null ) {
|
if ( entityIdentifierDefinition != null ) {
|
||||||
|
|
|
@ -11,15 +11,24 @@ import java.util.Map;
|
||||||
|
|
||||||
import org.hibernate.MappingException;
|
import org.hibernate.MappingException;
|
||||||
import org.hibernate.QueryException;
|
import org.hibernate.QueryException;
|
||||||
|
import org.hibernate.boot.spi.MetadataImplementor;
|
||||||
import org.hibernate.engine.spi.Mapping;
|
import org.hibernate.engine.spi.Mapping;
|
||||||
import org.hibernate.internal.CoreLogging;
|
import org.hibernate.internal.CoreLogging;
|
||||||
import org.hibernate.internal.CoreMessageLogger;
|
import org.hibernate.internal.CoreMessageLogger;
|
||||||
import org.hibernate.internal.util.StringHelper;
|
import org.hibernate.internal.util.StringHelper;
|
||||||
import org.hibernate.internal.util.collections.ArrayHelper;
|
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.sql.Template;
|
||||||
|
import org.hibernate.type.AnyType;
|
||||||
import org.hibernate.type.AssociationType;
|
import org.hibernate.type.AssociationType;
|
||||||
|
import org.hibernate.type.CollectionType;
|
||||||
import org.hibernate.type.CompositeType;
|
import org.hibernate.type.CompositeType;
|
||||||
import org.hibernate.type.EntityType;
|
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;
|
import org.hibernate.type.Type;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -109,6 +118,23 @@ public abstract class AbstractPropertyMapping implements PropertyMapping {
|
||||||
return result;
|
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(
|
protected void addPropertyPath(
|
||||||
String path,
|
String path,
|
||||||
Type type,
|
Type type,
|
||||||
|
@ -116,15 +142,96 @@ public abstract class AbstractPropertyMapping implements PropertyMapping {
|
||||||
String[] columnReaders,
|
String[] columnReaders,
|
||||||
String[] columnReaderTemplates,
|
String[] columnReaderTemplates,
|
||||||
String[] formulaTemplates) {
|
String[] formulaTemplates) {
|
||||||
// TODO : not quite sure yet of the difference, but this is only needed from annotations for @Id @ManyToOne support
|
addPropertyPath( path, type, columns, columnReaders, columnReaderTemplates, formulaTemplates, null );
|
||||||
if ( typesByPropertyPath.containsKey( path ) ) {
|
}
|
||||||
if ( LOG.isTraceEnabled() ) {
|
|
||||||
LOG.tracev(
|
protected void addPropertyPath(
|
||||||
"Skipping duplicate registration of path [{0}], existing type = [{1}], incoming type = [{2}]",
|
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,
|
path,
|
||||||
typesByPropertyPath.get( path ),
|
existingType,
|
||||||
type
|
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;
|
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(
|
/*protected void initPropertyPaths(
|
||||||
final String path,
|
final String path,
|
||||||
final Type type,
|
final Type type,
|
||||||
|
@ -189,7 +392,7 @@ public abstract class AbstractPropertyMapping implements PropertyMapping {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( path != null ) {
|
if ( path != null ) {
|
||||||
addPropertyPath( path, type, columns, columnReaders, columnReaderTemplates, formulaTemplates );
|
addPropertyPath( path, type, columns, columnReaders, columnReaderTemplates, formulaTemplates, factory );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( type.isComponentType() ) {
|
if ( type.isComponentType() ) {
|
||||||
|
@ -242,14 +445,14 @@ public abstract class AbstractPropertyMapping implements PropertyMapping {
|
||||||
if ( etype.isReferenceToPrimaryKey() ) {
|
if ( etype.isReferenceToPrimaryKey() ) {
|
||||||
if ( !hasNonIdentifierPropertyNamedId ) {
|
if ( !hasNonIdentifierPropertyNamedId ) {
|
||||||
String idpath1 = extendPath( path, EntityPersister.ENTITY_ID );
|
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 );
|
initPropertyPaths( idpath1, idtype, columns, columnReaders, columnReaderTemplates, null, factory );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( idPropName != null ) {
|
if ( idPropName != null ) {
|
||||||
String idpath2 = extendPath( path, idPropName );
|
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 );
|
initPropertyPaths( idpath2, idtype, columns, columnReaders, columnReaderTemplates, null, factory );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
@Override
|
||||||
public String toFromFragmentString() {
|
public String toFromFragmentString() {
|
||||||
return this.buffer.toString();
|
return this.buffer.toString();
|
||||||
|
|
|
@ -76,6 +76,23 @@ public abstract class JoinFragment {
|
||||||
*/
|
*/
|
||||||
public abstract void addJoin(String tableName, String alias, String[] fkColumns, String[] pkColumns, JoinType joinType, String on);
|
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.
|
* Adds a cross join to the specified table.
|
||||||
*
|
*
|
||||||
|
|
|
@ -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() {
|
public String toFromFragmentString() {
|
||||||
return afterFrom.toString();
|
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
|
* This method is a bit of a hack, and assumes
|
||||||
* that the column on the "right" side of the
|
* that the column on the "right" side of the
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.sql;
|
package org.hibernate.sql;
|
||||||
import org.hibernate.dialect.Dialect;
|
import org.hibernate.dialect.Dialect;
|
||||||
|
import org.hibernate.internal.util.StringHelper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A join that appears in a translated HQL query
|
* 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 );
|
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) {
|
private void addJoin(String tableName, String alias, String concreteAlias, String[] fkColumns, String[] pkColumns, JoinType joinType, String on) {
|
||||||
if ( !useThetaStyleInnerJoins || joinType != JoinType.INNER_JOIN ) {
|
if ( !useThetaStyleInnerJoins || joinType != JoinType.INNER_JOIN ) {
|
||||||
JoinFragment jf = dialect.createOuterJoinFragment();
|
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() {
|
public String toFromFragmentString() {
|
||||||
return afterFrom.toString();
|
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.
|
* Add the condition string to the join fragment.
|
||||||
*
|
*
|
||||||
|
@ -103,6 +150,7 @@ public class QueryJoinFragment extends JoinFragment {
|
||||||
public boolean addCondition(String condition) {
|
public boolean addCondition(String condition) {
|
||||||
// if the condition is not already there...
|
// if the condition is not already there...
|
||||||
if (
|
if (
|
||||||
|
!StringHelper.isEmpty( condition ) &&
|
||||||
afterFrom.toString().indexOf( condition.trim() ) < 0 &&
|
afterFrom.toString().indexOf( condition.trim() ) < 0 &&
|
||||||
afterWhere.toString().indexOf( condition.trim() ) < 0
|
afterWhere.toString().indexOf( condition.trim() ) < 0
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -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() {
|
public String toFromFragmentString() {
|
||||||
return afterFrom.toString();
|
return afterFrom.toString();
|
||||||
}
|
}
|
||||||
|
@ -109,4 +152,15 @@ public class Sybase11JoinFragment extends JoinFragment {
|
||||||
addJoin( tableName, alias, fkColumns, pkColumns, joinType );
|
addJoin( tableName, alias, fkColumns, pkColumns, joinType );
|
||||||
addCondition( on );
|
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 );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,6 +111,15 @@ public abstract class EntityType extends AbstractType implements AssociationType
|
||||||
this.referenceToPrimaryKey = referenceToPrimaryKey;
|
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() {
|
protected TypeFactory.TypeScope scope() {
|
||||||
return scope;
|
return scope;
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,6 +83,12 @@ public class ManyToOneType extends EntityType {
|
||||||
this.isLogicalOneToOne = isLogicalOneToOne;
|
this.isLogicalOneToOne = isLogicalOneToOne;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ManyToOneType(ManyToOneType original, String superTypeEntityName) {
|
||||||
|
super( original, superTypeEntityName );
|
||||||
|
this.ignoreNotFound = original.ignoreNotFound;
|
||||||
|
this.isLogicalOneToOne = original.isLogicalOneToOne;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean isNullable() {
|
protected boolean isNullable() {
|
||||||
return ignoreNotFound;
|
return ignoreNotFound;
|
||||||
|
|
|
@ -63,6 +63,13 @@ public class OneToOneType extends EntityType {
|
||||||
this.entityName = entityName;
|
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
|
@Override
|
||||||
public String getPropertyName() {
|
public String getPropertyName() {
|
||||||
return propertyName;
|
return propertyName;
|
||||||
|
|
|
@ -65,6 +65,10 @@ public class SpecialOneToOneType extends OneToOneType {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SpecialOneToOneType(SpecialOneToOneType original, String superTypeEntityName) {
|
||||||
|
super( original, superTypeEntityName );
|
||||||
|
}
|
||||||
|
|
||||||
public int getColumnSpan(Mapping mapping) throws MappingException {
|
public int getColumnSpan(Mapping mapping) throws MappingException {
|
||||||
return super.getIdentifierOrUniqueKeyType( mapping ).getColumnSpan( mapping );
|
return super.getIdentifierOrUniqueKeyType( mapping ).getColumnSpan( mapping );
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,6 +10,7 @@ import java.io.Serializable;
|
||||||
import javax.persistence.CascadeType;
|
import javax.persistence.CascadeType;
|
||||||
import javax.persistence.DiscriminatorValue;
|
import javax.persistence.DiscriminatorValue;
|
||||||
import javax.persistence.Entity;
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.FetchType;
|
||||||
import javax.persistence.Id;
|
import javax.persistence.Id;
|
||||||
import javax.persistence.Inheritance;
|
import javax.persistence.Inheritance;
|
||||||
import javax.persistence.InheritanceType;
|
import javax.persistence.InheritanceType;
|
||||||
|
@ -23,6 +24,7 @@ import org.junit.Test;
|
||||||
|
|
||||||
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
|
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Christian Beikov
|
* @author Christian Beikov
|
||||||
|
@ -50,7 +52,7 @@ public class MultiSingleTableLoadTest extends BaseCoreFunctionalTestCase {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestForIssue(jiraKey = "HHH-5954")
|
@TestForIssue(jiraKey = "HHH-5954")
|
||||||
public void testLoadMultipleHoldersWithDifferentSubtypes() {
|
public void testEagerLoadMultipleHoldersWithDifferentSubtypes() {
|
||||||
createTestData();
|
createTestData();
|
||||||
doInHibernate( this::sessionFactory, session -> {
|
doInHibernate( this::sessionFactory, session -> {
|
||||||
Holder task1 = session.find( Holder.class, 1L );
|
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
|
@Override
|
||||||
protected boolean isCleanupTestDataRequired() {
|
protected boolean isCleanupTestDataRequired() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Entity
|
@Entity(name = "Holder")
|
||||||
@Table(name = "holder")
|
@Table(name = "holder")
|
||||||
public static class Holder implements Serializable {
|
public static class Holder implements Serializable {
|
||||||
@Id
|
@Id
|
||||||
|
|
Loading…
Reference in New Issue