From b050a7c1bc15441d744ad2fdd2c69dbe6438bf89 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Tue, 21 Jul 2020 17:30:42 -0700 Subject: [PATCH] HHH-14103 : Add test cases showing that an entity's transient attribute can be overridden to be persistent in entity subclasses --- .../TransientOverrideAsPersistentJoined.java | 380 +++++++++++++++++ ...tOverrideAsPersistentMappedSuperclass.java | 381 ++++++++++++++++++ ...nsientOverrideAsPersistentSingleTable.java | 377 +++++++++++++++++ ...ientOverrideAsPersistentTablePerClass.java | 376 +++++++++++++++++ 4 files changed, 1514 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/inheritance/TransientOverrideAsPersistentJoined.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/inheritance/TransientOverrideAsPersistentMappedSuperclass.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/inheritance/TransientOverrideAsPersistentSingleTable.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/inheritance/TransientOverrideAsPersistentTablePerClass.java diff --git a/hibernate-core/src/test/java/org/hibernate/test/inheritance/TransientOverrideAsPersistentJoined.java b/hibernate-core/src/test/java/org/hibernate/test/inheritance/TransientOverrideAsPersistentJoined.java new file mode 100644 index 0000000000..0901121872 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/inheritance/TransientOverrideAsPersistentJoined.java @@ -0,0 +1,380 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.inheritance; + +import java.util.List; +import javax.persistence.Column; +import javax.persistence.ConstraintMode; +import javax.persistence.DiscriminatorColumn; +import javax.persistence.Entity; +import javax.persistence.ForeignKey; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToOne; +import javax.persistence.Table; +import javax.persistence.Transient; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.ParameterExpression; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; + +import org.hibernate.testing.FailureExpected; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +@TestForIssue(jiraKey = "HHH-14103") +public class TransientOverrideAsPersistentJoined extends BaseNonConfigCoreFunctionalTestCase { + + @Test + public void testFindByRootClass() { + doInHibernate( this::sessionFactory, session -> { + final Employee editor = session.find( Employee.class, "Jane Smith" ); + assertNotNull( editor ); + assertEquals( "Senior Editor", editor.getTitle() ); + final Employee writer = session.find( Employee.class, "John Smith" ); + assertTrue( Writer.class.isInstance( writer ) ); + assertEquals( "Writing", writer.getTitle() ); + assertNotNull( ( (Writer) writer ).getGroup() ); + final Group group = ( (Writer) writer ).getGroup(); + assertEquals( writer.getTitle(), group.getName() ); + final Job jobEditor = session.find( Job.class, "Edit" ); + assertSame( editor, jobEditor.getEmployee() ); + final Job jobWriter = session.find( Job.class, "Write" ); + assertSame( writer, jobWriter.getEmployee() ); + }); + } + + @Test + public void testFindBySubclass() { + doInHibernate( this::sessionFactory, session -> { + final Editor editor = session.find( Editor.class, "Jane Smith" ); + assertNotNull( editor ); + assertEquals( "Senior Editor", editor.getTitle() ); + final Writer writer = session.find( Writer.class, "John Smith" ); + assertEquals( "Writing", writer.getTitle() ); + assertNotNull( writer.getGroup() ); + final Group group = writer.getGroup(); + assertEquals( writer.getTitle(), group.getName() ); + final Job jobEditor = session.find( Job.class, "Edit" ); + assertSame( editor, jobEditor.getEmployee() ); + final Job jobWriter = session.find( Job.class, "Write" ); + assertSame( writer, jobWriter.getEmployee() ); + }); + } + + @Test + public void testQueryByRootClass() { + doInHibernate( this::sessionFactory, session -> { + final List employees = session.createQuery( "from Employee", Employee.class ) + .getResultList(); + assertEquals( 2, employees.size() ); + assertTrue( Editor.class.isInstance( employees.get( 0 ) ) ); + assertTrue( Writer.class.isInstance( employees.get( 1 ) ) ); + final Editor editor = (Editor) employees.get( 0 ); + assertEquals( "Senior Editor", editor.getTitle() ); + final Writer writer = (Writer) employees.get( 1 ); + assertEquals( "Writing", writer.getTitle() ); + assertNotNull( writer.getGroup() ); + final Group group = writer.getGroup(); + assertEquals( writer.getTitle(), group.getName() ); + }); + } + + @Test + @FailureExpected( jiraKey = "HHH-12981") + public void testQueryByRootClassAndOverridenProperty() { + doInHibernate( this::sessionFactory, session -> { + final Employee editor = session.createQuery( "from Employee where title=:title", Employee.class ) + .setParameter( "title", "Senior Editor" ) + .getSingleResult(); + assertTrue( Editor.class.isInstance( editor ) ); + + final Employee writer = session.createQuery( "from Employee where title=:title", Employee.class ) + .setParameter( "title", "Writing" ) + .getSingleResult(); + assertTrue( Writer.class.isInstance( writer ) ); + assertNotNull( ( (Writer) writer ).getGroup() ); + assertEquals( writer.getTitle(), ( (Writer) writer ).getGroup() .getName() ); + }); + } + + @Test + @FailureExpected( jiraKey = "HHH-12981") + public void testQueryByRootClassAndOverridenPropertyTreat() { + doInHibernate( this::sessionFactory, session -> { + final Employee editor = session.createQuery( "from Employee e where treat( e as Editor ).title=:title", Employee.class ) + .setParameter( "title", "Senior Editor" ) + .getSingleResult(); + assertTrue( Editor.class.isInstance( editor ) ); + + final Employee writer = session.createQuery( "from Employee e where treat( e as Writer).title=:title", Employee.class ) + .setParameter( "title", "Writing" ) + .getSingleResult(); + assertTrue( Writer.class.isInstance( writer ) ); + assertNotNull( ( (Writer) writer ).getGroup() ); + assertEquals( writer.getTitle(), ( (Writer) writer ).getGroup() .getName() ); + }); +} + + @Test + public void testQueryBySublassAndOverridenProperty() { + doInHibernate( this::sessionFactory, session -> { + final Editor editor = session.createQuery( "from Editor where title=:title", Editor.class ) + .setParameter( "title", "Senior Editor" ) + .getSingleResult(); + assertTrue( Editor.class.isInstance( editor ) ); + + final Writer writer = session.createQuery( "from Writer where title=:title", Writer.class ) + .setParameter( "title", "Writing" ) + .getSingleResult(); + assertNotNull( writer.getGroup() ); + assertEquals( writer.getTitle(), writer.getGroup().getName() ); + }); + } + + @Test + @FailureExpected( jiraKey = "HHH-12981") + public void testCriteriaQueryByRootClassAndOverridenProperty() { + doInHibernate( this::sessionFactory, session -> { + + final CriteriaBuilder builder = session.getCriteriaBuilder(); + + final CriteriaQuery query = builder.createQuery( Employee.class ); + final Root root = query.from( Employee.class ); + final ParameterExpression parameter = builder.parameter( String.class, "title" ); + + final Predicate predicateEditor = builder.equal( + builder.treat( root, Editor.class ).get( "title" ), + parameter + ); + query.where( predicateEditor ); + final Employee editor = session.createQuery( query ) + .setParameter( "title", "Senior Editor" ) + .getSingleResult(); + assertTrue( Editor.class.isInstance( editor ) ); + + final Predicate predicateWriter = builder.equal( + builder.treat( root, Writer.class ).get( "title" ), + parameter + ); + query.where( predicateWriter ); + final Employee writer = session.createQuery( query ) + .setParameter( "title", "Writing" ) + .getSingleResult(); + assertTrue( Writer.class.isInstance( writer ) ); + assertNotNull( ( (Writer) writer ).getGroup() ); + assertEquals( writer.getTitle(), ( (Writer) writer ).getGroup() .getName() ); + }); + } + + @Before + public void setupData() { + + doInHibernate( this::sessionFactory, session -> { + Job jobEditor = new Job("Edit"); + jobEditor.setEmployee(new Editor("Jane Smith", "Senior Editor")); + Job jobWriter= new Job("Write"); + jobWriter.setEmployee(new Writer("John Smith", new Group("Writing"))); + + Employee editor = jobEditor.getEmployee(); + Employee writer = jobWriter.getEmployee(); + Group group = Writer.class.cast( writer ).getGroup(); + + session.persist( editor ); + session.persist( group ); + session.persist( writer ); + session.persist( jobEditor ); + session.persist( jobWriter ); + }); + } + + @After + public void cleanupData() { + doInHibernate( this::sessionFactory, session -> { + session.createQuery( "delete from Job" ).executeUpdate(); + session.createQuery( "delete from Employee" ).executeUpdate(); + session.createQuery( "delete from Group" ).executeUpdate(); + }); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Employee.class, + Editor.class, + Writer.class, + Group.class, + Job.class + }; + } + + @Entity(name="Employee") + @Inheritance(strategy = InheritanceType.JOINED) + @DiscriminatorColumn(name="department") + public static abstract class Employee { + private String name; + private String title; + + protected Employee(String name) { + this(); + setName(name); + } + + @Id + public String getName() { + return name; + } + + @Transient + public String getTitle() { + return title; + } + + protected Employee() { + // this form used by Hibernate + } + + protected void setName(String name) { + this.name = name; + } + + protected void setTitle(String title) { + this.title = title; + } + } + + @Entity(name = "Editor") + public static class Editor extends Employee { + public Editor(String name, String title) { + super(name); + setTitle( title ); + } + + @Column(name = "e_title") + public String getTitle() { + return super.getTitle(); + } + + public void setTitle(String title) { + super.setTitle( title ); + } + + protected Editor() { + // this form used by Hibernate + super(); + } + } + + @Entity(name = "Writer") + public static class Writer extends Employee { + private Group group; + + public Writer(String name, Group group) { + super(name); + setGroup(group); + } + + // Cannot have a constraint on e_title because + // Editor#title (which uses the same e_title column) can be non-null, + // and there is no associated group. + @ManyToOne(optional = false) + @JoinColumn(name = "e_title", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT)) + public Group getGroup() { + return group; + } + + @Column(name = "e_title", insertable = false, updatable = false) + public String getTitle() { + return super.getTitle(); + } + + public void setTitle(String title) { + super.setTitle( title ); + } + + protected Writer() { + // this form used by Hibernate + super(); + } + + protected void setGroup(Group group) { + this.group = group; + setTitle( group.getName() ); + } + } + + @Entity(name = "Group") + @Table(name = "WorkGroup") + public static class Group { + private String name; + + public Group(String name) { + this(); + setName(name); + } + + @Id + public String getName() { + return name; + } + + protected Group() { + // this form used by Hibernate + } + + protected void setName(String name) { + this.name = name; + } + } + + @Entity(name = "Job") + public static class Job { + private String name; + private Employee employee; + + public Job(String name) { + this(); + setName(name); + } + + @Id + public String getName() { + return name; + } + + @OneToOne + @JoinColumn(name="employee_name") + public Employee getEmployee() { + return employee; + } + + protected Job() { + // this form used by Hibernate + } + + protected void setName(String name) { + this.name = name; + } + + protected void setEmployee(Employee e) { + this.employee = e; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/inheritance/TransientOverrideAsPersistentMappedSuperclass.java b/hibernate-core/src/test/java/org/hibernate/test/inheritance/TransientOverrideAsPersistentMappedSuperclass.java new file mode 100644 index 0000000000..8c4be1a790 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/inheritance/TransientOverrideAsPersistentMappedSuperclass.java @@ -0,0 +1,381 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.inheritance; + +import java.util.List; +import javax.persistence.Column; +import javax.persistence.ConstraintMode; +import javax.persistence.DiscriminatorColumn; +import javax.persistence.Entity; +import javax.persistence.ForeignKey; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.MappedSuperclass; +import javax.persistence.OneToOne; +import javax.persistence.Table; +import javax.persistence.Transient; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.ParameterExpression; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +@TestForIssue(jiraKey = "HHH-14103") +public class TransientOverrideAsPersistentMappedSuperclass extends BaseNonConfigCoreFunctionalTestCase { + + @Test + public void testFindByRootClass() { + doInHibernate( this::sessionFactory, session -> { + final Employee editor = session.find( Employee.class, "Jane Smith" ); + assertNotNull( editor ); + assertEquals( "Senior Editor", editor.getTitle() ); + final Employee writer = session.find( Employee.class, "John Smith" ); + assertTrue( Writer.class.isInstance( writer ) ); + assertEquals( "Writing", writer.getTitle() ); + assertNotNull( ( (Writer) writer ).getGroup() ); + final Group group = ( (Writer) writer ).getGroup(); + assertEquals( writer.getTitle(), group.getName() ); + final Job jobEditor = session.find( Job.class, "Edit" ); + assertSame( editor, jobEditor.getEmployee() ); + final Job jobWriter = session.find( Job.class, "Write" ); + assertSame( writer, jobWriter.getEmployee() ); + }); + } + + @Test + public void testFindBySubclass() { + doInHibernate( this::sessionFactory, session -> { + final Editor editor = session.find( Editor.class, "Jane Smith" ); + assertNotNull( editor ); + assertEquals( "Senior Editor", editor.getTitle() ); + final Writer writer = session.find( Writer.class, "John Smith" ); + assertEquals( "Writing", writer.getTitle() ); + assertNotNull( writer.getGroup() ); + final Group group = writer.getGroup(); + assertEquals( writer.getTitle(), group.getName() ); + final Job jobEditor = session.find( Job.class, "Edit" ); + assertSame( editor, jobEditor.getEmployee() ); + final Job jobWriter = session.find( Job.class, "Write" ); + assertSame( writer, jobWriter.getEmployee() ); + }); + } + + @Test + public void testQueryByRootClass() { + doInHibernate( this::sessionFactory, session -> { + final List employees = session.createQuery( "from Employee", Employee.class ) + .getResultList(); + assertEquals( 2, employees.size() ); + assertTrue( Editor.class.isInstance( employees.get( 0 ) ) ); + assertTrue( Writer.class.isInstance( employees.get( 1 ) ) ); + final Editor editor = (Editor) employees.get( 0 ); + assertEquals( "Senior Editor", editor.getTitle() ); + final Writer writer = (Writer) employees.get( 1 ); + assertEquals( "Writing", writer.getTitle() ); + assertNotNull( writer.getGroup() ); + final Group group = writer.getGroup(); + assertEquals( writer.getTitle(), group.getName() ); + }); + } + + @Test + public void testQueryByRootClassAndOverridenProperty() { + doInHibernate( this::sessionFactory, session -> { + final Employee editor = session.createQuery( "from Employee where title=:title", Employee.class ) + .setParameter( "title", "Senior Editor" ) + .getSingleResult(); + assertTrue( Editor.class.isInstance( editor ) ); + + final Employee writer = session.createQuery( "from Employee where title=:title", Employee.class ) + .setParameter( "title", "Writing" ) + .getSingleResult(); + assertTrue( Writer.class.isInstance( writer ) ); + assertNotNull( ( (Writer) writer ).getGroup() ); + assertEquals( writer.getTitle(), ( (Writer) writer ).getGroup() .getName() ); + }); + } + + @Test + public void testQueryByRootClassAndOverridenPropertyTreat() { + doInHibernate( this::sessionFactory, session -> { + final Employee editor = session.createQuery( "from Employee e where treat( e as Editor ).title=:title", Employee.class ) + .setParameter( "title", "Senior Editor" ) + .getSingleResult(); + assertTrue( Editor.class.isInstance( editor ) ); + + final Employee writer = session.createQuery( "from Employee e where treat( e as Writer).title=:title", Employee.class ) + .setParameter( "title", "Writing" ) + .getSingleResult(); + assertTrue( Writer.class.isInstance( writer ) ); + assertNotNull( ( (Writer) writer ).getGroup() ); + assertEquals( writer.getTitle(), ( (Writer) writer ).getGroup() .getName() ); + }); +} + + @Test + public void testQueryBySublassAndOverridenProperty() { + doInHibernate( this::sessionFactory, session -> { + final Editor editor = session.createQuery( "from Editor where title=:title", Editor.class ) + .setParameter( "title", "Senior Editor" ) + .getSingleResult(); + assertTrue( Editor.class.isInstance( editor ) ); + + final Writer writer = session.createQuery( "from Writer where title=:title", Writer.class ) + .setParameter( "title", "Writing" ) + .getSingleResult(); + assertNotNull( writer.getGroup() ); + assertEquals( writer.getTitle(), writer.getGroup().getName() ); + }); + } + + @Test + public void testCriteriaQueryByRootClassAndOverridenProperty() { + doInHibernate( this::sessionFactory, session -> { + + final CriteriaBuilder builder = session.getCriteriaBuilder(); + + final CriteriaQuery query = builder.createQuery( Employee.class ); + final Root root = query.from( Employee.class ); + final ParameterExpression parameter = builder.parameter( String.class, "title" ); + + final Predicate predicateEditor = builder.equal( + builder.treat( root, Editor.class ).get( "title" ), + parameter + ); + query.where( predicateEditor ); + final Employee editor = session.createQuery( query ) + .setParameter( "title", "Senior Editor" ) + .getSingleResult(); + assertTrue( Editor.class.isInstance( editor ) ); + + final Predicate predicateWriter = builder.equal( + builder.treat( root, Writer.class ).get( "title" ), + parameter + ); + query.where( predicateWriter ); + final Employee writer = session.createQuery( query ) + .setParameter( "title", "Writing" ) + .getSingleResult(); + assertTrue( Writer.class.isInstance( writer ) ); + assertNotNull( ( (Writer) writer ).getGroup() ); + assertEquals( writer.getTitle(), ( (Writer) writer ).getGroup() .getName() ); + }); + } + + @Before + public void setupData() { + + doInHibernate( this::sessionFactory, session -> { + Job jobEditor = new Job("Edit"); + jobEditor.setEmployee(new Editor("Jane Smith", "Senior Editor")); + Job jobWriter= new Job("Write"); + jobWriter.setEmployee(new Writer("John Smith", new Group("Writing"))); + + Employee editor = jobEditor.getEmployee(); + Employee writer = jobWriter.getEmployee(); + Group group = Writer.class.cast( writer ).getGroup(); + + session.persist( editor ); + session.persist( group ); + session.persist( writer ); + session.persist( jobEditor ); + session.persist( jobWriter ); + }); + } + + @After + public void cleanupData() { + doInHibernate( this::sessionFactory, session -> { + session.createQuery( "delete from Job" ).executeUpdate(); + session.createQuery( "delete from Employee" ).executeUpdate(); + session.createQuery( "delete from Group" ).executeUpdate(); + }); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Employee.class, + Editor.class, + Writer.class, + Group.class, + Job.class + }; + } + + @MappedSuperclass + public static class AbstractEmployee { + private String title; + + @Transient + public String getTitle() { + return title; + } + + protected void setTitle(String title) { + this.title = title; + } + } + + @Entity(name="Employee") + @Inheritance(strategy = InheritanceType.SINGLE_TABLE) + @DiscriminatorColumn(name="department") + public static abstract class Employee extends AbstractEmployee { + private String name; + + protected Employee(String name) { + this(); + setName(name); + } + + @Id + public String getName() { + return name; + } + + protected Employee() { + // this form used by Hibernate + } + + protected void setName(String name) { + this.name = name; + } + } + + @Entity(name = "Editor") + public static class Editor extends Employee { + public Editor(String name, String title) { + super(name); + setTitle( title ); + } + + @Column(name = "e_title") + public String getTitle() { + return super.getTitle(); + } + + public void setTitle(String title) { + super.setTitle( title ); + } + + protected Editor() { + // this form used by Hibernate + super(); + } + } + + @Entity(name = "Writer") + public static class Writer extends Employee { + private Group group; + + public Writer(String name, Group group) { + super(name); + setGroup(group); + } + + // Cannot have a constraint on e_title because + // Editor#title (which uses the same e_title column) can be non-null, + // and there is no associated group. + @ManyToOne(optional = false) + @JoinColumn(name = "e_title", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT)) + public Group getGroup() { + return group; + } + + @Column(name = "e_title", insertable = false, updatable = false) + public String getTitle() { + return super.getTitle(); + } + + public void setTitle(String title) { + super.setTitle( title ); + } + + protected Writer() { + // this form used by Hibernate + super(); + } + + protected void setGroup(Group group) { + this.group = group; + setTitle( group.getName() ); + } + } + + @Entity(name = "Group") + @Table(name = "WorkGroup") + public static class Group { + private String name; + + public Group(String name) { + this(); + setName(name); + } + + @Id + public String getName() { + return name; + } + + protected Group() { + // this form used by Hibernate + } + + protected void setName(String name) { + this.name = name; + } + } + + @Entity(name = "Job") + public static class Job { + private String name; + private Employee employee; + + public Job(String name) { + this(); + setName(name); + } + + @Id + public String getName() { + return name; + } + + @OneToOne + @JoinColumn(name="employee_name") + public Employee getEmployee() { + return employee; + } + + protected Job() { + // this form used by Hibernate + } + + protected void setName(String name) { + this.name = name; + } + + protected void setEmployee(Employee e) { + this.employee = e; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/inheritance/TransientOverrideAsPersistentSingleTable.java b/hibernate-core/src/test/java/org/hibernate/test/inheritance/TransientOverrideAsPersistentSingleTable.java new file mode 100644 index 0000000000..8b504e57fc --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/inheritance/TransientOverrideAsPersistentSingleTable.java @@ -0,0 +1,377 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.inheritance; + +import java.util.List; +import javax.persistence.Column; +import javax.persistence.ConstraintMode; +import javax.persistence.DiscriminatorColumn; +import javax.persistence.Entity; +import javax.persistence.ForeignKey; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToOne; +import javax.persistence.Table; +import javax.persistence.Transient; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.ParameterExpression; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; + + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +@TestForIssue(jiraKey = "HHH-14103") +public class TransientOverrideAsPersistentSingleTable extends BaseNonConfigCoreFunctionalTestCase { + + @Test + public void testFindByRootClass() { + doInHibernate( this::sessionFactory, session -> { + final Employee editor = session.find( Employee.class, "Jane Smith" ); + assertNotNull( editor ); + assertEquals( "Senior Editor", editor.getTitle() ); + final Employee writer = session.find( Employee.class, "John Smith" ); + assertTrue( Writer.class.isInstance( writer ) ); + assertEquals( "Writing", writer.getTitle() ); + assertNotNull( ( (Writer) writer ).getGroup() ); + final Group group = ( (Writer) writer ).getGroup(); + assertEquals( writer.getTitle(), group.getName() ); + final Job jobEditor = session.find( Job.class, "Edit" ); + assertSame( editor, jobEditor.getEmployee() ); + final Job jobWriter = session.find( Job.class, "Write" ); + assertSame( writer, jobWriter.getEmployee() ); + }); + } + + @Test + public void testFindBySubclass() { + doInHibernate( this::sessionFactory, session -> { + final Editor editor = session.find( Editor.class, "Jane Smith" ); + assertNotNull( editor ); + assertEquals( "Senior Editor", editor.getTitle() ); + final Writer writer = session.find( Writer.class, "John Smith" ); + assertEquals( "Writing", writer.getTitle() ); + assertNotNull( writer.getGroup() ); + final Group group = writer.getGroup(); + assertEquals( writer.getTitle(), group.getName() ); + final Job jobEditor = session.find( Job.class, "Edit" ); + assertSame( editor, jobEditor.getEmployee() ); + final Job jobWriter = session.find( Job.class, "Write" ); + assertSame( writer, jobWriter.getEmployee() ); + }); + } + + @Test + public void testQueryByRootClass() { + doInHibernate( this::sessionFactory, session -> { + final List employees = session.createQuery( "from Employee", Employee.class ) + .getResultList(); + assertEquals( 2, employees.size() ); + assertTrue( Editor.class.isInstance( employees.get( 0 ) ) ); + assertTrue( Writer.class.isInstance( employees.get( 1 ) ) ); + final Editor editor = (Editor) employees.get( 0 ); + assertEquals( "Senior Editor", editor.getTitle() ); + final Writer writer = (Writer) employees.get( 1 ); + assertEquals( "Writing", writer.getTitle() ); + assertNotNull( writer.getGroup() ); + final Group group = writer.getGroup(); + assertEquals( writer.getTitle(), group.getName() ); + }); + } + + @Test + public void testQueryByRootClassAndOverridenProperty() { + doInHibernate( this::sessionFactory, session -> { + final Employee editor = session.createQuery( "from Employee where title=:title", Employee.class ) + .setParameter( "title", "Senior Editor" ) + .getSingleResult(); + assertTrue( Editor.class.isInstance( editor ) ); + + final Employee writer = session.createQuery( "from Employee where title=:title", Employee.class ) + .setParameter( "title", "Writing" ) + .getSingleResult(); + assertTrue( Writer.class.isInstance( writer ) ); + assertNotNull( ( (Writer) writer ).getGroup() ); + assertEquals( writer.getTitle(), ( (Writer) writer ).getGroup() .getName() ); + }); + } + + @Test + public void testQueryByRootClassAndOverridenPropertyTreat() { + doInHibernate( this::sessionFactory, session -> { + final Employee editor = session.createQuery( "from Employee e where treat( e as Editor ).title=:title", Employee.class ) + .setParameter( "title", "Senior Editor" ) + .getSingleResult(); + assertTrue( Editor.class.isInstance( editor ) ); + + final Employee writer = session.createQuery( "from Employee e where treat( e as Writer).title=:title", Employee.class ) + .setParameter( "title", "Writing" ) + .getSingleResult(); + assertTrue( Writer.class.isInstance( writer ) ); + assertNotNull( ( (Writer) writer ).getGroup() ); + assertEquals( writer.getTitle(), ( (Writer) writer ).getGroup() .getName() ); + }); +} + + @Test + public void testQueryBySublassAndOverridenProperty() { + doInHibernate( this::sessionFactory, session -> { + final Editor editor = session.createQuery( "from Editor where title=:title", Editor.class ) + .setParameter( "title", "Senior Editor" ) + .getSingleResult(); + assertTrue( Editor.class.isInstance( editor ) ); + + final Writer writer = session.createQuery( "from Writer where title=:title", Writer.class ) + .setParameter( "title", "Writing" ) + .getSingleResult(); + assertNotNull( writer.getGroup() ); + assertEquals( writer.getTitle(), writer.getGroup().getName() ); + }); + } + + @Test + public void testCriteriaQueryByRootClassAndOverridenProperty() { + doInHibernate( this::sessionFactory, session -> { + + final CriteriaBuilder builder = session.getCriteriaBuilder(); + + final CriteriaQuery query = builder.createQuery( Employee.class ); + final Root root = query.from( Employee.class ); + final ParameterExpression parameter = builder.parameter( String.class, "title" ); + + final Predicate predicateEditor = builder.equal( + builder.treat( root, Editor.class ).get( "title" ), + parameter + ); + query.where( predicateEditor ); + final Employee editor = session.createQuery( query ) + .setParameter( "title", "Senior Editor" ) + .getSingleResult(); + assertTrue( Editor.class.isInstance( editor ) ); + + final Predicate predicateWriter = builder.equal( + builder.treat( root, Writer.class ).get( "title" ), + parameter + ); + query.where( predicateWriter ); + final Employee writer = session.createQuery( query ) + .setParameter( "title", "Writing" ) + .getSingleResult(); + assertTrue( Writer.class.isInstance( writer ) ); + assertNotNull( ( (Writer) writer ).getGroup() ); + assertEquals( writer.getTitle(), ( (Writer) writer ).getGroup() .getName() ); + }); + } + + @Before + public void setupData() { + + doInHibernate( this::sessionFactory, session -> { + Job jobEditor = new Job("Edit"); + jobEditor.setEmployee(new Editor("Jane Smith", "Senior Editor")); + Job jobWriter= new Job("Write"); + jobWriter.setEmployee(new Writer("John Smith", new Group("Writing"))); + + Employee editor = jobEditor.getEmployee(); + Employee writer = jobWriter.getEmployee(); + Group group = Writer.class.cast( writer ).getGroup(); + + session.persist( editor ); + session.persist( group ); + session.persist( writer ); + session.persist( jobEditor ); + session.persist( jobWriter ); + }); + } + + @After + public void cleanupData() { + doInHibernate( this::sessionFactory, session -> { + session.createQuery( "delete from Job" ).executeUpdate(); + session.createQuery( "delete from Employee" ).executeUpdate(); + session.createQuery( "delete from Group" ).executeUpdate(); + }); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Employee.class, + Editor.class, + Writer.class, + Group.class, + Job.class + }; + } + + @Entity(name="Employee") + @Inheritance(strategy = InheritanceType.SINGLE_TABLE) + @DiscriminatorColumn(name="department") + public static abstract class Employee { + private String name; + private String title; + + protected Employee(String name) { + this(); + setName(name); + } + + @Id + public String getName() { + return name; + } + + @Transient + public String getTitle() { + return title; + } + + protected Employee() { + // this form used by Hibernate + } + + protected void setName(String name) { + this.name = name; + } + + protected void setTitle(String title) { + this.title = title; + } + } + + @Entity(name = "Editor") + public static class Editor extends Employee { + public Editor(String name, String title) { + super(name); + setTitle( title ); + } + + @Column(name = "e_title") + public String getTitle() { + return super.getTitle(); + } + + public void setTitle(String title) { + super.setTitle( title ); + } + + protected Editor() { + // this form used by Hibernate + super(); + } + } + + @Entity(name = "Writer") + public static class Writer extends Employee { + private Group group; + + public Writer(String name, Group group) { + super(name); + setGroup(group); + } + + // Cannot have a constraint on e_title because + // Editor#title (which uses the same e_title column) can be non-null, + // and there is no associated group. + @ManyToOne(optional = false) + @JoinColumn(name = "e_title", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT)) + public Group getGroup() { + return group; + } + + @Column(name = "e_title", insertable = false, updatable = false) + public String getTitle() { + return super.getTitle(); + } + + public void setTitle(String title) { + super.setTitle( title ); + } + + protected Writer() { + // this form used by Hibernate + super(); + } + + protected void setGroup(Group group) { + this.group = group; + setTitle( group.getName() ); + } + } + + @Entity(name = "Group") + @Table(name = "WorkGroup") + public static class Group { + private String name; + + public Group(String name) { + this(); + setName(name); + } + + @Id + public String getName() { + return name; + } + + protected Group() { + // this form used by Hibernate + } + + protected void setName(String name) { + this.name = name; + } + } + + @Entity(name = "Job") + public static class Job { + private String name; + private Employee employee; + + public Job(String name) { + this(); + setName(name); + } + + @Id + public String getName() { + return name; + } + + @OneToOne + @JoinColumn(name="employee_name") + public Employee getEmployee() { + return employee; + } + + protected Job() { + // this form used by Hibernate + } + + protected void setName(String name) { + this.name = name; + } + + protected void setEmployee(Employee e) { + this.employee = e; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/inheritance/TransientOverrideAsPersistentTablePerClass.java b/hibernate-core/src/test/java/org/hibernate/test/inheritance/TransientOverrideAsPersistentTablePerClass.java new file mode 100644 index 0000000000..68cbc5d209 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/inheritance/TransientOverrideAsPersistentTablePerClass.java @@ -0,0 +1,376 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.inheritance; + +import java.util.List; +import javax.persistence.Column; +import javax.persistence.ConstraintMode; +import javax.persistence.DiscriminatorColumn; +import javax.persistence.Entity; +import javax.persistence.ForeignKey; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToOne; +import javax.persistence.Table; +import javax.persistence.Transient; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.ParameterExpression; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +@TestForIssue(jiraKey = "HHH-14103") +public class TransientOverrideAsPersistentTablePerClass extends BaseNonConfigCoreFunctionalTestCase { + + @Test + public void testFindByRootClass() { + doInHibernate( this::sessionFactory, session -> { + final Employee editor = session.find( Employee.class, "Jane Smith" ); + assertNotNull( editor ); + assertEquals( "Senior Editor", editor.getTitle() ); + final Employee writer = session.find( Employee.class, "John Smith" ); + assertTrue( Writer.class.isInstance( writer ) ); + assertEquals( "Writing", writer.getTitle() ); + assertNotNull( ( (Writer) writer ).getGroup() ); + final Group group = ( (Writer) writer ).getGroup(); + assertEquals( writer.getTitle(), group.getName() ); + final Job jobEditor = session.find( Job.class, "Edit" ); + assertSame( editor, jobEditor.getEmployee() ); + final Job jobWriter = session.find( Job.class, "Write" ); + assertSame( writer, jobWriter.getEmployee() ); + }); + } + + @Test + public void testFindBySubclass() { + doInHibernate( this::sessionFactory, session -> { + final Editor editor = session.find( Editor.class, "Jane Smith" ); + assertNotNull( editor ); + assertEquals( "Senior Editor", editor.getTitle() ); + final Writer writer = session.find( Writer.class, "John Smith" ); + assertEquals( "Writing", writer.getTitle() ); + assertNotNull( writer.getGroup() ); + final Group group = writer.getGroup(); + assertEquals( writer.getTitle(), group.getName() ); + final Job jobEditor = session.find( Job.class, "Edit" ); + assertSame( editor, jobEditor.getEmployee() ); + final Job jobWriter = session.find( Job.class, "Write" ); + assertSame( writer, jobWriter.getEmployee() ); + }); + } + + @Test + public void testQueryByRootClass() { + doInHibernate( this::sessionFactory, session -> { + final List employees = session.createQuery( "from Employee", Employee.class ) + .getResultList(); + assertEquals( 2, employees.size() ); + assertTrue( Editor.class.isInstance( employees.get( 0 ) ) ); + assertTrue( Writer.class.isInstance( employees.get( 1 ) ) ); + final Editor editor = (Editor) employees.get( 0 ); + assertEquals( "Senior Editor", editor.getTitle() ); + final Writer writer = (Writer) employees.get( 1 ); + assertEquals( "Writing", writer.getTitle() ); + assertNotNull( writer.getGroup() ); + final Group group = writer.getGroup(); + assertEquals( writer.getTitle(), group.getName() ); + }); + } + + @Test + public void testQueryByRootClassAndOverridenProperty() { + doInHibernate( this::sessionFactory, session -> { + final Employee editor = session.createQuery( "from Employee where title=:title", Employee.class ) + .setParameter( "title", "Senior Editor" ) + .getSingleResult(); + assertTrue( Editor.class.isInstance( editor ) ); + + final Employee writer = session.createQuery( "from Employee where title=:title", Employee.class ) + .setParameter( "title", "Writing" ) + .getSingleResult(); + assertTrue( Writer.class.isInstance( writer ) ); + assertNotNull( ( (Writer) writer ).getGroup() ); + assertEquals( writer.getTitle(), ( (Writer) writer ).getGroup() .getName() ); + }); + } + + @Test + public void testQueryByRootClassAndOverridenPropertyTreat() { + doInHibernate( this::sessionFactory, session -> { + final Employee editor = session.createQuery( "from Employee e where treat( e as Editor ).title=:title", Employee.class ) + .setParameter( "title", "Senior Editor" ) + .getSingleResult(); + assertTrue( Editor.class.isInstance( editor ) ); + + final Employee writer = session.createQuery( "from Employee e where treat( e as Writer).title=:title", Employee.class ) + .setParameter( "title", "Writing" ) + .getSingleResult(); + assertTrue( Writer.class.isInstance( writer ) ); + assertNotNull( ( (Writer) writer ).getGroup() ); + assertEquals( writer.getTitle(), ( (Writer) writer ).getGroup() .getName() ); + }); +} + + @Test + public void testQueryBySublassAndOverridenProperty() { + doInHibernate( this::sessionFactory, session -> { + final Editor editor = session.createQuery( "from Editor where title=:title", Editor.class ) + .setParameter( "title", "Senior Editor" ) + .getSingleResult(); + assertTrue( Editor.class.isInstance( editor ) ); + + final Writer writer = session.createQuery( "from Writer where title=:title", Writer.class ) + .setParameter( "title", "Writing" ) + .getSingleResult(); + assertNotNull( writer.getGroup() ); + assertEquals( writer.getTitle(), writer.getGroup().getName() ); + }); + } + + @Test + public void testCriteriaQueryByRootClassAndOverridenProperty() { + doInHibernate( this::sessionFactory, session -> { + + final CriteriaBuilder builder = session.getCriteriaBuilder(); + + final CriteriaQuery query = builder.createQuery( Employee.class ); + final Root root = query.from( Employee.class ); + final ParameterExpression parameter = builder.parameter( String.class, "title" ); + + final Predicate predicateEditor = builder.equal( + builder.treat( root, Editor.class ).get( "title" ), + parameter + ); + query.where( predicateEditor ); + final Employee editor = session.createQuery( query ) + .setParameter( "title", "Senior Editor" ) + .getSingleResult(); + assertTrue( Editor.class.isInstance( editor ) ); + + final Predicate predicateWriter = builder.equal( + builder.treat( root, Writer.class ).get( "title" ), + parameter + ); + query.where( predicateWriter ); + final Employee writer = session.createQuery( query ) + .setParameter( "title", "Writing" ) + .getSingleResult(); + assertTrue( Writer.class.isInstance( writer ) ); + assertNotNull( ( (Writer) writer ).getGroup() ); + assertEquals( writer.getTitle(), ( (Writer) writer ).getGroup() .getName() ); + }); + } + + @Before + public void setupData() { + + doInHibernate( this::sessionFactory, session -> { + Job jobEditor = new Job("Edit"); + jobEditor.setEmployee(new Editor("Jane Smith", "Senior Editor")); + Job jobWriter= new Job("Write"); + jobWriter.setEmployee(new Writer("John Smith", new Group("Writing"))); + + Employee editor = jobEditor.getEmployee(); + Employee writer = jobWriter.getEmployee(); + Group group = Writer.class.cast( writer ).getGroup(); + + session.persist( editor ); + session.persist( group ); + session.persist( writer ); + session.persist( jobEditor ); + session.persist( jobWriter ); + }); + } + + @After + public void cleanupData() { + doInHibernate( this::sessionFactory, session -> { + session.createQuery( "delete from Job" ).executeUpdate(); + session.createQuery( "delete from Employee" ).executeUpdate(); + session.createQuery( "delete from Group" ).executeUpdate(); + }); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Employee.class, + Editor.class, + Writer.class, + Group.class, + Job.class + }; + } + + @Entity(name="Employee") + @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) + @DiscriminatorColumn(name="department") + public static abstract class Employee { + private String name; + private String title; + + protected Employee(String name) { + this(); + setName(name); + } + + @Id + public String getName() { + return name; + } + + @Transient + public String getTitle() { + return title; + } + + protected Employee() { + // this form used by Hibernate + } + + protected void setName(String name) { + this.name = name; + } + + protected void setTitle(String title) { + this.title = title; + } + } + + @Entity(name = "Editor") + public static class Editor extends Employee { + public Editor(String name, String title) { + super(name); + setTitle( title ); + } + + @Column(name = "e_title") + public String getTitle() { + return super.getTitle(); + } + + public void setTitle(String title) { + super.setTitle( title ); + } + + protected Editor() { + // this form used by Hibernate + super(); + } + } + + @Entity(name = "Writer") + public static class Writer extends Employee { + private Group group; + + public Writer(String name, Group group) { + super(name); + setGroup(group); + } + + // Cannot have a constraint on e_title because + // Editor#title (which uses the same e_title column) can be non-null, + // and there is no associated group. + @ManyToOne(optional = false) + @JoinColumn(name = "e_title", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT)) + public Group getGroup() { + return group; + } + + @Column(name = "e_title", insertable = false, updatable = false) + public String getTitle() { + return super.getTitle(); + } + + public void setTitle(String title) { + super.setTitle( title ); + } + + protected Writer() { + // this form used by Hibernate + super(); + } + + protected void setGroup(Group group) { + this.group = group; + setTitle( group.getName() ); + } + } + + @Entity(name = "Group") + @Table(name = "WorkGroup") + public static class Group { + private String name; + + public Group(String name) { + this(); + setName(name); + } + + @Id + public String getName() { + return name; + } + + protected Group() { + // this form used by Hibernate + } + + protected void setName(String name) { + this.name = name; + } + } + + @Entity(name = "Job") + public static class Job { + private String name; + private Employee employee; + + public Job(String name) { + this(); + setName(name); + } + + @Id + public String getName() { + return name; + } + + @OneToOne + @JoinColumn(name="employee_name") + public Employee getEmployee() { + return employee; + } + + protected Job() { + // this form used by Hibernate + } + + protected void setName(String name) { + this.name = name; + } + + protected void setEmployee(Employee e) { + this.employee = e; + } + } +}