In this post I would like to show how to create Hibernate many to many mapping (using annotations) with join table containing additional column, next to two foreign keys. Let's consider two entities: Student and Course. Student can have multiple Courses and Course can have multiple Students. First, basic and typical many to many Hibernate mapping:
package uk.co.cloudidentity.manymany.simple;
import java.io.Serializable;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
/**
* @author Lukasz Moren
*/
@Table
@Entity
public class Course implements Serializable {
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
@Column(name = "course_name")
private String courseName;
@ManyToMany(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
@JoinTable(
name = "student_courses",
joinColumns = @JoinColumn(name = "course_id"),
inverseJoinColumns = @JoinColumn(name = "student_id"),
uniqueConstraints = {@UniqueConstraint(columnNames = {"student_id", "course_id"})})
private List students;
public Course() {
}
public Course(String courseName) {
this.courseName = courseName;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getCourseName() {
return courseName;
}
public void setCourseName(String courseName) {
this.courseName = courseName;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Course course = (Course)o;
if (courseName != null ? !courseName.equals(course.courseName) : course.courseName != null) {
return false;
}
if (id != null ? !id.equals(course.id) : course.id != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = id != null ? id.hashCode() : 0;
result = 31 * result + (courseName != null ? courseName.hashCode() : 0);
return result;
}
}
package uk.co.cloudidentity.manymany.simple;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
/**
* @author Lukasz Moren
*/
@Entity
@Table
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "first_name")
private String firstName;
@Column(name = "last_name")
private String lastName;
@ManyToMany(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
@JoinTable(
name = "student_courses",
joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_id"),
uniqueConstraints = {@UniqueConstraint(columnNames = {"student_id", "course_id"})})
private List courses;
public Student() {
}
public Student(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public List getCourses() {
return courses;
}
public void setCourses(List courses) {
this.courses = courses;
}
public void addCourse(Course course) {
if (courses == null) {
courses = new ArrayList();
}
courses.add(course);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Student student = (Student)o;
if (firstName != null ? !firstName.equals(student.firstName) : student.firstName != null) {
return false;
}
if (id != null ? !id.equals(student.id) : student.id != null) {
return false;
}
if (lastName != null ? !lastName.equals(student.lastName) : student.lastName != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = id != null ? id.hashCode() : 0;
result = 31 * result + (firstName != null ? firstName.hashCode() : 0);
result = 31 * result + (lastName != null ? lastName.hashCode() : 0);
return result;
}
}
@JoinTable annotation creates new table: student_courses that associates Students and Courses. We don't need to create new mapping class in Java, table will be created automatically by Hibernate. The problem with this solution is that we cannot add new column to student_courses table. What if we would like to add sign_up_date column. To solve that we have to create use different mapping technique that requires creating student_courses mapping class. Full list of required mappings below: package uk.co.cloudidentity.manymany.pk;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
/**
* @author Lukasz Moren
*/
@Table
@Entity
public class CoursePK implements Serializable {
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
@Column(name = "ID")
private Long id;
@Column(name = "course_name")
private String courseName;
public CoursePK() {
}
public CoursePK(String courseName) {
this.courseName = courseName;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getCourseName() {
return courseName;
}
public void setCourseName(String courseName) {
this.courseName = courseName;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
CoursePK course = (CoursePK)o;
if (courseName != null ? !courseName.equals(course.courseName) : course.courseName != null) {
return false;
}
if (id != null ? !id.equals(course.id) : course.id != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = id != null ? id.hashCode() : 0;
result = 31 * result + (courseName != null ? courseName.hashCode() : 0);
return result;
}
}
package uk.co.cloudidentity.manymany.pk;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
/**
* @author Lukasz Moren
*/
@Entity
@Table
public class StudentPK {
@Id
@Column(name = "ID")
@GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
@Column(name = "first_name")
private String firstName;
@Column(name = "last_name")
private String lastName;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "student", fetch = FetchType.LAZY)
private List courses;
public StudentPK() {
}
public StudentPK(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public List getCourses() {
return courses;
}
public void setCourses(List courses) {
this.courses = courses;
}
public void addCourse(CoursePK coursePK) {
if (courses == null) {
courses = new ArrayList();
}
StudentCourse studentCourse = new StudentCourse(this, coursePK);
courses.add(studentCourse);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
StudentPK student = (StudentPK)o;
if (firstName != null ? !firstName.equals(student.firstName) : student.firstName != null) {
return false;
}
if (id != null ? !id.equals(student.id) : student.id != null) {
return false;
}
if (lastName != null ? !lastName.equals(student.lastName) : student.lastName != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = id != null ? id.hashCode() : 0;
result = 31 * result + (firstName != null ? firstName.hashCode() : 0);
result = 31 * result + (lastName != null ? lastName.hashCode() : 0);
return result;
}
}
Student an Course mapping classes are very similar to our first example. package uk.co.cloudidentity.manymany.pk;
import java.io.Serializable;
import javax.persistence.Embeddable;
/**
* @author Lukasz Moren
*/
@Embeddable
public class StudentCoursePK implements Serializable {
private Long courseId;
private Long studentId;
public StudentCoursePK(Long courseId, Long studentId) {
this.courseId = courseId;
this.studentId = studentId;
}
public StudentCoursePK() {
}
public Long getCourseId() {
return courseId;
}
public void setCourseId(Long courseId) {
this.courseId = courseId;
}
public Long getStudentId() {
return studentId;
}
public void setStudentId(Long studentId) {
this.studentId = studentId;
}
}
Above class maps student_course table primary key, that contains two columns: courseId and studentId. Class is marked with @Embeddable what means it can be used in other mapping classes as a primary key. package uk.co.cloudidentity.manymany.pk;
import java.io.Serializable;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.MapsId;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
/**
* @author Lukasz Moren
*/
@Table
@Entity
public class StudentCourse implements Serializable {
@EmbeddedId
private StudentCoursePK userHostPK = new StudentCoursePK();
@MapsId("studentId")
@JoinColumn(name = "STUDENT_ID", referencedColumnName = "ID")
@ManyToOne(optional = false, fetch = FetchType.EAGER)
private StudentPK student;
@MapsId("courseId")
@JoinColumn(name = "COURSE_ID", referencedColumnName = "ID")
@ManyToOne(optional = false, fetch = FetchType.EAGER)
private CoursePK course;
@Column
@Temporal(TemporalType.TIMESTAMP)
private Date signUpDate;
public StudentCourse() {
}
public StudentCourse(StudentPK student, CoursePK course) {
this.student = student;
this.course = course;
}
public StudentCoursePK getUserHostPK() {
return userHostPK;
}
public void setUserHostPK(StudentCoursePK userHostPK) {
this.userHostPK = userHostPK;
}
public StudentPK getStudent() {
return student;
}
public void setStudent(StudentPK student) {
this.student = student;
}
public CoursePK getCourse() {
return course;
}
public void setCourse(CoursePK course) {
this.course = course;
}
}
Finally we have mapping class for student_course table. Apart from embedded primary key it contains fields of StudentPK and CoursePK types. Moreover we have custom signUpDate column defined.
Hi Lukasz Moren,
ReplyDeleteThis is great article.But You can share code how to add-update-delete functionality for these entities.
Thanks