From 5329bba1ea724eabf5783c71e5127b8f84ad0fcc Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Fri, 13 Dec 2013 00:11:25 -0600 Subject: [PATCH] HHH-6911 - Write DiscriminatorValue to DiscriminatorColumn when combined with InheritanceType#JOINED --- .../org/hibernate/cfg/AnnotationBinder.java | 196 +++++++++++++----- .../org/hibernate/cfg/AvailableSettings.java | 16 ++ .../java/org/hibernate/cfg/Configuration.java | 15 ++ .../cfg/Ejb3DiscriminatorColumn.java | 4 +- .../main/java/org/hibernate/cfg/Mappings.java | 11 + .../entity/JoinedSubclassEntityPersister.java | 150 ++++++++++++-- ...SubclassWithExplicitDiscriminatorTest.java | 115 ++++++++++ ...SubclassWithImplicitDiscriminatorTest.java | 118 +++++++++++ 8 files changed, 560 insertions(+), 65 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/joinedsubclass/JoinedSubclassWithExplicitDiscriminatorTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/joinedsubclass/JoinedSubclassWithImplicitDiscriminatorTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java index d125142b66..ed00df910d 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java @@ -40,6 +40,7 @@ import javax.persistence.Basic; import javax.persistence.Cacheable; import javax.persistence.CollectionTable; import javax.persistence.Column; +import javax.persistence.DiscriminatorColumn; import javax.persistence.DiscriminatorType; import javax.persistence.DiscriminatorValue; import javax.persistence.ElementCollection; @@ -97,6 +98,7 @@ import org.hibernate.annotations.CascadeType; import org.hibernate.annotations.Check; import org.hibernate.annotations.CollectionId; import org.hibernate.annotations.Columns; +import org.hibernate.annotations.DiscriminatorFormula; import org.hibernate.annotations.DiscriminatorOptions; import org.hibernate.annotations.Fetch; import org.hibernate.annotations.FetchProfile; @@ -635,12 +637,27 @@ public final class AnnotationBinder { Ejb3JoinColumn[] inheritanceJoinedColumns = makeInheritanceJoinColumns( clazzToProcess, mappings, inheritanceState, superEntity ); - Ejb3DiscriminatorColumn discriminatorColumn = null; + + final Ejb3DiscriminatorColumn discriminatorColumn; if ( InheritanceType.SINGLE_TABLE.equals( inheritanceState.getType() ) ) { - discriminatorColumn = processDiscriminatorProperties( - clazzToProcess, mappings, inheritanceState, entityBinder + discriminatorColumn = processSingleTableDiscriminatorProperties( + clazzToProcess, + mappings, + inheritanceState, + entityBinder ); } + else if ( InheritanceType.JOINED.equals( inheritanceState.getType() ) ) { + discriminatorColumn = processJoinedDiscriminatorProperties( + clazzToProcess, + mappings, + inheritanceState, + entityBinder + ); + } + else { + discriminatorColumn = null; + } entityBinder.setProxy( clazzToProcess.getAnnotation( Proxy.class ) ); entityBinder.setBatchSize( clazzToProcess.getAnnotation( BatchSize.class ) ); @@ -685,46 +702,71 @@ public final class AnnotationBinder { OnDelete onDeleteAnn = clazzToProcess.getAnnotation( OnDelete.class ); boolean onDeleteAppropriate = false; - if ( InheritanceType.JOINED.equals( inheritanceState.getType() ) && inheritanceState.hasParents() ) { - onDeleteAppropriate = true; - final JoinedSubclass jsc = ( JoinedSubclass ) persistentClass; - SimpleValue key = new DependantValue( mappings, jsc.getTable(), jsc.getIdentifier() ); - jsc.setKey( key ); - ForeignKey fk = clazzToProcess.getAnnotation( ForeignKey.class ); - if ( fk != null && !BinderHelper.isEmptyAnnotationValue( fk.name() ) ) { - key.setForeignKeyName( fk.name() ); - } - if ( onDeleteAnn != null ) { - key.setCascadeDeleteEnabled( OnDeleteAction.CASCADE.equals( onDeleteAnn.action() ) ); - } - else { - key.setCascadeDeleteEnabled( false ); - } - //we are never in a second pass at that stage, so queue it - SecondPass sp = new JoinedSubclassFkSecondPass( jsc, inheritanceJoinedColumns, key, mappings ); - mappings.addSecondPass( sp ); - mappings.addSecondPass( new CreateKeySecondPass( jsc ) ); + // todo : sucks that this is separate from RootClass distinction + final boolean isInheritanceRoot = !inheritanceState.hasParents(); + final boolean hasSubclasses = inheritanceState.hasSiblings(); + + if ( InheritanceType.JOINED.equals( inheritanceState.getType() ) ) { + if ( inheritanceState.hasParents() ) { + onDeleteAppropriate = true; + final JoinedSubclass jsc = ( JoinedSubclass ) persistentClass; + SimpleValue key = new DependantValue( mappings, jsc.getTable(), jsc.getIdentifier() ); + jsc.setKey( key ); + ForeignKey fk = clazzToProcess.getAnnotation( ForeignKey.class ); + if ( fk != null && !BinderHelper.isEmptyAnnotationValue( fk.name() ) ) { + key.setForeignKeyName( fk.name() ); + } + if ( onDeleteAnn != null ) { + key.setCascadeDeleteEnabled( OnDeleteAction.CASCADE.equals( onDeleteAnn.action() ) ); + } + else { + key.setCascadeDeleteEnabled( false ); + } + //we are never in a second pass at that stage, so queue it + SecondPass sp = new JoinedSubclassFkSecondPass( jsc, inheritanceJoinedColumns, key, mappings ); + mappings.addSecondPass( sp ); + mappings.addSecondPass( new CreateKeySecondPass( jsc ) ); + } + + if ( isInheritanceRoot ) { + // the class we are processing is the root of the hierarchy, see if we had a discriminator column + // (it is perfectly valid for joined subclasses to not have discriminators). + if ( discriminatorColumn != null ) { + // we have a discriminator column + if ( hasSubclasses || !discriminatorColumn.isImplicit() ) { + bindDiscriminatorColumnToRootPersistentClass( + (RootClass) persistentClass, + discriminatorColumn, + entityBinder.getSecondaryTables(), + propertyHolder, + mappings + ); + //bind it again since the type might have changed + entityBinder.bindDiscriminatorValue(); + } + } + } } else if ( InheritanceType.SINGLE_TABLE.equals( inheritanceState.getType() ) ) { - if ( ! inheritanceState.hasParents() ) { - if ( inheritanceState.hasSiblings() || !discriminatorColumn.isImplicit() ) { - //need a discriminator column - bindDiscriminatorToPersistentClass( - ( RootClass ) persistentClass, + if ( isInheritanceRoot ) { + if ( hasSubclasses || !discriminatorColumn.isImplicit() ) { + bindDiscriminatorColumnToRootPersistentClass( + (RootClass) persistentClass, discriminatorColumn, entityBinder.getSecondaryTables(), propertyHolder, mappings ); - entityBinder.bindDiscriminatorValue();//bind it again since the type might have changed + //bind it again since the type might have changed + entityBinder.bindDiscriminatorValue(); } } } - else if ( InheritanceType.TABLE_PER_CLASS.equals( inheritanceState.getType() ) ) { - //nothing to do + + if ( onDeleteAnn != null && !onDeleteAppropriate ) { + LOG.invalidOnDeleteAnnotation(propertyHolder.getEntityName()); } - if (onDeleteAnn != null && !onDeleteAppropriate) LOG.invalidOnDeleteAnnotation(propertyHolder.getEntityName()); // try to find class level generators HashMap classGenerators = buildLocalGenerators( clazzToProcess, mappings ); @@ -782,32 +824,43 @@ public final class AnnotationBinder { entityBinder.processComplementaryTableDefinitions( tabAnn ); } - // parse everything discriminator column relevant in case of single table inheritance - private static Ejb3DiscriminatorColumn processDiscriminatorProperties(XClass clazzToProcess, Mappings mappings, InheritanceState inheritanceState, EntityBinder entityBinder) { + /** + * Process all discriminator-related metadata per rules for "single table" inheritance + */ + private static Ejb3DiscriminatorColumn processSingleTableDiscriminatorProperties( + XClass clazzToProcess, + Mappings mappings, + InheritanceState inheritanceState, + EntityBinder entityBinder) { + final boolean isRoot = !inheritanceState.hasParents(); + Ejb3DiscriminatorColumn discriminatorColumn = null; javax.persistence.DiscriminatorColumn discAnn = clazzToProcess.getAnnotation( javax.persistence.DiscriminatorColumn.class ); - DiscriminatorType discriminatorType = discAnn != null ? - discAnn.discriminatorType() : - DiscriminatorType.STRING; + DiscriminatorType discriminatorType = discAnn != null + ? discAnn.discriminatorType() + : DiscriminatorType.STRING; org.hibernate.annotations.DiscriminatorFormula discFormulaAnn = clazzToProcess.getAnnotation( org.hibernate.annotations.DiscriminatorFormula.class ); - if ( !inheritanceState.hasParents() ) { + if ( isRoot ) { discriminatorColumn = Ejb3DiscriminatorColumn.buildDiscriminatorColumn( - discriminatorType, discAnn, discFormulaAnn, mappings + discriminatorType, + discAnn, + discFormulaAnn, + mappings ); } - if ( discAnn != null && inheritanceState.hasParents() ) { + if ( discAnn != null && !isRoot ) { LOG.invalidDiscriminatorAnnotation( clazzToProcess.getName() ); } - String discrimValue = clazzToProcess.isAnnotationPresent( DiscriminatorValue.class ) ? - clazzToProcess.getAnnotation( DiscriminatorValue.class ).value() : - null; - entityBinder.setDiscriminatorValue( discrimValue ); + final String discriminatorValue = clazzToProcess.isAnnotationPresent( DiscriminatorValue.class ) + ? clazzToProcess.getAnnotation( DiscriminatorValue.class ).value() + : null; + entityBinder.setDiscriminatorValue( discriminatorValue ); DiscriminatorOptions discriminatorOptions = clazzToProcess.getAnnotation( DiscriminatorOptions.class ); if ( discriminatorOptions != null) { @@ -818,6 +871,53 @@ public final class AnnotationBinder { return discriminatorColumn; } + /** + * Process all discriminator-related metadata per rules for "joined" inheritance + */ + private static Ejb3DiscriminatorColumn processJoinedDiscriminatorProperties( + XClass clazzToProcess, + Mappings mappings, + InheritanceState inheritanceState, + EntityBinder entityBinder) { + if ( clazzToProcess.isAnnotationPresent( DiscriminatorFormula.class ) ) { + throw new MappingException( "@DiscriminatorFormula on joined inheritance not supported at this time" ); + } + + + // DiscriminatorValue handling ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + final DiscriminatorValue discriminatorValueAnnotation = clazzToProcess.getAnnotation( DiscriminatorValue.class ); + final String discriminatorValue = discriminatorValueAnnotation != null + ? clazzToProcess.getAnnotation( DiscriminatorValue.class ).value() + : null; + entityBinder.setDiscriminatorValue( discriminatorValue ); + + + // DiscriminatorColumn handling ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + final DiscriminatorColumn discriminatorColumnAnnotation = clazzToProcess.getAnnotation( DiscriminatorColumn.class ); + if ( !inheritanceState.hasParents() ) { + if ( discriminatorColumnAnnotation != null || mappings.useImplicitDiscriminatorColumnForJoinedInheritance() ) { + final DiscriminatorType discriminatorType = discriminatorColumnAnnotation != null + ? discriminatorColumnAnnotation.discriminatorType() + : DiscriminatorType.STRING; + return Ejb3DiscriminatorColumn.buildDiscriminatorColumn( + discriminatorType, + discriminatorColumnAnnotation, + null, + mappings + ); + } + } + else { + if ( discriminatorColumnAnnotation != null ) { + LOG.invalidDiscriminatorAnnotation( clazzToProcess.getName() ); + } + } + + return null; + } + private static void processIdPropertiesIfNotAlready( Map inheritanceStatePerClass, Mappings mappings, @@ -1375,7 +1475,7 @@ public final class AnnotationBinder { } - private static void bindDiscriminatorToPersistentClass( + private static void bindDiscriminatorColumnToRootPersistentClass( RootClass rootClass, Ejb3DiscriminatorColumn discriminatorColumn, Map secondaryTables, @@ -1387,10 +1487,10 @@ public final class AnnotationBinder { } discriminatorColumn.setJoins( secondaryTables ); discriminatorColumn.setPropertyHolder( propertyHolder ); - SimpleValue discrim = new SimpleValue( mappings, rootClass.getTable() ); - rootClass.setDiscriminator( discrim ); - discriminatorColumn.linkWithValue( discrim ); - discrim.setTypeName( discriminatorColumn.getDiscriminatorTypeName() ); + SimpleValue discriminatorColumnBinding = new SimpleValue( mappings, rootClass.getTable() ); + rootClass.setDiscriminator( discriminatorColumnBinding ); + discriminatorColumn.linkWithValue( discriminatorColumnBinding ); + discriminatorColumnBinding.setTypeName( discriminatorColumn.getDiscriminatorTypeName() ); rootClass.setPolymorphic( true ); if ( LOG.isTraceEnabled() ) { LOG.tracev( "Setting discriminator for entity {0}", rootClass.getEntityName() ); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java index 15eb96882d..54abee94c7 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java @@ -604,6 +604,21 @@ public interface AvailableSettings { String FORCE_DISCRIMINATOR_IN_SELECTS_BY_DEFAULT = "hibernate.discriminator.force_in_select"; + /** + * The legacy behavior of Hibernate is to not use discriminators for joined inheritance (Hibernate does not need + * the discriminator...). However, some JPA providers do need the discriminator for handling joined inheritance. + * In the interest of portability this capability has been added to Hibernate too. + *

+ * However, we want to make sure that legacy applications continue to work as well. Which puts us in a bind in + * terms of how to handle "implicit" discriminator mappings. The solution is to assume that the absence of + * discriminator metadata means to follow the legacy behavior *unless* this setting is enabled. With this setting + * enabled, Hibernate will interpret the absence of discriminator metadata as an indication to use the JPA + * defined defaults for these absent annotations. + * + * See Hibernate Jira issue HHH-6911 for additional background info, + */ + String IMPLICIT_DISCRIMINATOR_COLUMNS_FOR_JOINED_SUBCLASS = "hibernate.discriminator.implicit_for_joined"; + String ENABLE_LAZY_LOAD_NO_TRANS = "hibernate.enable_lazy_load_no_trans"; String HQL_BULK_ID_STRATEGY = "hibernate.hql.bulk_id_strategy"; @@ -678,4 +693,5 @@ public interface AvailableSettings { String LOG_SESSION_METRICS = "hibernate.session.events.log"; String AUTO_SESSION_EVENTS_LISTENER = "hibernate.session.events.auto"; + } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java b/hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java index d1e7854066..afdbb3d593 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java @@ -3375,6 +3375,7 @@ public class Configuration implements Serializable { } private Boolean useNewGeneratorMappings; + @Override public boolean useNewGeneratorMappings() { if ( useNewGeneratorMappings == null ) { @@ -3385,6 +3386,20 @@ public class Configuration implements Serializable { return useNewGeneratorMappings; } + + private Boolean implicitDiscriminatorColumnForJoinedInheritance; + + @Override + public boolean useImplicitDiscriminatorColumnForJoinedInheritance() { + if ( implicitDiscriminatorColumnForJoinedInheritance == null ) { + final String booleanName = getConfigurationProperties() + .getProperty( AvailableSettings.IMPLICIT_DISCRIMINATOR_COLUMNS_FOR_JOINED_SUBCLASS ); + implicitDiscriminatorColumnForJoinedInheritance = Boolean.valueOf( booleanName ); + } + return implicitDiscriminatorColumnForJoinedInheritance; + } + + private Boolean useNationalizedCharacterData; @Override diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3DiscriminatorColumn.java b/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3DiscriminatorColumn.java index 63579017c6..cf47d1dba0 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3DiscriminatorColumn.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3DiscriminatorColumn.java @@ -34,8 +34,8 @@ import org.hibernate.annotations.DiscriminatorFormula; * @author Emmanuel Bernard */ public class Ejb3DiscriminatorColumn extends Ejb3Column { - private static final String DEFAULT_DISCRIMINATOR_COLUMN_NAME = "DTYPE"; - private static final String DEFAULT_DISCRIMINATOR_TYPE = "string"; + public static final String DEFAULT_DISCRIMINATOR_COLUMN_NAME = "DTYPE"; + public static final String DEFAULT_DISCRIMINATOR_TYPE = "string"; private static final int DEFAULT_DISCRIMINATOR_LENGTH = 31; private String discriminatorTypeName; diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/Mappings.java b/hibernate-core/src/main/java/org/hibernate/cfg/Mappings.java index 6d72162a68..f2d681c124 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/Mappings.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/Mappings.java @@ -797,6 +797,17 @@ public interface Mappings { */ public boolean useNewGeneratorMappings(); + /** + * Should we handle discriminators for joined inheritance per legacy Hibernate rules, or + * Should we use the new generator strategy mappings. This is controlled by the + * {@link AvailableSettings#USE_NEW_ID_GENERATOR_MAPPINGS} setting. + * + * @return True if the new generators should be used, false otherwise. + * + * @see AvailableSettings#IMPLICIT_DISCRIMINATOR_COLUMNS_FOR_JOINED_SUBCLASS + */ + public boolean useImplicitDiscriminatorColumnForJoinedInheritance(); + /** * Should we use nationalized variants of character data by default? This is controlled by the * {@link AvailableSettings#USE_NATIONALIZED_CHARACTER_DATA} setting. diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java index 5543c12bcc..d90331606e 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java @@ -43,8 +43,10 @@ import org.hibernate.engine.spi.Mapping; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.internal.DynamicFilterAliasGenerator; import org.hibernate.internal.FilterAliasGenerator; +import org.hibernate.internal.util.MarkerObject; import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.mapping.Column; +import org.hibernate.mapping.Formula; import org.hibernate.mapping.Join; import org.hibernate.mapping.KeyValue; import org.hibernate.mapping.MappedSuperclass; @@ -53,11 +55,16 @@ import org.hibernate.mapping.Property; import org.hibernate.mapping.Selectable; import org.hibernate.mapping.Subclass; import org.hibernate.mapping.Table; +import org.hibernate.mapping.Value; import org.hibernate.metamodel.binding.EntityBinding; import org.hibernate.sql.CaseFragment; +import org.hibernate.sql.InFragment; +import org.hibernate.sql.Insert; import org.hibernate.sql.SelectFragment; -import org.hibernate.type.StandardBasicTypes; -import org.hibernate.type.Type; +import org.hibernate.type.*; +import org.hibernate.type.DiscriminatorType; + +import org.jboss.logging.Logger; /** * An EntityPersister implementing the normalized "table-per-subclass" @@ -66,6 +73,13 @@ import org.hibernate.type.Type; * @author Gavin King */ public class JoinedSubclassEntityPersister extends AbstractEntityPersister { + private static final Logger log = Logger.getLogger( JoinedSubclassEntityPersister.class ); + + private static final String IMPLICIT_DISCRIMINATOR_ALIAS = "clazz_"; + private static final Object NULL_DISCRIMINATOR = new MarkerObject(""); + private static final Object NOT_NULL_DISCRIMINATOR = new MarkerObject(""); + private static final String NULL_STRING = "null"; + private static final String NOT_NULL_STRING = "not null"; // the class hierarchy structure private final int tableSpan; @@ -116,6 +130,9 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister { private final Object discriminatorValue; private final String discriminatorSQLString; + private final DiscriminatorType discriminatorType; + private final String explicitDiscriminatorColumnName; + private final String discriminatorAlias; // Span of the tables directly mapped by this entity and super-classes, if any private final int coreTableSpan; @@ -136,15 +153,58 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister { // DISCRIMINATOR if ( persistentClass.isPolymorphic() ) { - try { - discriminatorValue = persistentClass.getSubclassId(); - discriminatorSQLString = discriminatorValue.toString(); + final Value discriminatorMapping = persistentClass.getDiscriminator(); + if ( discriminatorMapping != null ) { + log.debug( "Encountered explicit discriminator mapping for joined inheritance" ); + + final Selectable selectable = discriminatorMapping.getColumnIterator().next(); + if ( Formula.class.isInstance( selectable ) ) { + throw new MappingException( "Discriminator formulas on joined inheritance hierarchies not supported at this time" ); + } + else { + final Column column = (Column) selectable; + explicitDiscriminatorColumnName = column.getQuotedName( factory.getDialect() ); + discriminatorAlias = column.getAlias( factory.getDialect(), persistentClass.getRootTable() ); + } + discriminatorType = (DiscriminatorType) persistentClass.getDiscriminator().getType(); + if ( persistentClass.isDiscriminatorValueNull() ) { + discriminatorValue = NULL_DISCRIMINATOR; + discriminatorSQLString = InFragment.NULL; + } + else if ( persistentClass.isDiscriminatorValueNotNull() ) { + discriminatorValue = NOT_NULL_DISCRIMINATOR; + discriminatorSQLString = InFragment.NOT_NULL; + } + else { + try { + discriminatorValue = discriminatorType.stringToObject( persistentClass.getDiscriminatorValue() ); + discriminatorSQLString = discriminatorType.objectToSQLString( discriminatorValue, factory.getDialect() ); + } + catch (ClassCastException cce) { + throw new MappingException("Illegal discriminator type: " + discriminatorType.getName() ); + } + catch (Exception e) { + throw new MappingException("Could not format discriminator value to SQL string", e); + } + } } - catch ( Exception e ) { - throw new MappingException( "Could not format discriminator value to SQL string", e ); + else { + explicitDiscriminatorColumnName = null; + discriminatorAlias = IMPLICIT_DISCRIMINATOR_ALIAS; + discriminatorType = StandardBasicTypes.INTEGER; + try { + discriminatorValue = persistentClass.getSubclassId(); + discriminatorSQLString = discriminatorValue.toString(); + } + catch ( Exception e ) { + throw new MappingException( "Could not format discriminator value to SQL string", e ); + } } } else { + explicitDiscriminatorColumnName = null; + discriminatorAlias = IMPLICIT_DISCRIMINATOR_ALIAS; + discriminatorType = StandardBasicTypes.INTEGER; discriminatorValue = null; discriminatorSQLString = null; } @@ -477,12 +537,35 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister { subclassClosure[k] = sc.getEntityName(); try { if ( persistentClass.isPolymorphic() ) { - // we now use subclass ids that are consistent across all - // persisters for a class hierarchy, so that the use of - // "foo.class = Bar" works in HQL - Integer subclassId = sc.getSubclassId(); - subclassesByDiscriminatorValue.put( subclassId, sc.getEntityName() ); - discriminatorValues[k] = subclassId.toString(); + final Object discriminatorValue; + if ( explicitDiscriminatorColumnName != null ) { + if ( sc.isDiscriminatorValueNull() ) { + discriminatorValue = NULL_DISCRIMINATOR; + } + else if ( sc.isDiscriminatorValueNotNull() ) { + discriminatorValue = NOT_NULL_DISCRIMINATOR; + } + else { + try { + discriminatorValue = discriminatorType.stringToObject( sc.getDiscriminatorValue() ); + } + catch (ClassCastException cce) { + throw new MappingException( "Illegal discriminator type: " + discriminatorType.getName() ); + } + catch (Exception e) { + throw new MappingException( "Could not format discriminator value to SQL string", e); + } + } + } + else { + // we now use subclass ids that are consistent across all + // persisters for a class hierarchy, so that the use of + // "foo.class = Bar" works in HQL + discriminatorValue = sc.getSubclassId(); + } + + subclassesByDiscriminatorValue.put( discriminatorValue, sc.getEntityName() ); + discriminatorValues[k] = discriminatorValue.toString(); int id = getTableId( sc.getTable().getQualifiedName( factory.getDialect(), @@ -702,6 +785,9 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister { constraintOrderedKeyColumnNames = null; discriminatorValue = null; discriminatorSQLString = null; + discriminatorType = StandardBasicTypes.INTEGER; + explicitDiscriminatorColumnName = null; + discriminatorAlias = IMPLICIT_DISCRIMINATOR_ALIAS; coreTableSpan = -1; isNullableTable = null; subclassNamesBySubclassTable = null; @@ -729,21 +815,50 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister { } public Type getDiscriminatorType() { - return StandardBasicTypes.INTEGER; + return discriminatorType; } public Object getDiscriminatorValue() { return discriminatorValue; } + @Override public String getDiscriminatorSQLValue() { return discriminatorSQLString; } + @Override + public String getDiscriminatorColumnName() { + return explicitDiscriminatorColumnName == null + ? super.getDiscriminatorColumnName() + : explicitDiscriminatorColumnName; + } + + @Override + public String getDiscriminatorColumnReaders() { + return getDiscriminatorColumnName(); + } + + @Override + public String getDiscriminatorColumnReaderTemplate() { + return getDiscriminatorColumnName(); + } + + protected String getDiscriminatorAlias() { + return discriminatorAlias; + } + public String getSubclassForDiscriminatorValue(Object value) { return (String) subclassesByDiscriminatorValue.get( value ); } + @Override + protected void addDiscriminatorToInsert(Insert insert) { + if ( explicitDiscriminatorColumnName != null ) { + insert.addColumn( explicitDiscriminatorColumnName, getDiscriminatorSQLValue() ); + } + } + public Serializable[] getPropertySpaces() { return spaces; // don't need subclass tables, because they can't appear in conditions } @@ -858,7 +973,12 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister { public void addDiscriminatorToSelect(SelectFragment select, String name, String suffix) { if ( hasSubclasses() ) { - select.setExtraSelectList( discriminatorFragment( name ), getDiscriminatorAlias() ); + if ( explicitDiscriminatorColumnName == null ) { + select.setExtraSelectList( discriminatorFragment( name ), getDiscriminatorAlias() ); + } + else { + select.addColumn( name, explicitDiscriminatorColumnName, discriminatorAlias ); + } } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/joinedsubclass/JoinedSubclassWithExplicitDiscriminatorTest.java b/hibernate-core/src/test/java/org/hibernate/test/joinedsubclass/JoinedSubclassWithExplicitDiscriminatorTest.java new file mode 100644 index 0000000000..ab836a61ba --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/joinedsubclass/JoinedSubclassWithExplicitDiscriminatorTest.java @@ -0,0 +1,115 @@ +package org.hibernate.test.joinedsubclass; + +import javax.persistence.DiscriminatorColumn; +import javax.persistence.DiscriminatorType; +import javax.persistence.DiscriminatorValue; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.Table; + +import org.hibernate.Session; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.persister.entity.JoinedSubclassEntityPersister; +import org.hibernate.persister.entity.Loadable; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.testing.junit4.ExtraAssertions; +import org.junit.Test; + +import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * @author Steve Ebersole + */ +@TestForIssue( jiraKey = "HHH-6911" ) +public class JoinedSubclassWithExplicitDiscriminatorTest extends BaseCoreFunctionalTestCase { + @Entity( name = "Animal" ) + @Table( name = "animal" ) + @Inheritance( strategy = InheritanceType.JOINED ) + @DiscriminatorColumn( name = "type", discriminatorType = DiscriminatorType.STRING ) + @DiscriminatorValue( value = "???animal???" ) + public static abstract class Animal { + @Id + public Integer id; + + protected Animal() { + } + + protected Animal(Integer id) { + this.id = id; + } + } + + @Entity( name = "Cat" ) + @DiscriminatorValue( value = "cat" ) + public static class Cat extends Animal { + public Cat() { + super(); + } + + public Cat(Integer id) { + super( id ); + } + } + + @Entity( name = "Dog" ) + @DiscriminatorValue( value = "dog" ) + public static class Dog extends Animal { + public Dog() { + super(); + } + + public Dog(Integer id) { + super( id ); + } + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Animal.class, Cat.class, Dog.class }; + } + + @Test + public void metadataAssertions() { + EntityPersister p = sessionFactory().getEntityPersister( Dog.class.getName() ); + assertNotNull( p ); + final JoinedSubclassEntityPersister dogPersister = assertTyping( JoinedSubclassEntityPersister.class, p ); + assertEquals( "string", dogPersister.getDiscriminatorType().getName() ); + assertEquals( "type", dogPersister.getDiscriminatorColumnName() ); + assertEquals( "dog", dogPersister.getDiscriminatorValue() ); + + p = sessionFactory().getEntityPersister( Cat.class.getName() ); + assertNotNull( p ); + final JoinedSubclassEntityPersister catPersister = assertTyping( JoinedSubclassEntityPersister.class, p ); + assertEquals( "string", catPersister.getDiscriminatorType().getName() ); + assertEquals( "type", catPersister.getDiscriminatorColumnName() ); + assertEquals( "cat", catPersister.getDiscriminatorValue() ); + } + + @Test + public void basicUsageTest() { + Session session = openSession(); + session.beginTransaction(); + session.save( new Cat( 1 ) ); + session.save( new Dog( 2 ) ); + session.getTransaction().commit(); + session.close(); + + session = openSession(); + session.beginTransaction(); + session.createQuery( "from Animal" ).list(); + Cat cat = (Cat) session.get( Cat.class, 1 ); + assertNotNull( cat ); + session.delete( cat ); + Dog dog = (Dog) session.get( Dog.class, 2 ); + assertNotNull( dog ); + session.delete( dog ); + session.getTransaction().commit(); + session.close(); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/joinedsubclass/JoinedSubclassWithImplicitDiscriminatorTest.java b/hibernate-core/src/test/java/org/hibernate/test/joinedsubclass/JoinedSubclassWithImplicitDiscriminatorTest.java new file mode 100644 index 0000000000..95b1337917 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/joinedsubclass/JoinedSubclassWithImplicitDiscriminatorTest.java @@ -0,0 +1,118 @@ +package org.hibernate.test.joinedsubclass; + +import javax.persistence.DiscriminatorColumn; +import javax.persistence.DiscriminatorType; +import javax.persistence.DiscriminatorValue; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.Table; + +import org.hibernate.Session; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.cfg.Ejb3DiscriminatorColumn; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.persister.entity.JoinedSubclassEntityPersister; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * @author Steve Ebersole + */ +@TestForIssue( jiraKey = "HHH-6911" ) +public class JoinedSubclassWithImplicitDiscriminatorTest extends BaseCoreFunctionalTestCase { + @Entity( name = "Animal" ) + @Table( name = "animal" ) + @Inheritance( strategy = InheritanceType.JOINED ) + public static abstract class Animal { + @Id + public Integer id; + + protected Animal() { + } + + protected Animal(Integer id) { + this.id = id; + } + } + + @Entity( name = "Cat" ) + public static class Cat extends Animal { + public Cat() { + super(); + } + + public Cat(Integer id) { + super( id ); + } + } + + @Entity( name = "Dog" ) + public static class Dog extends Animal { + public Dog() { + super(); + } + + public Dog(Integer id) { + super( id ); + } + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Animal.class, Cat.class, Dog.class }; + } + + @Override + protected void configure(Configuration configuration) { + super.configure( configuration ); + configuration.setProperty( AvailableSettings.IMPLICIT_DISCRIMINATOR_COLUMNS_FOR_JOINED_SUBCLASS, "true" ); + } + + @Test + public void metadataAssertions() { + EntityPersister p = sessionFactory().getEntityPersister( Dog.class.getName() ); + assertNotNull( p ); + final JoinedSubclassEntityPersister dogPersister = assertTyping( JoinedSubclassEntityPersister.class, p ); + assertEquals( Ejb3DiscriminatorColumn.DEFAULT_DISCRIMINATOR_TYPE, dogPersister.getDiscriminatorType().getName() ); + assertEquals( Ejb3DiscriminatorColumn.DEFAULT_DISCRIMINATOR_COLUMN_NAME, dogPersister.getDiscriminatorColumnName() ); + assertEquals( "Dog", dogPersister.getDiscriminatorValue() ); + + p = sessionFactory().getEntityPersister( Cat.class.getName() ); + assertNotNull( p ); + final JoinedSubclassEntityPersister catPersister = assertTyping( JoinedSubclassEntityPersister.class, p ); + assertEquals( Ejb3DiscriminatorColumn.DEFAULT_DISCRIMINATOR_TYPE, catPersister.getDiscriminatorType().getName() ); + assertEquals( Ejb3DiscriminatorColumn.DEFAULT_DISCRIMINATOR_COLUMN_NAME, catPersister.getDiscriminatorColumnName() ); + assertEquals( "Cat", catPersister.getDiscriminatorValue() ); + } + + @Test + public void basicUsageTest() { + Session session = openSession(); + session.beginTransaction(); + session.save( new Cat( 1 ) ); + session.save( new Dog( 2 ) ); + session.getTransaction().commit(); + session.close(); + + session = openSession(); + session.beginTransaction(); + session.createQuery( "from Animal" ).list(); + Cat cat = (Cat) session.get( Cat.class, 1 ); + assertNotNull( cat ); + session.delete( cat ); + Dog dog = (Dog) session.get( Dog.class, 2 ); + assertNotNull( dog ); + session.delete( dog ); + session.getTransaction().commit(); + session.close(); + } +}