-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 5 posts ] 
Author Message
 Post subject: How can you implement inheritance with a List (@OneToMany) t
PostPosted: Thu Jul 03, 2008 6:39 pm 
Newbie

Joined: Wed Aug 02, 2006 6:11 am
Posts: 8
How can you implement inheritance with a List (@OneToMany) typed as a subclass, using JPA annotations ?

For my problem description I have created a simple example that I am trying to make work...
Please do not suggest introducing a many-to-many-relationship because I have an existing database structure that are using foreign key relationships that I need to reuse,
so I need to figure out how to make code like this working without using downcasting.

Except from my main problem (i.e. that I below want to be able use "List<Student>" instead of "List<Person>" below) I would actually prefer to not having any java reference to School from the Person/Student classes, i.e. I would prefer a uni-directional dependency, but as far as I have understood this bidirectional relation in the java code is necessary.

First some small pieces (the full code including the annotations can be found further down) of the three persisted classes, to illustrate their relations:
Code:
public class Person {
   public String getPersonName() {
   ...

public class Student extends Person {
   public int getGraduationYear() {
   ...

public class School {
   public List<Student> getStudents() { // this is what I want ! NOTE: This is the row that illustrates what I want to do !
   // but I have only been able to succesfully implement this:
   // public List<Person> getStudents() {
   // but with this typing of the List, the client code will need to down cast

   // Here in the School class I also might have another List, such as
   // public List<Teacher> getTeachers() { // "Teacher extends Person" (also)
   // and would not want to do downcasting to be able to use the subclass methods
   // i.e. I want the correct typing in the List


Here are some table content examples, to make it easier to understand the table relations (class relations) for the three annotated classes using these tables:

Code:
TABLE "school"
SCHOOL_ID , SCHOOL_NAME
1         , 'Harvard'
2         , 'Stanford'

TABLE "person" (PERSON_TYPE is the discrimator column with S=Student)
PERSON_ID , PERSON_NAME , SCHOOL_ID , PERSON_TYPE
1         , 'Scott'     , 1         , 'S'
2         , 'Bill'      , 1         , 'S'
3         , 'Steve'     , 2         , 'S'
4         , 'Larry'     , 2         , 'S'

TABLE "student" (PERSON_ID is both primary key and foreign key)
PERSON_ID , GRADUATION_YEAR
1         , 1980
2         , 1985
3         , 1990
4         , 1995

(there might be other kind of persons belonging to the schools also, for example a teacher table wih columns such as employment_year but for simplicity only one subclass of person is used in this example)



Now I will first show some code that will work (with the "Student" list typed as the baseclass, i.e. as a "Person" list that need to be downcasted:
(then I will show the changes that I tried with to make the code work for the desired subclass typing of the list)

Code:
public static void main(String[] args) {
   EntityManagerFactory emf = Persistence.createEntityManagerFactory("students");
   EntityManager em = emf.createEntityManager();
   School school = em.find(School.class, 1);
   System.out.println(school.getSchoolName());
   List<Person> students = school.getStudents(); // note that the basetype "Person" is used but I would like to be able to use "List<Student>"
   for (Person person : students) {
      Student student = (Student)person; // note the undesired downcasting !
      System.out.println(student.getGraduationYear());
   }

   school = new School();
   school.setSchoolName("MIT");
   Student student = new Student();
   student.setPersonName("Martin");
   student.setGraduationYear(1960);
   school.addStudent(student);
   
   EntityTransaction transaction = em.getTransaction();
   transaction.begin();
   em.persist(school);
   transaction.commit();
}

package domain;
// the imports are not displayed here ...
@Entity
@Table(name="SCHOOL")
public class School {
   public School() {
   }
   
   private Integer id;
   @Id
   @GeneratedValue(strategy = GenerationType.AUTO)
   @Column(name = "SCHOOL_ID")   
   public Integer getId() {
      return id;
   }
   public void setId(Integer id) {
      this.id = id;
   }

   private String schoolName;
   @Column(name = "SCHOOL_NAME")   
   public String getSchoolName() {
      return this.schoolName;
   }
   public void setSchoolName(String schoolName) {
      this.schoolName = schoolName;
   }   


   private List<Person> students = new Vector<Person>(); // NOTE ! I WANT to use List<Student> instead
   @OneToMany(cascade=CascadeType.ALL, mappedBy="school")
   public List<Person> getStudents() { // NOTE ! I WANT to use List<Student> instead
      return students;
   }
   public void setStudents(List<Person> students) { // NOTE ! I WANT to use List<Student> instead
      this.students = students;
      for (Person student : students) {
         student.setSchool(this);
      }
   }
   public void addStudent(Student student) {
      this.students.add(student);
   }   
}

package domain;
// the imports are not displayed here ...
@Entity
@Table(name="PERSON")
@Inheritance(strategy=InheritanceType.JOINED)
@DiscriminatorColumn(name="PERSON_TYPE", discriminatorType=DiscriminatorType.STRING)
public class Person {
   public Person() {
      this.setPersonType("P");
   }
   
   private Integer id;
   @Id
   @GeneratedValue(strategy = GenerationType.AUTO)
   @Column(name = "PERSON_ID")   
   public Integer getId() {
      return id;
   }
   public void setId(Integer id) {
      this.id = id;
   }
   
   private String personName;
   @Column(name = "PERSON_NAME")   
   public String getPersonName() {
      return this.personName;
   }
   public void setPersonName(String personName) {
      this.personName = personName;
   }
   
   private String personType;
   @Column(name = "PERSON_TYPE")   
   public String getPersonType() {
      return this.personType;
   }
   public void setPersonType(String personType) {
      this.personType = personType;
   }   

   private School school;
   @ManyToOne
   @JoinColumn(name="SCHOOL_ID")
   public School getSchool() {
      return school;
   }
   public void setSchool(School school) {
      this.school = school;
   }   
}

package domain;
// the imports are not displayed here ...
@Entity
@Table(name="STUDENT")
@DiscriminatorValue("S") // S = Student
@Inheritance(strategy=InheritanceType.JOINED)
@PrimaryKeyJoinColumn(name="PERSON_ID")
public class Student extends Person {
   public Student() {
      super.setPersonType("S");
   }

   private int graduationYear;
   @Column(name="GRADUATION_YEAR")
   public int getGraduationYear() {
      return graduationYear;
   }
   public void setGraduationYear(int employmentYear) {
      this.graduationYear = employmentYear;
   }
}



The above code works but I instead want the list of students to be typed correctly, i.e. with the actual type (Student) of the items in the list and not typed as the basetype (Person) to avoid the dirty downcasting.

I tried to modify the above code with "List<Student>" instead of "List<Person>" and I also moved the "School methods and field" to the subclass, i.e. these were the changes I tried:
(and if not moving down the School code, I get this exception: "org.hibernate.AnnotationException: mappedBy reference an unknown target entity property: domain.Student.school in domain.School.students")

Code:
...
public class School {
   ...
   private List<Student> students = new Vector<Student>();  // this is the typing I want, i.e. typed as subclass !
   @OneToMany(cascade=CascadeType.ALL, mappedBy="school")
   public List<Student> getStudents() {
      return students;
   }
   public void setStudents(List<Student> students) {
   ...

...

public class Person {
   ...
   // I pushed down the "School code" to the subclass

public class Student extends Person {
   ...
   // this code was pushed down from the baseclass
   private School school;
   @ManyToOne
   @JoinColumn(name="SCHOOL_ID")
   public School getSchool() {
      return school;
   }
   public void setSchool(School school) {
      this.school = school;
   }   
   // but it would actually be desired if I even could get rid of this School reference from Student/Person ...
   ...



Below is the complete code listing of the code that does not work, and at the end the stacktrace is shown:

Code:
public static void main(String[] args) {
   EntityManagerFactory emf = Persistence.createEntityManagerFactory("students");
   EntityManager em = emf.createEntityManager();
   School school = em.find(School.class, 1);
   System.out.println(school.getSchoolName());
   List<Student> students = school.getStudents(); // this is the typing I want, i.e. typed as subclass !
   for (Student student : students) {
      System.out.println(student.getGraduationYear());
   }

   school = new School();
   school.setSchoolName("MIT");
   Student student = new Student();
   student.setPersonName("Martin");
   student.setGraduationYear(1960);
   school.addStudent(student);
   
   EntityTransaction transaction = em.getTransaction();
   transaction.begin();
   em.persist(school);
   transaction.commit();
}

package domain;
// the imports are not displayed here ...
@Entity
@Table(name="SCHOOL")
public class School {
   public School() {
   }
   
   private Integer id;
   @Id
   @GeneratedValue(strategy = GenerationType.AUTO)
   @Column(name = "SCHOOL_ID")   
   public Integer getId() {
      return id;
   }
   public void setId(Integer id) {
      this.id = id;
   }

   private String schoolName;
   @Column(name = "SCHOOL_NAME")   
   public String getSchoolName() {
      return this.schoolName;
   }
   public void setSchoolName(String schoolName) {
      this.schoolName = schoolName;
   }   


   private List<Student> students = new Vector<Student>();  // this is the typing I want, i.e. typed as subclass !
   @OneToMany(cascade=CascadeType.ALL, mappedBy="school")
   public List<Student> getStudents() {
      return students;
   }
   public void setStudents(List<Student> students) {
      this.students = students;
      // if I use the rows below, the Exception will be: "Exception occurred inside setter of domain.School.students"
      //for (Student student : students) {
      //   student.setSchool(this);
      //}
      // but without the commented rows above, the exception will instead be:
      // "could not initialize a collection: [domain.School.students#1]"
   }
   public void addStudent(Student student) {
      this.students.add(student);
   }   
}

package domain;
// the imports are not displayed here ...
@Entity
@Table(name="PERSON")
@Inheritance(strategy=InheritanceType.JOINED)
@DiscriminatorColumn(name="PERSON_TYPE", discriminatorType=DiscriminatorType.STRING)
public class Person {
   public Person() {
      this.setPersonType("P");
   }
   
   private Integer id;
   @Id
   @GeneratedValue(strategy = GenerationType.AUTO)
   @Column(name = "PERSON_ID")   
   public Integer getId() {
      return id;
   }
   public void setId(Integer id) {
      this.id = id;
   }
   
   private String personName;
   @Column(name = "PERSON_NAME")   
   public String getPersonName() {
      return this.personName;
   }
   public void setPersonName(String personName) {
      this.personName = personName;
   }
   
   private String personType;
   @Column(name = "PERSON_TYPE")   
   public String getPersonType() {
      return this.personType;
   }
   public void setPersonType(String personType) {
      this.personType = personType;
   }   

}

package domain;
// the imports are not displayed here ...
@Entity
@Table(name="STUDENT")
@DiscriminatorValue("S") // S = Student
@Inheritance(strategy=InheritanceType.JOINED)
@PrimaryKeyJoinColumn(name="PERSON_ID")
public class Student extends Person {
   public Student() {
      super.setPersonType("S");
   }

   private int graduationYear;
   @Column(name="GRADUATION_YEAR")
   public int getGraduationYear() {
      return graduationYear;
   }
   public void setGraduationYear(int employmentYear) {
      this.graduationYear = employmentYear;
   }

   private School school;
   @ManyToOne
   @JoinColumn(name="SCHOOL_ID")
   public School getSchool() {
      return school;
   }
   public void setSchool(School school) {
      this.school = school;
   }   
}


This is the result when I execute the main method above:

Code:
Hibernate: select school0_.SCHOOL_ID as SCHOOL1_0_0_, school0_.SCHOOL_NAME as SCHOOL2_0_0_ from SCHOOL school0_ where school0_.SCHOOL_ID=?
Harvard
Hibernate: select students0_.SCHOOL_ID as SCHOOL3_1_, students0_.PERSON_ID as PERSON2_1_, students0_.PERSON_ID as PERSON1_1_0_, students0_1_.PERSON_NAME as PERSON2_1_0_, students0_1_.PERSON_TYPE as PERSON3_1_0_, students0_.GRADUATION_YEAR as GRADUATION1_2_0_, students0_.SCHOOL_ID as SCHOOL3_2_0_ from STUDENT students0_ inner join PERSON students0_1_ on students0_.PERSON_ID=students0_1_.PERSON_ID where students0_.SCHOOL_ID=?
Exception in thread "main" org.hibernate.exception.SQLGrammarException: could not initialize a collection: [domain.School.students#1]
   at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:67)
   at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:43)
   at org.hibernate.loader.Loader.loadCollection(Loader.java:2001)
   at org.hibernate.loader.collection.CollectionLoader.initialize(CollectionLoader.java:36)
   at org.hibernate.persister.collection.AbstractCollectionPersister.initialize(AbstractCollectionPersister.java:565)
   at org.hibernate.event.def.DefaultInitializeCollectionEventListener.onInitializeCollection(DefaultInitializeCollectionEventListener.java:63)
   at org.hibernate.impl.SessionImpl.initializeCollection(SessionImpl.java:1716)
   at org.hibernate.collection.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:344)
   at org.hibernate.collection.AbstractPersistentCollection.read(AbstractPersistentCollection.java:86)
   at org.hibernate.collection.PersistentBag.iterator(PersistentBag.java:249)
   at domain.TestKlass.main(TestKlass.java:19)
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'students0_.SCHOOL_ID' in 'field list'
   at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
   at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
   at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
   at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
   at com.mysql.jdbc.Util.handleNewInstance(Util.java:406)
   at com.mysql.jdbc.Util.getInstance(Util.java:381)
   at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1030)
   at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:956)
   at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3491)
   at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3423)
   at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1936)
   at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2060)
   at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2542)
   at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1734)
   at com.mysql.jdbc.PreparedStatement.executeQuery(PreparedStatement.java:1885)
   at org.hibernate.jdbc.AbstractBatcher.getResultSet(AbstractBatcher.java:186)
   at org.hibernate.loader.Loader.getResultSet(Loader.java:1787)
   at org.hibernate.loader.Loader.doQuery(Loader.java:674)
   at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:236)
   at org.hibernate.loader.Loader.loadCollection(Loader.java:1994)
   ... 8 more


Can anyone tell me what changes I need to do with the annotations to be able to use the type "List<Student>" instead of "List<Person>" in the above code ?

/ Tom


Top
 Profile  
 
 Post subject:
PostPosted: Fri Jul 04, 2008 3:52 am 
Pro
Pro

Joined: Tue Jun 12, 2007 4:13 am
Posts: 209
Location: Berlin, Germany
Well, first you have to admit to what MySql tells you:
- there is an
Quote:
Unknown column 'students0_.SCHOOL_ID' in 'field list'
,
so this
Quote:
@ManyToOne
@JoinColumn(name="SCHOOL_ID")

is a lie or wrong in some other way.

Second: it seems very strange to map Persons to School, when you really want to say there a Students in a School. So in fact you should map the students as a List<Student> in your School class.

Third: you do not need to implement bi-directionality. So you could have the @OneToMany List in your School and remove the reverse side; then you should of course als remove "mappedBy".

_________________
Carlo
-----------------------------------------------------------
please don't forget to rate if this post helped you


Top
 Profile  
 
 Post subject:
PostPosted: Fri Jul 04, 2008 3:54 pm 
Newbie

Joined: Wed Aug 02, 2006 6:11 am
Posts: 8
carlolf wrote:
Well, first you have to admit to what MySql tells you:
- there is an
Quote:
Unknown column 'students0_.SCHOOL_ID' in 'field list'
,
so this
Quote:
@ManyToOne
@JoinColumn(name="SCHOOL_ID")

is a lie or wrong in some other way.


Yes, I can see that something becomes wrong in the SQL, so I am asking for the concrete solution about how to do the annotation to make it work.

carlolf wrote:
Second: it seems very strange to map Persons to School, when you really want to say there a Students in a School. So in fact you should map the students as a List<Student> in your School class.


Yes, absolutely. That was indeed what I am trying to do (!) but the only working solution (to use JPA for populating the list of POJOs with database data) I have been able to find was to use the basetype as the list type and then downcast it to the actual concrete type of the POJO (i.e. I have to downcast Person to Student).
In my posting I displayed this workaround solution with the downcasting, to show some inital concrete code, so that someone might tell me how I need to change the annotations to be able to avoid the downcasting.

carlolf wrote:
Third: you do not need to implement bi-directionality. So you could have the @OneToMany List in your School and remove the reverse side; then you should of course als remove "mappedBy".


No, it does not work.
As you suggested, I have tried to remove the field School, and the javabean methods getSchool and setSchool (and their annotations of course) from the Student (and also do not have these methods/field in the Person baseclass, but then I get this exception:
Code:
Hibernate: select school0_.SCHOOL_ID as SCHOOL1_0_0_, school0_.SCHOOL_NAME as SCHOOL2_0_0_ from SCHOOL school0_ where school0_.SCHOOL_ID=?
Harvard
Hibernate: select students0_.SCHOOL_SCHOOL_ID as SCHOOL1_1_, students0_.students_PERSON_ID as students2_1_, student1_.PERSON_ID as PERSON1_1_0_, student1_1_.PERSON_NAME as PERSON2_1_0_, student1_1_.PERSON_TYPE as PERSON3_1_0_, student1_.GRADUATION_YEAR as GRADUATION1_2_0_ from SCHOOL_STUDENT students0_ left outer join STUDENT student1_ on students0_.students_PERSON_ID=student1_.PERSON_ID left outer join PERSON student1_1_ on student1_.PERSON_ID=student1_1_.PERSON_ID where students0_.SCHOOL_SCHOOL_ID=?
Exception in thread "main" org.hibernate.exception.SQLGrammarException: could not initialize a collection: [domain.School.students#1]
   at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:67)
   at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:43)
   at org.hibernate.loader.Loader.loadCollection(Loader.java:2001)
   at org.hibernate.loader.collection.CollectionLoader.initialize(CollectionLoader.java:36)
   at org.hibernate.persister.collection.AbstractCollectionPersister.initialize(AbstractCollectionPersister.java:565)
   at org.hibernate.event.def.DefaultInitializeCollectionEventListener.onInitializeCollection(DefaultInitializeCollectionEventListener.java:63)
   at org.hibernate.impl.SessionImpl.initializeCollection(SessionImpl.java:1716)
   at org.hibernate.collection.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:344)
   at org.hibernate.collection.AbstractPersistentCollection.read(AbstractPersistentCollection.java:86)
   at org.hibernate.collection.PersistentBag.iterator(PersistentBag.java:249)
   at domain.TestKlass.main(TestKlass.java:19)
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 't.school_student' doesn't exist
   at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
   at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
   at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
   at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
   at com.mysql.jdbc.Util.handleNewInstance(Util.java:406)
   at com.mysql.jdbc.Util.getInstance(Util.java:381)
   at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1030)
   at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:956)
   at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3491)
   at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3423)
   at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1936)
   at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2060)
   at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2542)
   at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1734)
   at com.mysql.jdbc.PreparedStatement.executeQuery(PreparedStatement.java:1885)
   at org.hibernate.jdbc.AbstractBatcher.getResultSet(AbstractBatcher.java:186)
   at org.hibernate.loader.Loader.getResultSet(Loader.java:1787)
   at org.hibernate.loader.Loader.doQuery(Loader.java:674)
   at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:236)
   at org.hibernate.loader.Loader.loadCollection(Loader.java:1994)
   ... 8 more


Any other suggestions, someone ?

/ Tom


Top
 Profile  
 
 Post subject:
PostPosted: Mon Jul 07, 2008 2:44 am 
Pro
Pro

Joined: Tue Jun 12, 2007 4:13 am
Posts: 209
Location: Berlin, Germany
Quote:
Table 't.school_student' doesn't exist


Because MySQL (-JDBC) gives you this error message, I think you have done the mapping to a Join table named 't.school_student', but you didn't update your DB schema.

_________________
Carlo
-----------------------------------------------------------
please don't forget to rate if this post helped you


Top
 Profile  
 
 Post subject:
PostPosted: Mon Jul 07, 2008 3:25 am 
Senior
Senior

Joined: Tue Jul 25, 2006 9:05 am
Posts: 163
Location: Stuttgart/Karlsruhe, Germany
Hi,

Could you post the schema created by hibernate, as your first example posted works fine with a PostgreSQL database, so maybe the problem lies with the DB dialect.

Cheers,

Andy

_________________
Rules are only there to be broken


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 5 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.