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
619a3f8445
commit
97b0c635f8
|
@ -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,18 +283,29 @@ 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] );
|
||||
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.length - 1 ) {
|
||||
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 " );
|
||||
joinFragment.addFromFragmentString( withClauseFragment );
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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 )
|
||||
);
|
||||
|
||||
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,
|
||||
join.resolveAliasedLeftHandSideJoinConditionColumns( lhsTableAlias ),
|
||||
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 )
|
||||
|
|
|
@ -5404,6 +5404,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 ) {
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
) {
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
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.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
|
||||
|
|
Loading…
Reference in New Issue