Skip to content

Commit b01abea

Browse files
sebersolebrmeyer
authored andcommitted
HHH-6911 - Write DiscriminatorValue to DiscriminatorColumn when combined
with InheritanceType#JOINED Conflicts: hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java
1 parent 323cc9d commit b01abea

File tree

8 files changed

+561
-66
lines changed

8 files changed

+561
-66
lines changed

hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java

Lines changed: 148 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import javax.persistence.Cacheable;
4141
import javax.persistence.CollectionTable;
4242
import javax.persistence.Column;
43+
import javax.persistence.DiscriminatorColumn;
4344
import javax.persistence.DiscriminatorType;
4445
import javax.persistence.DiscriminatorValue;
4546
import javax.persistence.ElementCollection;
@@ -95,6 +96,7 @@
9596
import org.hibernate.annotations.Check;
9697
import org.hibernate.annotations.CollectionId;
9798
import org.hibernate.annotations.Columns;
99+
import org.hibernate.annotations.DiscriminatorFormula;
98100
import org.hibernate.annotations.DiscriminatorOptions;
99101
import org.hibernate.annotations.Fetch;
100102
import org.hibernate.annotations.FetchProfile;
@@ -567,12 +569,27 @@ public static void bindClass(
567569
Ejb3JoinColumn[] inheritanceJoinedColumns = makeInheritanceJoinColumns(
568570
clazzToProcess, mappings, inheritanceState, superEntity
569571
);
570-
Ejb3DiscriminatorColumn discriminatorColumn = null;
572+
573+
final Ejb3DiscriminatorColumn discriminatorColumn;
571574
if ( InheritanceType.SINGLE_TABLE.equals( inheritanceState.getType() ) ) {
572-
discriminatorColumn = processDiscriminatorProperties(
573-
clazzToProcess, mappings, inheritanceState, entityBinder
575+
discriminatorColumn = processSingleTableDiscriminatorProperties(
576+
clazzToProcess,
577+
mappings,
578+
inheritanceState,
579+
entityBinder
580+
);
581+
}
582+
else if ( InheritanceType.JOINED.equals( inheritanceState.getType() ) ) {
583+
discriminatorColumn = processJoinedDiscriminatorProperties(
584+
clazzToProcess,
585+
mappings,
586+
inheritanceState,
587+
entityBinder
574588
);
575589
}
590+
else {
591+
discriminatorColumn = null;
592+
}
576593

577594
entityBinder.setProxy( clazzToProcess.getAnnotation( Proxy.class ) );
578595
entityBinder.setBatchSize( clazzToProcess.getAnnotation( BatchSize.class ) );
@@ -617,46 +634,71 @@ else if ( clazzToProcess.isAnnotationPresent( Table.class ) ) {
617634

618635
OnDelete onDeleteAnn = clazzToProcess.getAnnotation( OnDelete.class );
619636
boolean onDeleteAppropriate = false;
620-
if ( InheritanceType.JOINED.equals( inheritanceState.getType() ) && inheritanceState.hasParents() ) {
621-
onDeleteAppropriate = true;
622-
final JoinedSubclass jsc = ( JoinedSubclass ) persistentClass;
623-
SimpleValue key = new DependantValue( mappings, jsc.getTable(), jsc.getIdentifier() );
624-
jsc.setKey( key );
625-
ForeignKey fk = clazzToProcess.getAnnotation( ForeignKey.class );
626-
if ( fk != null && !BinderHelper.isEmptyAnnotationValue( fk.name() ) ) {
627-
key.setForeignKeyName( fk.name() );
628-
}
629-
if ( onDeleteAnn != null ) {
630-
key.setCascadeDeleteEnabled( OnDeleteAction.CASCADE.equals( onDeleteAnn.action() ) );
631-
}
632-
else {
633-
key.setCascadeDeleteEnabled( false );
634-
}
635-
//we are never in a second pass at that stage, so queue it
636-
SecondPass sp = new JoinedSubclassFkSecondPass( jsc, inheritanceJoinedColumns, key, mappings );
637-
mappings.addSecondPass( sp );
638-
mappings.addSecondPass( new CreateKeySecondPass( jsc ) );
639637

638+
// todo : sucks that this is separate from RootClass distinction
639+
final boolean isInheritanceRoot = !inheritanceState.hasParents();
640+
final boolean hasSubclasses = inheritanceState.hasSiblings();
641+
642+
if ( InheritanceType.JOINED.equals( inheritanceState.getType() ) ) {
643+
if ( inheritanceState.hasParents() ) {
644+
onDeleteAppropriate = true;
645+
final JoinedSubclass jsc = ( JoinedSubclass ) persistentClass;
646+
SimpleValue key = new DependantValue( mappings, jsc.getTable(), jsc.getIdentifier() );
647+
jsc.setKey( key );
648+
ForeignKey fk = clazzToProcess.getAnnotation( ForeignKey.class );
649+
if ( fk != null && !BinderHelper.isEmptyAnnotationValue( fk.name() ) ) {
650+
key.setForeignKeyName( fk.name() );
651+
}
652+
if ( onDeleteAnn != null ) {
653+
key.setCascadeDeleteEnabled( OnDeleteAction.CASCADE.equals( onDeleteAnn.action() ) );
654+
}
655+
else {
656+
key.setCascadeDeleteEnabled( false );
657+
}
658+
//we are never in a second pass at that stage, so queue it
659+
SecondPass sp = new JoinedSubclassFkSecondPass( jsc, inheritanceJoinedColumns, key, mappings );
660+
mappings.addSecondPass( sp );
661+
mappings.addSecondPass( new CreateKeySecondPass( jsc ) );
662+
}
663+
664+
if ( isInheritanceRoot ) {
665+
// the class we are processing is the root of the hierarchy, see if we had a discriminator column
666+
// (it is perfectly valid for joined subclasses to not have discriminators).
667+
if ( discriminatorColumn != null ) {
668+
// we have a discriminator column
669+
if ( hasSubclasses || !discriminatorColumn.isImplicit() ) {
670+
bindDiscriminatorColumnToRootPersistentClass(
671+
(RootClass) persistentClass,
672+
discriminatorColumn,
673+
entityBinder.getSecondaryTables(),
674+
propertyHolder,
675+
mappings
676+
);
677+
//bind it again since the type might have changed
678+
entityBinder.bindDiscriminatorValue();
679+
}
680+
}
681+
}
640682
}
641683
else if ( InheritanceType.SINGLE_TABLE.equals( inheritanceState.getType() ) ) {
642-
if ( ! inheritanceState.hasParents() ) {
643-
if ( inheritanceState.hasSiblings() || !discriminatorColumn.isImplicit() ) {
644-
//need a discriminator column
645-
bindDiscriminatorToPersistentClass(
646-
( RootClass ) persistentClass,
684+
if ( isInheritanceRoot ) {
685+
if ( hasSubclasses || !discriminatorColumn.isImplicit() ) {
686+
bindDiscriminatorColumnToRootPersistentClass(
687+
(RootClass) persistentClass,
647688
discriminatorColumn,
648689
entityBinder.getSecondaryTables(),
649690
propertyHolder,
650691
mappings
651692
);
652-
entityBinder.bindDiscriminatorValue();//bind it again since the type might have changed
693+
//bind it again since the type might have changed
694+
entityBinder.bindDiscriminatorValue();
653695
}
654696
}
655697
}
656-
else if ( InheritanceType.TABLE_PER_CLASS.equals( inheritanceState.getType() ) ) {
657-
//nothing to do
698+
699+
if ( onDeleteAnn != null && !onDeleteAppropriate ) {
700+
LOG.invalidOnDeleteAnnotation(propertyHolder.getEntityName());
658701
}
659-
if (onDeleteAnn != null && !onDeleteAppropriate) LOG.invalidOnDeleteAnnotation(propertyHolder.getEntityName());
660702

661703
// try to find class level generators
662704
HashMap<String, IdGenerator> classGenerators = buildLocalGenerators( clazzToProcess, mappings );
@@ -714,32 +756,43 @@ else if ( InheritanceType.TABLE_PER_CLASS.equals( inheritanceState.getType() ) )
714756

715757
}
716758

717-
// parse everything discriminator column relevant in case of single table inheritance
718-
private static Ejb3DiscriminatorColumn processDiscriminatorProperties(XClass clazzToProcess, Mappings mappings, InheritanceState inheritanceState, EntityBinder entityBinder) {
759+
/**
760+
* Process all discriminator-related metadata per rules for "single table" inheritance
761+
*/
762+
private static Ejb3DiscriminatorColumn processSingleTableDiscriminatorProperties(
763+
XClass clazzToProcess,
764+
Mappings mappings,
765+
InheritanceState inheritanceState,
766+
EntityBinder entityBinder) {
767+
final boolean isRoot = !inheritanceState.hasParents();
768+
719769
Ejb3DiscriminatorColumn discriminatorColumn = null;
720770
javax.persistence.DiscriminatorColumn discAnn = clazzToProcess.getAnnotation(
721771
javax.persistence.DiscriminatorColumn.class
722772
);
723-
DiscriminatorType discriminatorType = discAnn != null ?
724-
discAnn.discriminatorType() :
725-
DiscriminatorType.STRING;
773+
DiscriminatorType discriminatorType = discAnn != null
774+
? discAnn.discriminatorType()
775+
: DiscriminatorType.STRING;
726776

727777
org.hibernate.annotations.DiscriminatorFormula discFormulaAnn = clazzToProcess.getAnnotation(
728778
org.hibernate.annotations.DiscriminatorFormula.class
729779
);
730-
if ( !inheritanceState.hasParents() ) {
780+
if ( isRoot ) {
731781
discriminatorColumn = Ejb3DiscriminatorColumn.buildDiscriminatorColumn(
732-
discriminatorType, discAnn, discFormulaAnn, mappings
782+
discriminatorType,
783+
discAnn,
784+
discFormulaAnn,
785+
mappings
733786
);
734787
}
735-
if ( discAnn != null && inheritanceState.hasParents() ) {
788+
if ( discAnn != null && !isRoot ) {
736789
LOG.invalidDiscriminatorAnnotation( clazzToProcess.getName() );
737790
}
738791

739-
String discrimValue = clazzToProcess.isAnnotationPresent( DiscriminatorValue.class ) ?
740-
clazzToProcess.getAnnotation( DiscriminatorValue.class ).value() :
741-
null;
742-
entityBinder.setDiscriminatorValue( discrimValue );
792+
final String discriminatorValue = clazzToProcess.isAnnotationPresent( DiscriminatorValue.class )
793+
? clazzToProcess.getAnnotation( DiscriminatorValue.class ).value()
794+
: null;
795+
entityBinder.setDiscriminatorValue( discriminatorValue );
743796

744797
DiscriminatorOptions discriminatorOptions = clazzToProcess.getAnnotation( DiscriminatorOptions.class );
745798
if ( discriminatorOptions != null) {
@@ -750,6 +803,53 @@ private static Ejb3DiscriminatorColumn processDiscriminatorProperties(XClass cla
750803
return discriminatorColumn;
751804
}
752805

806+
/**
807+
* Process all discriminator-related metadata per rules for "joined" inheritance
808+
*/
809+
private static Ejb3DiscriminatorColumn processJoinedDiscriminatorProperties(
810+
XClass clazzToProcess,
811+
Mappings mappings,
812+
InheritanceState inheritanceState,
813+
EntityBinder entityBinder) {
814+
if ( clazzToProcess.isAnnotationPresent( DiscriminatorFormula.class ) ) {
815+
throw new MappingException( "@DiscriminatorFormula on joined inheritance not supported at this time" );
816+
}
817+
818+
819+
// DiscriminatorValue handling ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
820+
821+
final DiscriminatorValue discriminatorValueAnnotation = clazzToProcess.getAnnotation( DiscriminatorValue.class );
822+
final String discriminatorValue = discriminatorValueAnnotation != null
823+
? clazzToProcess.getAnnotation( DiscriminatorValue.class ).value()
824+
: null;
825+
entityBinder.setDiscriminatorValue( discriminatorValue );
826+
827+
828+
// DiscriminatorColumn handling ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
829+
830+
final DiscriminatorColumn discriminatorColumnAnnotation = clazzToProcess.getAnnotation( DiscriminatorColumn.class );
831+
if ( !inheritanceState.hasParents() ) {
832+
if ( discriminatorColumnAnnotation != null || mappings.useImplicitDiscriminatorColumnForJoinedInheritance() ) {
833+
final DiscriminatorType discriminatorType = discriminatorColumnAnnotation != null
834+
? discriminatorColumnAnnotation.discriminatorType()
835+
: DiscriminatorType.STRING;
836+
return Ejb3DiscriminatorColumn.buildDiscriminatorColumn(
837+
discriminatorType,
838+
discriminatorColumnAnnotation,
839+
null,
840+
mappings
841+
);
842+
}
843+
}
844+
else {
845+
if ( discriminatorColumnAnnotation != null ) {
846+
LOG.invalidDiscriminatorAnnotation( clazzToProcess.getName() );
847+
}
848+
}
849+
850+
return null;
851+
}
852+
753853
private static void processIdPropertiesIfNotAlready(
754854
Map<XClass, InheritanceState> inheritanceStatePerClass,
755855
Mappings mappings,
@@ -1308,7 +1408,7 @@ private static void bindTypeDef(TypeDef defAnn, Mappings mappings) {
13081408
}
13091409

13101410

1311-
private static void bindDiscriminatorToPersistentClass(
1411+
private static void bindDiscriminatorColumnToRootPersistentClass(
13121412
RootClass rootClass,
13131413
Ejb3DiscriminatorColumn discriminatorColumn,
13141414
Map<String, Join> secondaryTables,
@@ -1320,10 +1420,10 @@ private static void bindDiscriminatorToPersistentClass(
13201420
}
13211421
discriminatorColumn.setJoins( secondaryTables );
13221422
discriminatorColumn.setPropertyHolder( propertyHolder );
1323-
SimpleValue discrim = new SimpleValue( mappings, rootClass.getTable() );
1324-
rootClass.setDiscriminator( discrim );
1325-
discriminatorColumn.linkWithValue( discrim );
1326-
discrim.setTypeName( discriminatorColumn.getDiscriminatorTypeName() );
1423+
SimpleValue discriminatorColumnBinding = new SimpleValue( mappings, rootClass.getTable() );
1424+
rootClass.setDiscriminator( discriminatorColumnBinding );
1425+
discriminatorColumn.linkWithValue( discriminatorColumnBinding );
1426+
discriminatorColumnBinding.setTypeName( discriminatorColumn.getDiscriminatorTypeName() );
13271427
rootClass.setPolymorphic( true );
13281428
if ( LOG.isTraceEnabled() ) {
13291429
LOG.tracev( "Setting discriminator for entity {0}", rootClass.getEntityName() );

hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -607,7 +607,22 @@ public interface AvailableSettings {
607607

608608
public static final String FORCE_DISCRIMINATOR_IN_SELECTS_BY_DEFAULT = "hibernate.discriminator.force_in_select";
609609

610-
public static final String ENABLE_LAZY_LOAD_NO_TRANS = "hibernate.enable_lazy_load_no_trans";
610+
/**
611+
* The legacy behavior of Hibernate is to not use discriminators for joined inheritance (Hibernate does not need
612+
* the discriminator...). However, some JPA providers do need the discriminator for handling joined inheritance.
613+
* In the interest of portability this capability has been added to Hibernate too.
614+
* <p/>
615+
* However, we want to make sure that legacy applications continue to work as well. Which puts us in a bind in
616+
* terms of how to handle "implicit" discriminator mappings. The solution is to assume that the absence of
617+
* discriminator metadata means to follow the legacy behavior *unless* this setting is enabled. With this setting
618+
* enabled, Hibernate will interpret the absence of discriminator metadata as an indication to use the JPA
619+
* defined defaults for these absent annotations.
620+
*
621+
* See Hibernate Jira issue HHH-6911 for additional background info,
622+
*/
623+
public static final String IMPLICIT_DISCRIMINATOR_COLUMNS_FOR_JOINED_SUBCLASS = "hibernate.discriminator.implicit_for_joined";
624+
625+
public static final String ENABLE_LAZY_LOAD_NO_TRANS = "hibernate.enable_lazy_load_no_trans";
611626

612627
public static final String HQL_BULK_ID_STRATEGY = "hibernate.hql.bulk_id_strategy";
613628

@@ -677,4 +692,5 @@ public interface AvailableSettings {
677692
String LOG_SESSION_METRICS = "hibernate.session.events.log";
678693

679694
String AUTO_SESSION_EVENTS_LISTENER = "hibernate.session.events.auto";
695+
680696
}

hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3161,6 +3161,7 @@ public void addToOneAndIdProperty(XClass entityType, PropertyData property) {
31613161
private Boolean useNewGeneratorMappings;
31623162

31633163
@SuppressWarnings({ "UnnecessaryUnboxing" })
3164+
@Override
31643165
public boolean useNewGeneratorMappings() {
31653166
if ( useNewGeneratorMappings == null ) {
31663167
final String booleanName = getConfigurationProperties()
@@ -3170,6 +3171,20 @@ public boolean useNewGeneratorMappings() {
31703171
return useNewGeneratorMappings.booleanValue();
31713172
}
31723173

3174+
3175+
private Boolean implicitDiscriminatorColumnForJoinedInheritance;
3176+
3177+
@Override
3178+
public boolean useImplicitDiscriminatorColumnForJoinedInheritance() {
3179+
if ( implicitDiscriminatorColumnForJoinedInheritance == null ) {
3180+
final String booleanName = getConfigurationProperties()
3181+
.getProperty( AvailableSettings.IMPLICIT_DISCRIMINATOR_COLUMNS_FOR_JOINED_SUBCLASS );
3182+
implicitDiscriminatorColumnForJoinedInheritance = Boolean.valueOf( booleanName );
3183+
}
3184+
return implicitDiscriminatorColumnForJoinedInheritance;
3185+
}
3186+
3187+
31733188
private Boolean useNationalizedCharacterData;
31743189

31753190
@Override

hibernate-core/src/main/java/org/hibernate/cfg/Ejb3DiscriminatorColumn.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@
3434
* @author Emmanuel Bernard
3535
*/
3636
public class Ejb3DiscriminatorColumn extends Ejb3Column {
37-
private static final String DEFAULT_DISCRIMINATOR_COLUMN_NAME = "DTYPE";
38-
private static final String DEFAULT_DISCRIMINATOR_TYPE = "string";
37+
public static final String DEFAULT_DISCRIMINATOR_COLUMN_NAME = "DTYPE";
38+
public static final String DEFAULT_DISCRIMINATOR_TYPE = "string";
3939
private static final int DEFAULT_DISCRIMINATOR_LENGTH = 31;
4040

4141
private String discriminatorTypeName;

hibernate-core/src/main/java/org/hibernate/cfg/Mappings.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,17 @@ public PropertyReference(String referencedClass, String propertyName, boolean un
747747
*/
748748
public boolean useNewGeneratorMappings();
749749

750+
/**
751+
* Should we handle discriminators for joined inheritance per legacy Hibernate rules, or
752+
* Should we use the new generator strategy mappings. This is controlled by the
753+
* {@link AvailableSettings#USE_NEW_ID_GENERATOR_MAPPINGS} setting.
754+
*
755+
* @return True if the new generators should be used, false otherwise.
756+
*
757+
* @see AvailableSettings#IMPLICIT_DISCRIMINATOR_COLUMNS_FOR_JOINED_SUBCLASS
758+
*/
759+
public boolean useImplicitDiscriminatorColumnForJoinedInheritance();
760+
750761
/**
751762
* Should we use nationalized variants of character data by default? This is controlled by the
752763
* {@link AvailableSettings#USE_NATIONALIZED_CHARACTER_DATA} setting.

0 commit comments

Comments
 (0)