I don't know if it is the best way to do this, but I have a quite elegant solution to this problem... And, it seems to work properly! Of course, it is not as elegant as if Hibernate could manage it natively...
Sorry not to have added comments in my code but there is not so much to understand...
First, the Subject class and mapping file which are very simple, nothing special in there:
Code:
package mypackage;
public class Subject {
private Long subject_id;
private String subject_name;
public Long getSubject_id() {
return subject_id;
}
public void setSubject_id(Long subject_id) {
this.subject_id = subject_id;
}
public String getSubject_name() {
return subject_name;
}
public void setSubject_name(String subject_name) {
this.subject_name = subject_name;
}
}
Code:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="mypackage">
<class name="Subject">
<id name="subject_id">
<generator class="increment"></generator>
</id>
<property name="subject_name"></property>
</class>
</hibernate-mapping>
For the student, we need a bag to hold the student's scores. I've mapped the bag on a list. The getScore method go through the list and gets the StudentScore (see below) that matches the Subject and returns the score, like in a Map<Subject,Integer>. You can try to implement such a map if you like...
Code:
package mypackage;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
public class Student {
private Long student_id;
private String student_name;
private List<StudentScore> scores = new LinkedList<StudentScore>();
public Long getStudent_id() {
return student_id;
}
public void setStudent_id(Long student_id) {
this.student_id = student_id;
}
public String getStudent_name() {
return student_name;
}
public void setStudent_name(String student_name) {
this.student_name = student_name;
}
public List<StudentScore> getScores() {
return scores;
}
public void setScores(List<StudentScore> scores) {
this.scores = scores;
}
public int getScore(Subject s) {
Iterator<StudentScore> iter = scores.iterator();
StudentScore result = null;
while((iter.hasNext()) && ((result==null) || (result.getSubject()!=s))) {
result = iter.next();
}
if ((result!=null) && (result.getSubject()==s)) {
return result.getScore();
}
return -1;
}
}
Code:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="mypackage">
<class name="Student">
<id name="student_id">
<generator class="increment"></generator></id>
<property name="student_name"></property>
<bag name="scores">
<key column="student_id"></key>
<one-to-many class="StudentScore" ></one-to-many>
</bag>
</class>
</hibernate-mapping>
Now the association table. I had to declare a composite primary key. Thus, the StudentScore class has to implement Serializable.
I also added a reference to the subject by reusing the subject_id column. Thus I had to add insert="false" and update="false" in the mapping.
Code:
package mypackage;
import java.io.Serializable;
public class StudentScore implements Serializable {
static final long serialVersionUID = 1L;
private Long student_id;
private Long subject_id;
private int score;
private Subject subject;
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
public Long getStudent_id() {
return student_id;
}
public void setStudent_id(Long student_id) {
this.student_id = student_id;
}
public Long getSubject_id() {
return subject_id;
}
public void setSubject_id(Long subject_id) {
this.subject_id = subject_id;
}
public Subject getSubject() {
return subject;
}
public void setSubject(Subject subject) {
this.subject = subject;
}
}
Code:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="mypackage">
<class name="StudentScore">
<composite-id>
<key-property name="student_id"></key-property>
<key-property name="subject_id"></key-property>
</composite-id>
<property name="score"></property>
<many-to-one name="subject" column="subject_id" class="Subject"
insert="false" update="false">
</many-to-one>
</class>
</hibernate-mapping>
And here is a short example code to test all this:
Code:
public void testStudents() {
// Create the DB and populate it!
HibernateUtil hibernate = new HibernateUtil(true);
Session session = hibernate.getSessionFactory().getCurrentSession();
try {
session.beginTransaction();
// Create a student
Student student = new Student();
student.setStudent_name("STUDENT1");
session.save(student);
// Create some subjects
Subject[] subjects = new Subject[10];
for (int i=0;i<subjects.length;i++) {
subjects[i] = new Subject();
subjects[i].setSubject_name("SUBJECT"+i);
session.save(subjects[i]);
// Give a score for the student in the newly created Subject
StudentScore score = new StudentScore();
score.setStudent_id(student.getStudent_id());
score.setSubject_id(subjects[i].getSubject_id());
score.setScore(i);
score.setSubject(subjects[i]);
session.save(score);
}
session.getTransaction().commit();
} catch (HibernateException e) {
e.printStackTrace();
session.getTransaction().rollback();
} finally {
hibernate.getSessionFactory().close();
}
// Now try to load and use the data
hibernate = new HibernateUtil(true);
session = hibernate.getSessionFactory().getCurrentSession();
try {
session.beginTransaction();
List<Subject> subjects = session.createQuery("from Subject").list();
List<Student> students = session.createQuery("from Student").list();
// Try it with the getScore method
for (Iterator<Student> iter = students.iterator();iter.hasNext();) {
Student student = iter.next();
for (Iterator<Subject> iter2 = subjects.iterator();iter2.hasNext();) {
Subject subject = iter2.next();
logger.debug("Student: "+student.getStudent_name()+" has "+student.getScore(subject)+" in "+subject.getSubject_name());
}
}
// Just show all the date in the DB
for (Iterator<Student> iter = students.iterator();iter.hasNext();) {
Student student = iter.next();
logger.debug("Student Name:"+student.getStudent_name());
for (Iterator<StudentScore>iter2 = student.getScores().iterator();iter2.hasNext();) {
StudentScore score = iter2.next();
logger.debug("Subject: "+score.getSubject().getSubject_name()+" Score: "+score.getScore());
}
}
session.getTransaction().commit();
} catch (HibernateException e) {
e.printStackTrace();
session.getTransaction().rollback();
} finally {
hibernate.getSessionFactory().close();
}
}
I don't know if it is the best way, I'm quite a newby in Hibernate, but it seems to work in this case (no bi-directional relations).
Don't hesitate if you have any comment on this...
Enjoy!