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 0de3662ad0..5b2016d2f5 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java @@ -829,7 +829,11 @@ public final class AnnotationBinder { final DiscriminatorColumn discriminatorColumnAnnotation = clazzToProcess.getAnnotation( DiscriminatorColumn.class ); if ( !inheritanceState.hasParents() ) { - if ( discriminatorColumnAnnotation != null || mappings.useImplicitDiscriminatorColumnForJoinedInheritance() ) { + // we want to process the discriminator column if either: + // 1) There is an explicit DiscriminatorColumn annotation && we are not told to ignore them + // 2) There is not an explicit DiscriminatorColumn annotation && we are told to create them implicitly + if ( ( discriminatorColumnAnnotation != null && !mappings.ignoreExplicitDiscriminatorColumnForJoinedInheritance() ) + || ( discriminatorColumnAnnotation == null && mappings.useImplicitDiscriminatorColumnForJoinedInheritance() ) ) { final DiscriminatorType discriminatorType = discriminatorColumnAnnotation != null ? discriminatorColumnAnnotation.discriminatorType() : DiscriminatorType.STRING; 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 05d833c4c3..79607e9f2f 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java @@ -617,11 +617,28 @@ public interface AvailableSettings { * 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. * - * See Hibernate Jira issue HHH-6911 for additional background info, + * @see #IGNORE_EXPLICIT_DISCRIMINATOR_COLUMNS_FOR_JOINED_SUBCLASS */ public static final String IMPLICIT_DISCRIMINATOR_COLUMNS_FOR_JOINED_SUBCLASS = "hibernate.discriminator.implicit_for_joined"; + /** + * 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. + *

+ * Existing applications rely (implicitly or explicitly) on Hibernate ignoring any DiscriminatorColumn declarations + * on joined inheritance hierarchies. This setting allows these applications to maintain the legacy behavior + * of DiscriminatorColumn annotations being ignored when paired with joined inheritance. + *

+ * See Hibernate Jira issue HHH-6911 for additional background info. + * + * @see #IMPLICIT_DISCRIMINATOR_COLUMNS_FOR_JOINED_SUBCLASS + */ + public static final String IGNORE_EXPLICIT_DISCRIMINATOR_COLUMNS_FOR_JOINED_SUBCLASS = "hibernate.discriminator.ignore_explicit_for_joined"; + public static final String ENABLE_LAZY_LOAD_NO_TRANS = "hibernate.enable_lazy_load_no_trans"; public static final String HQL_BULK_ID_STRATEGY = "hibernate.hql.bulk_id_strategy"; 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 e3ce0945e6..29547d052f 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java @@ -3185,6 +3185,19 @@ public class Configuration implements Serializable { } + private Boolean ignoreExplicitDiscriminatorColumnForJoinedInheritance; + + @Override + public boolean ignoreExplicitDiscriminatorColumnForJoinedInheritance() { + if ( ignoreExplicitDiscriminatorColumnForJoinedInheritance == null ) { + final String booleanName = getConfigurationProperties() + .getProperty( AvailableSettings.IGNORE_EXPLICIT_DISCRIMINATOR_COLUMNS_FOR_JOINED_SUBCLASS ); + ignoreExplicitDiscriminatorColumnForJoinedInheritance = Boolean.valueOf( booleanName ); + } + return ignoreExplicitDiscriminatorColumnForJoinedInheritance; + } + + private Boolean useNationalizedCharacterData; @Override 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 f1e337c229..49f5c6eef6 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/Mappings.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/Mappings.java @@ -748,16 +748,26 @@ 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. + * Should we handle absent DiscriminatorColumn mappings for joined inheritance by implicitly mapping a + * discriminator column? * - * @return True if the new generators should be used, false otherwise. + * @return {@code true} indicates we should infer DiscriminatorColumn implicitly (aka, map to a discriminator + * column even without a DiscriminatorColumn annotation); {@code false} (the default) indicates that we should not. * * @see AvailableSettings#IMPLICIT_DISCRIMINATOR_COLUMNS_FOR_JOINED_SUBCLASS */ public boolean useImplicitDiscriminatorColumnForJoinedInheritance(); + /** + * Should we ignore explicit DiscriminatorColumn annotations when combined with joined inheritance? + * + * @return {@code true} indicates we should ignore explicit DiscriminatorColumn annotations; {@code false} (the + * default) indicates we should not ignore them + * + * @see AvailableSettings#IGNORE_EXPLICIT_DISCRIMINATOR_COLUMNS_FOR_JOINED_SUBCLASS + */ + public boolean ignoreExplicitDiscriminatorColumnForJoinedInheritance(); + /** * 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/test/java/org/hibernate/test/joinedsubclass/JoinedSubclassWithIgnoredExplicitDiscriminatorTest.java b/hibernate-core/src/test/java/org/hibernate/test/joinedsubclass/JoinedSubclassWithIgnoredExplicitDiscriminatorTest.java new file mode 100644 index 0000000000..9aa7d08c2d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/joinedsubclass/JoinedSubclassWithIgnoredExplicitDiscriminatorTest.java @@ -0,0 +1,122 @@ +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.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; +import static org.junit.Assert.assertTrue; + +/** + * @author Steve Ebersole + */ +@TestForIssue( jiraKey = "HHH-6911" ) +public class JoinedSubclassWithIgnoredExplicitDiscriminatorTest 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 }; + } + + @Override + protected void configure(Configuration configuration) { + super.configure( configuration ); + configuration.setProperty( AvailableSettings.IGNORE_EXPLICIT_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( "integer", dogPersister.getDiscriminatorType().getName() ); + assertEquals( "clazz_", dogPersister.getDiscriminatorColumnName() ); + assertTrue( Integer.class.isInstance( dogPersister.getDiscriminatorValue() ) ); + + p = sessionFactory().getEntityPersister( Cat.class.getName() ); + assertNotNull( p ); + final JoinedSubclassEntityPersister catPersister = assertTyping( JoinedSubclassEntityPersister.class, p ); + assertEquals( "integer", catPersister.getDiscriminatorType().getName() ); + assertEquals( "clazz_", catPersister.getDiscriminatorColumnName() ); + assertTrue( Integer.class.isInstance( 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(); + } +}