Hibernate Many-to-Many Relationship with Join Table Example
In the previous tutorial, Hibernate Many-to-Many Relationship Example (XML Mapping and Annotation), we saw how use Hibernate in order to work with classes that have many-to-many relationships. I would strongly recommend to read that tutorial before proceeding to this one. For this tutorial we are going to see how to map the classes with Annotations.
So these are the tools we are going to use on a Windows 7 platform:
- JDK 1.7
- Maven 3.0.5
- Hibernate 3.6.3.Final
- MySQL JDBC driver 5.1.9
- Eclipse 4.2 Juno
Remember the database schema from the previous tutorial:
In this, we have two tables, student and class, associated with many-to-many relationship. We explained that in relational databases the above relationship is implemented using a third table, the join table, that “connects” the aforementioned two tables that participate in the relationship. As you can see, in the join table (student_class) every tuple consists of a ID_STUDENT (foreign key to student) and CLASS_ID (foreign key to class). This is essential in order to express the many-to-many relationship. But this relationship might have additional characteristics and properties. For example you might want to hold the date the user registered to the class and the grade of the student to that class. So we have to add additional columns to the join table.
This is the script to create the tables:
DROP TABLE IF EXISTS `student`; CREATE TABLE `student` ( `ID_STUDENT` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, `STUDENT_NAME` VARCHAR(10) NOT NULL, `STUDENT_AGE` VARCHAR(20) NOT NULL, PRIMARY KEY (`ID_STUDENT`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `class`; CREATE TABLE `class` ( `CLASS_ID` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, `TITLE` VARCHAR(10) NOT NULL, `SEMESTER` VARCHAR(255) NOT NULL, UNIQUE KEY `UNI_TITLE` (`TITLE`), PRIMARY KEY (`CLASS_ID`) ) DROP TABLE IF EXISTS `student_class`; CREATE TABLE `student_class` ( `ID_STUDENT` INT(10) UNSIGNED NOT NULL, `CLASS_ID` INT(10) UNSIGNED NOT NULL, `REGISTERED` DATE NOT NULL, PRIMARY KEY (`ID_STUDENT`,`CLASS_ID`), CONSTRAINT `FK_CLASS_ID` FOREIGN KEY (`CLASS_ID`) REFERENCES `class` (`CLASS_ID`), CONSTRAINT `FK_ID_STUDENT` FOREIGN KEY (`ID_STUDENT`) REFERENCES `student` (`STUDENT_ID`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
So this is the new diagram:
For the new schema we need to create more java classes. One class that holds the new columns of the student_class
(StudentClass.java
) table, and the class that contains the join columns of the join table (StudentClassID.java
).
1. Project Structure from previous tutorial
Go to , Hibernate Many-to-Many Relationship Example (XML Mapping and Annotation) and download the code of the Annotation tutorial. Make sure that the project structure is as follows:
2. Create the new Java Classes
In the Package Explorer go to src/main/java
folder and to com.javacodegeeks.enterprise.hibernate
package. Right Click -> New -> Class :
This will create the new StudentClass.java
file. Go ahead and create StudentClassID.java
file.
Here is the code of the classes:
Student.java:
package com.javacodegeeks.enterprise.hibernate; import static javax.persistence.GenerationType.IDENTITY; import java.util.HashSet; import java.util.Set; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.OneToMany; import javax.persistence.Table; @Entity @Table(name = "student", catalog = "tutorials") public class Student implements java.io.Serializable { private static final long serialVersionUID = 1L; private Integer studentId; private String studentName; private String studentAge; private Set<StudentClass> studentClasses = new HashSet<StudentClass>(0); public Student() { } public Student(String studentName, String studentAge) { this.studentName = studentName; this.studentAge = studentAge; } @Id @GeneratedValue(strategy = IDENTITY) @Column(name = "STUDENT_ID", unique = true, nullable = false) public Integer getStudentId() { return studentId; } public void setStudentId(Integer studentId) { this.studentId = studentId; } @Column(name = "STUDENT_NAME", nullable = false, length = 10) public String getStudentName() { return studentName; } public void setStudentName(String studentName) { this.studentName = studentName; } @Column(name = "STUDENT_AGE", nullable = false, length = 20) public String getStudentAge() { return studentAge; } public void setStudentAge(String studentAge) { this.studentAge = studentAge; } @OneToMany(fetch = FetchType.LAZY, mappedBy = "pk.student", cascade=CascadeType.ALL) public Set<StudentClass> getStudentClasses() { return studentClasses; } public void setStudentClasses(Set<StudentClass> studentClasses) { this.studentClasses = studentClasses; } }
In the above code notice that we have a Set
of StudentClass
instances. StudentClass
will represent the join table student_class
. We also state that Student
class has a one-to-many relationship with StudentClass
. This directly associates with the fact that student
table has one-to-many relationship with the join table student_class
.
Class.java:
package com.javacodegeeks.enterprise.hibernate; import static javax.persistence.GenerationType.IDENTITY; import java.util.HashSet; import java.util.Set; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.OneToMany; import javax.persistence.Table; @Entity @Table(name = "class", catalog = "tutorials") public class Class implements java.io.Serializable{ private Integer classID; private String title; private String semester; private Set studentClasses = new HashSet(0); public Class(String title, String semester){ this.title = title; this.semester = semester; } @Id @GeneratedValue(strategy = IDENTITY) @Column(name = "CLASS_ID", unique = true, nullable = false) public Integer getClassID() { return classID; } public void setClassID(Integer classID) { this.classID = classID; } @Column(name = "TITLE", nullable = false, length = 10, unique = true) public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } @Column(name = "SEMESTER", nullable = false, length = 255) public String getSemester() { return semester; } public void setSemester(String semester) { this.semester = semester; } @OneToMany(fetch = FetchType.LAZY, mappedBy = "pk.cl") public Set getStudentClasses() { return studentClasses; } public void setStudentClasses(Set studentClasses) { this.studentClasses = studentClasses; } private static final long serialVersionUID = 1L; }
You will notice the same pattern here. Class
has one-to-many relationships with StudentClass
, as class
table has one-to-many relationship with the join table student_class
.
Now, let’s get on to the interesting bit.
StudentClass.java:
package com.javacodegeeks.enterprise.hibernate; import java.util.Date; import javax.persistence.AssociationOverride; import javax.persistence.AssociationOverrides; import javax.persistence.Column; import javax.persistence.EmbeddedId; import javax.persistence.Entity; import javax.persistence.JoinColumn; import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.TemporalType; import javax.persistence.Transient; @Entity @Table(name = "student_class", catalog = "tutorials") @AssociationOverrides({ @AssociationOverride(name = "pk.student", joinColumns = @JoinColumn(name = "ID_STUDENT")), @AssociationOverride(name = "pk.cl", joinColumns = @JoinColumn(name = "CLASS_ID")) }) public class StudentClass implements java.io.Serializable { private static final long serialVersionUID = 4050660680047579957L; private StudentClassID pk = new StudentClassID(); private Date registered; @EmbeddedId public StudentClassID getPk() { return pk; } @Transient public Student getStudent() { return getPk().getStudent(); } public void setStudent(Student student) { getPk().setStudent(student); } @Transient public Class getCl() { return getPk().getCl(); } public void setCl(Class c) { getPk().setCl(c); } public void setPk(StudentClassID pk) { this.pk = pk; } @Temporal(TemporalType.DATE) @Column(name = "REGISTERED", nullable = false, length = 10) public Date getRegistered() { return registered; } public void setRegistered(Date registered) { this.registered = registered; } @Override public int hashCode() { return (getPk() != null ? getPk().hashCode() : 0); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (!(obj instanceof StudentClass)) return false; StudentClass other = (StudentClass) obj; if (pk == null) { if (other.pk != null) return false; } else if (!pk.equals(other.pk)) return false; if (registered == null) { if (other.registered != null) return false; } else if (!registered.equals(other.registered)) return false; return true; } }
StudentClassID.java:
package com.javacodegeeks.enterprise.hibernate; import javax.persistence.Embeddable; import javax.persistence.ManyToOne; @Embeddable public class StudentClassID implements java.io.Serializable { private static final long serialVersionUID = -9120607274421816301L; private Student student; private Class cl; @ManyToOne public Student getStudent() { return student; } public void setStudent(Student student) { this.student = student; } @ManyToOne public Class getCl() { return cl; } public void setCl(Class cl) { this.cl = cl; } @Override public int hashCode() { int result; result = (student != null ? student.hashCode() : 0); result = 17 * result + (cl != null ? cl.hashCode() : 0); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (!(obj instanceof StudentClassID)) return false; StudentClassID other = (StudentClassID) obj; if (cl == null) { if (other.cl != null) return false; } else if (!cl.equals(other.cl)) return false; if (student == null) { if (other.student != null) return false; } else if (!student.equals(other.student)) return false; return true; } }
StudentClassID
is what we call a composite primary key. This composite class is the primary key of StudentClass
. To make a composite primary key class you have to declare it as embeddable using @Embeddable
annotation. Now, in StudentClass
you should have a StudentClassID
instance that will represent the primary key of the class. Because StudentClassID
is an embeddable class and the primary of StudentClass
we use @EmbeddedId
annotation in StudentClass
.
The next thing to notice is the @AssociationOverrides
annotation in StudentClass
. We use that annotation when using an embedded composite primary key. It overrides a relationship mapping when the embeddable class is on the owning side of the relationship. When used to override a relationship mapping defined by an embeddable class (including an embeddable class embedded within another embeddable class), @AssociationOverride
is applied to the field or property containing the embeddable.
When @AssociationOverride
is used to override a relationship mapping from an embeddable class, the name
element specifies the referencing relationship field or property within the embeddable class. To override mappings at multiple levels of embedding, a dot (“.”) notation syntax must be used in the name element to indicate an attribute within an embedded attribute. The value of each identifier used with the dot notation is the name of the respective embedded field or property. @JoinColumn
declarers the join column. The name parameter declares the column in the targeted entity that will be used to the join.
3. App.java
code
App.java:
package com.javacodegeeks.enterprise.hibernate; import java.util.Date; import org.hibernate.Session; import com.javacodegeeks.enterprise.hibernate.utils.HibernateUtil; public class App { public static void main( String[] args ) { Session session = HibernateUtil.getSessionFactory().openSession(); session.beginTransaction(); Student student1 = new Student("Jeremny","21"); Class class1 = new Class("Math","Spring"); session.save(class1); StudentClass studentClass = new StudentClass(); studentClass.setCl(class1); studentClass.setStudent(student1); Date registered = new Date(); studentClass.setRegistered(registered); // The new date column student1.getStudentClasses().add(studentClass); session.save(student1); session.getTransaction().commit(); System.out.println("Great! Students were saved"); } }
In the above code we create a Student
and a Class
. We save the Class
instance to the session. Then we create a new StudentClass
and register student1
and class1
to it in order to create a new couple of the relationship. We also add studentClass
to the StudentClass
set of the Student
class. We then save student1 one to the session and commit.
5. Hibernate configuration
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <property name="hibernate.bytecode.use_reflection_optimizer">false</property> <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property> <property name="hibernate.connection.username">root</property> <property name="hibernate.connection.password"></property> <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/tutorials</property> <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property> <property name="show_sql">true</property> <property name="format_sql">true</property> <mapping class="com.javacodegeeks.enterprise.hibernate.Student"></mapping> <mapping class="com.javacodegeeks.enterprise.hibernate.Class"></mapping> <mapping class="com.javacodegeeks.enterprise.hibernate.StudentClass"></mapping> <mapping class="com.javacodegeeks.enterprise.hibernate.StudentClassID"></mapping> </session-factory> </hibernate-configuration>
6. Output
Make sure the project structure is as follows:
This is the output of the programm:
log4j:WARN No appenders could be found for logger (org.hibernate.type.BasicTypeRegistry).
log4j:WARN Please initialize the log4j system properly.
Hibernate:
insert
into
tutorials.class
(SEMESTER, TITLE)
values
(?, ?)
Hibernate:
insert
into
tutorials.student
(STUDENT_AGE, STUDENT_NAME)
values
(?, ?)
Hibernate:
select
studentcla_.CLASS_ID,
studentcla_.ID_STUDENT,
studentcla_.REGISTERED as REGISTERED2_
from
tutorials.student_class studentcla_
where
studentcla_.CLASS_ID=?
and studentcla_.ID_STUDENT=?
Hibernate:
insert
into
tutorials.student_class
(REGISTERED, CLASS_ID, ID_STUDENT)
values
(?, ?, ?)
Great! Students were saved
This was an example on Hibernate Many-to-Many Relationship with Join Table. Download the eclipse project of this tutorial:HibernateManyToManyJoinTable.zip
How to write an HQL query on that Student_Class table to get number of student registered in a particular month.Please help.