From 8312db1b9e7acdaeb40151136805a1600ae2d64f Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Wed, 1 Mar 2023 15:34:37 +0100 Subject: [PATCH] HHH-16191 Add test and fix checks for skipping resolving polymorphic associated entity instances --- .../sql/results/graph/Initializer.java | 12 +- .../entity/AbstractEntityInitializer.java | 42 +- .../EntityJoinedFetchInitializer.java | 5 + .../LoadANonExistingNotFoundEntityTest.java | 14 +- ...tOverrideAsPersistentMappedSuperclass.java | 5 - ...nsientOverrideAsPersistentSingleTable.java | 5 - ...ientOverrideAsPersistentTablePerClass.java | 5 - ...entOverrideAsPersistentWithEmbeddable.java | 411 ++++++++++++++++++ 8 files changed, 457 insertions(+), 42 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/TransientOverrideAsPersistentWithEmbeddable.java diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/Initializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/Initializer.java index e3ee2362a4..f95e0fcb5b 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/Initializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/Initializer.java @@ -78,15 +78,15 @@ public interface Initializer { default boolean isAttributeAssignableToConcreteDescriptor( FetchParentAccess parentAccess, AttributeMapping referencedModelPart) { - if ( parentAccess != null && parentAccess.isEntityInitializer() ) { - final EntityPersister concreteDescriptor = parentAccess.findFirstEntityInitializer() - .getConcreteDescriptor(); + final EntityInitializer entityInitializer = parentAccess == null ? + null : + parentAccess.findFirstEntityInitializer(); + if ( entityInitializer != null ) { + final EntityPersister concreteDescriptor = entityInitializer.getConcreteDescriptor(); if ( concreteDescriptor.getEntityMetamodel().isPolymorphic() ) { final EntityPersister declaringType = (EntityPersister) referencedModelPart.getDeclaringType(); if ( concreteDescriptor != declaringType ) { - if ( !declaringType.getSubclassEntityNames().contains( concreteDescriptor.getEntityName() ) ) { - return false; - } + return declaringType.getSubclassEntityNames().contains( concreteDescriptor.getEntityName() ); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/AbstractEntityInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/AbstractEntityInitializer.java index 171439fa0f..b987dc1819 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/AbstractEntityInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/AbstractEntityInitializer.java @@ -98,7 +98,7 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces private EntityKey entityKey; private Object entityInstance; private Object entityInstanceForNotify; - private boolean missing; + protected boolean missing; boolean isInitialized; private boolean isOwningInitializer; private Object[] resolvedEntityState; @@ -399,26 +399,44 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces } } - private boolean shouldSkipResolveInstance(RowProcessingState rowProcessingState) { - final NavigablePath parent = navigablePath.getParent(); - if ( parent != null ) { - final Initializer parentInitializer = rowProcessingState.resolveInitializer( parent ); - if ( parentInitializer != null && parentInitializer.isEntityInitializer() ) { - if ( isReferencedModelPartAssignableToConcreteParent( parentInitializer ) ) { - return true; + protected boolean shouldSkipResolveInstance(RowProcessingState rowProcessingState) { + if ( navigablePath.getParent() != null ) { + Initializer parentInitializer = rowProcessingState.resolveInitializer( navigablePath.getParent() ); + if ( parentInitializer != null ) { + ModelPart modelPart = referencedModelPart; + NavigablePath currentNavigablePath = navigablePath; + // Walk back initializers until we find an EntityInitializer + while ( parentInitializer != null && !parentInitializer.isEntityInitializer() ) { + modelPart = parentInitializer.getInitializedPart(); + currentNavigablePath = currentNavigablePath.getParent(); + parentInitializer = rowProcessingState.resolveInitializer( currentNavigablePath.getParent() ); + } + if ( parentInitializer != null && parentInitializer.asEntityInitializer() + .getEntityDescriptor() + .getEntityMetamodel() + .isPolymorphic() ) { + parentInitializer.resolveKey( rowProcessingState ); + return isReferencedModelPartInConcreteParent( + modelPart, + currentNavigablePath, + parentInitializer + ); } } } return false; } - private boolean isReferencedModelPartAssignableToConcreteParent(Initializer parentInitializer) { + private boolean isReferencedModelPartInConcreteParent( + ModelPart modelPart, + NavigablePath partNavigablePath, + Initializer parentInitializer) { final EntityPersister parentConcreteDescriptor = parentInitializer.asEntityInitializer() .getConcreteDescriptor(); - if ( parentConcreteDescriptor != null && parentConcreteDescriptor.getEntityMetamodel().isPolymorphic()) { - final ModelPart concreteModelPart = parentConcreteDescriptor.findByPath( navigablePath.getLocalName() ); + if ( parentConcreteDescriptor != null && parentConcreteDescriptor.getEntityMetamodel().isPolymorphic() ) { + final ModelPart concreteModelPart = parentConcreteDescriptor.findByPath( partNavigablePath.getLocalName() ); if ( concreteModelPart == null - || !referencedModelPart.getJavaType().getJavaTypeClass() + || !modelPart.getJavaType().getJavaTypeClass() .isAssignableFrom( concreteModelPart.getJavaType().getJavaTypeClass() ) ) { /* Given: diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityJoinedFetchInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityJoinedFetchInitializer.java index 109e5a854c..b41f77fca7 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityJoinedFetchInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityJoinedFetchInitializer.java @@ -71,6 +71,11 @@ public class EntityJoinedFetchInitializer extends AbstractEntityInitializer { @Override public void resolveKey(RowProcessingState rowProcessingState) { + if ( shouldSkipResolveInstance( rowProcessingState ) ) { + missing = true; + return; + } + super.resolveKey( rowProcessingState ); // super processes the foreign-key target column. here we diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/proxy/LoadANonExistingNotFoundEntityTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/proxy/LoadANonExistingNotFoundEntityTest.java index 7e38757e0d..d8eb51ae98 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/proxy/LoadANonExistingNotFoundEntityTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/proxy/LoadANonExistingNotFoundEntityTest.java @@ -67,10 +67,8 @@ public class LoadANonExistingNotFoundEntityTest extends BaseNonConfigCoreFunctio } ); - // The Employee#employer must be initialized immediately because - // enhanced proxies (and HibernateProxy objects) should never be created - // for a "not found" association. - assertEquals( 2, statistics.getPrepareStatementCount() ); + // we should get 1 query for the Employee with join + assertEquals( 1, statistics.getPrepareStatementCount() ); } @Test @@ -86,10 +84,8 @@ public class LoadANonExistingNotFoundEntityTest extends BaseNonConfigCoreFunctio } ); - // The Employee#employer must be initialized immediately because - // enhanced proxies (and HibernateProxy objects) should never be created - // for a "not found" association. - assertEquals( 2, statistics.getPrepareStatementCount() ); + // we should get 1 query for the Employee with join + assertEquals( 1, statistics.getPrepareStatementCount() ); } @Test @@ -178,7 +174,7 @@ public class LoadANonExistingNotFoundEntityTest extends BaseNonConfigCoreFunctio private String name; - @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) + @ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL) @JoinColumn(name = "employer_id",foreignKey = @ForeignKey(value= ConstraintMode.NO_CONSTRAINT)) @NotFound(action=NotFoundAction.IGNORE) private Employer employer; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/TransientOverrideAsPersistentMappedSuperclass.java b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/TransientOverrideAsPersistentMappedSuperclass.java index 60aa50f9f1..b28e086c40 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/TransientOverrideAsPersistentMappedSuperclass.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/TransientOverrideAsPersistentMappedSuperclass.java @@ -7,10 +7,6 @@ package org.hibernate.orm.test.inheritance; import java.util.List; - -import org.hibernate.annotations.NotFound; -import org.hibernate.annotations.NotFoundAction; - import jakarta.persistence.Column; import jakarta.persistence.ConstraintMode; import jakarta.persistence.DiscriminatorColumn; @@ -307,7 +303,6 @@ public class TransientOverrideAsPersistentMappedSuperclass { // Editor#title (which uses the same e_title column) can be non-null, // and there is no associated group. @ManyToOne(optional = false) - @NotFound(action = NotFoundAction.IGNORE) @JoinColumn(name = "e_title", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT)) public Group getGroup() { return group; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/TransientOverrideAsPersistentSingleTable.java b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/TransientOverrideAsPersistentSingleTable.java index a09b64d715..e01a9d3fdf 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/TransientOverrideAsPersistentSingleTable.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/TransientOverrideAsPersistentSingleTable.java @@ -7,10 +7,6 @@ package org.hibernate.orm.test.inheritance; import java.util.List; - -import org.hibernate.annotations.NotFound; -import org.hibernate.annotations.NotFoundAction; - import jakarta.persistence.Column; import jakarta.persistence.ConstraintMode; import jakarta.persistence.DiscriminatorColumn; @@ -300,7 +296,6 @@ public class TransientOverrideAsPersistentSingleTable { // Editor#title (which uses the same e_title column) can be non-null, // and there is no associated group. @ManyToOne(optional = false) - @NotFound(action = NotFoundAction.IGNORE) @JoinColumn(name = "e_title", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT)) public Group getGroup() { return group; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/TransientOverrideAsPersistentTablePerClass.java b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/TransientOverrideAsPersistentTablePerClass.java index ba8ea3e813..f897ddc93b 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/TransientOverrideAsPersistentTablePerClass.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/TransientOverrideAsPersistentTablePerClass.java @@ -8,10 +8,6 @@ package org.hibernate.orm.test.inheritance; import java.util.Comparator; import java.util.List; - -import org.hibernate.annotations.NotFound; -import org.hibernate.annotations.NotFoundAction; - import jakarta.persistence.Column; import jakarta.persistence.ConstraintMode; import jakarta.persistence.DiscriminatorColumn; @@ -303,7 +299,6 @@ public class TransientOverrideAsPersistentTablePerClass { // Editor#title (which uses the same e_title column) can be non-null, // and there is no associated group. @ManyToOne(optional = false) - @NotFound(action = NotFoundAction.IGNORE) @JoinColumn(name = "e_title", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT)) public Group getGroup() { return group; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/TransientOverrideAsPersistentWithEmbeddable.java b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/TransientOverrideAsPersistentWithEmbeddable.java new file mode 100644 index 0000000000..5dc6a5627d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/TransientOverrideAsPersistentWithEmbeddable.java @@ -0,0 +1,411 @@ +/* + * 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.orm.test.inheritance; + +import java.util.List; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Column; +import jakarta.persistence.ConstraintMode; +import jakarta.persistence.DiscriminatorColumn; +import jakarta.persistence.Embeddable; +import jakarta.persistence.Entity; +import jakarta.persistence.ForeignKey; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Table; +import jakarta.persistence.Transient; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.ParameterExpression; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; + +@TestForIssue(jiraKey = "HHH-14103") +@DomainModel( + annotatedClasses = { + TransientOverrideAsPersistentWithEmbeddable.Employee.class, + TransientOverrideAsPersistentWithEmbeddable.Editor.class, + TransientOverrideAsPersistentWithEmbeddable.Writer.class, + TransientOverrideAsPersistentWithEmbeddable.Group.class, + TransientOverrideAsPersistentWithEmbeddable.Job.class + } +) +@SessionFactory +public class TransientOverrideAsPersistentWithEmbeddable { + + @Test + public void testFindByRootClass(SessionFactoryScope scope) { + scope.inTransaction( 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" ); + assertThat( writer, instanceOf( Writer.class ) ); + assertEquals( "Writing", writer.getTitle() ); + assertNotNull( ( (Writer) writer ).getWriterEmbeddable().getGroup() ); + final Group group = ( (Writer) writer ).getWriterEmbeddable().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(SessionFactoryScope scope) { + scope.inTransaction( 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.getWriterEmbeddable().getGroup() ); + final Group group = writer.getWriterEmbeddable().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(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final List employees = session.createQuery( "from Employee", Employee.class ) + .getResultList(); + assertEquals( 2, employees.size() ); + assertThat( employees.get( 0 ), instanceOf( Editor.class ) ); + assertThat( employees.get( 1 ), instanceOf( Writer.class ) ); + 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.getWriterEmbeddable().getGroup() ); + final Group group = writer.getWriterEmbeddable().getGroup(); + assertEquals( writer.getTitle(), group.getName() ); + } ); + } + + @Test + public void testQueryByRootClassAndOverridenProperty(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final Employee editor = session.createQuery( "from Employee where title=:title", Employee.class ) + .setParameter( "title", "Senior Editor" ) + .getSingleResult(); + assertThat( editor, instanceOf( Editor.class ) ); + + final Employee writer = session.createQuery( "from Employee where title=:title", Employee.class ) + .setParameter( "title", "Writing" ) + .getSingleResult(); + assertThat( writer, instanceOf( Writer.class ) ); + assertNotNull( ( (Writer) writer ).getWriterEmbeddable().getGroup() ); + assertEquals( writer.getTitle(), ( (Writer) writer ).getWriterEmbeddable().getGroup().getName() ); + } ); + } + + @Test + public void testQueryByRootClassAndOverridenPropertyTreat(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final Employee editor = session.createQuery( + "from Employee e where treat( e as Editor ).title=:title", + Employee.class + ) + .setParameter( "title", "Senior Editor" ) + .getSingleResult(); + assertThat( editor, instanceOf( Editor.class ) ); + + final Employee writer = session.createQuery( + "from Employee e where treat( e as Writer).title=:title", + Employee.class + ) + .setParameter( "title", "Writing" ) + .getSingleResult(); + assertThat( writer, instanceOf( Writer.class ) ); + assertNotNull( ( (Writer) writer ).getWriterEmbeddable().getGroup() ); + assertEquals( writer.getTitle(), ( (Writer) writer ).getWriterEmbeddable().getGroup().getName() ); + } ); + } + + @Test + public void testQueryBySublassAndOverridenProperty(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final Editor editor = session.createQuery( "from Editor where title=:title", Editor.class ) + .setParameter( "title", "Senior Editor" ) + .getSingleResult(); + assertThat( editor, instanceOf( Editor.class ) ); + + final Writer writer = session.createQuery( "from Writer where title=:title", Writer.class ) + .setParameter( "title", "Writing" ) + .getSingleResult(); + assertNotNull( writer.getWriterEmbeddable().getGroup() ); + assertEquals( writer.getTitle(), writer.getWriterEmbeddable().getGroup().getName() ); + } ); + } + + @Test + public void testCriteriaQueryByRootClassAndOverridenProperty(SessionFactoryScope scope) { + scope.inTransaction( 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(); + assertThat( editor, instanceOf( Editor.class ) ); + + 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(); + assertThat( writer, instanceOf( Writer.class ) ); + assertNotNull( ( (Writer) writer ).getWriterEmbeddable().getGroup() ); + assertEquals( writer.getTitle(), ( (Writer) writer ).getWriterEmbeddable().getGroup().getName() ); + } ); + } + + @BeforeEach + public void setupData(SessionFactoryScope scope) { + scope.inTransaction( 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 ).getWriterEmbeddable().getGroup(); + + session.persist( editor ); + session.persist( group ); + session.persist( writer ); + session.persist( jobEditor ); + session.persist( jobWriter ); + } ); + } + + @AfterEach + public void cleanupData(SessionFactoryScope scope) { + scope.inTransaction( session -> { + session.createQuery( "delete from Job" ).executeUpdate(); + session.createQuery( "delete from Employee" ).executeUpdate(); + session.createQuery( "delete from Group" ).executeUpdate(); + } ); + } + + @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(); + } + } + + @Embeddable + public static class WriterEmbeddable { + private Group 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; + } + + public void setGroup(Group group) { + this.group = group; + } + } + + @Entity(name = "Writer") + public static class Writer extends Employee { + private WriterEmbeddable writerEmbeddable; + + public Writer(String name, Group group) { + super( name ); + this.writerEmbeddable = new WriterEmbeddable(); + setGroup( 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.writerEmbeddable.setGroup( group ); + setTitle( group.getName() ); + } + + public WriterEmbeddable getWriterEmbeddable() { + return writerEmbeddable; + } + + public void setWriterEmbeddable(WriterEmbeddable writerEmbeddable) { + this.writerEmbeddable = writerEmbeddable; + } + } + + @Entity(name = "Group") + @Table(name = "WorkGroup") + public static class Group { + private String name; + + private String desctiption; + + 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; + + private String description; + + 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; + } + } +}