Hibernate中的ManyToMany的最佳实践
概述
本文将介绍如何高效的使用Hibernate的ManyToMany。
具体说明
假设我们有Student实体和Course实体,一个学生可以修多个课程,一个课程会有多个学生来学习。因此我们有如下的实体定义:
@Entity
@Setter
@Getter
@EqualsAndHashCode
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
@ManyToOne
@JoinColumn(name = "school_id")
private School school;
@ManyToMany(cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
@JoinTable(
name = "student_course",
joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_id")
)
private List<Course> courses = new ArrayList<Course>();
public void addCourse(Course course) {
courses.add(course);
course.getStudents().add(this);
}
public void deleteCourse(Course course) {
courses.remove(course);
course.getStudents().remove(this);
}
}
如上的代码有如下的要点:
- 因为一个学生可以修多个课程,因此在上面的Student实体定义中,我们使用了List来定义courses属性;
- Student和Course是双向的绑定关系,Student是ManyToMany关系的持有方,所以Student提供addCourse和deleteCourse才能保持Student和Course的关系同步; 3.级连操作设置不要设置CascadeType.ALL,因为这样会默认继承Cascade.Remove,这样会导致如果Student实体被删除,会同时删除相关联Course实体,而Course其实是可以不依赖Student实体独立存在的,所以不能设置CascadeType.ALL参考我之前的文章;
Course实体的定义如下:
@Entity
@Getter(AccessLevel.PUBLIC)
@Setter(AccessLevel.PUBLIC)
public class Course {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
@ManyToMany(mappedBy = "courses",
cascade = {CascadeType.MERGE,CascadeType.PERSIST}
)
private List<Student> students = new ArrayList<Student>();
}
通过运行如下的测试代码,将course对象从student对象中删除:
@Test
public void saveStudentCourse() {
Student student = new Student();
student.setName("name_1");
Course course = new Course();
course.setName("c1");
student.addCourse(course);
course = new Course();
course.setName("c2");
student.addCourse(course);
course = new Course();
course.setName("c3");
student.addCourse(course);
Transaction tx = session.beginTransaction();
session.persist(student);
tx.commit();
tx = session.beginTransaction();
student.getCourses().remove(course);
tx.commit();
}
会产生如下的SQL:
Hibernate:
insert
into
Student
(name, school_id)
values
(?, ?)
Hibernate:
insert
into
Course
(name)
values
(?)
Hibernate:
insert
into
Course
(name)
values
(?)
Hibernate:
insert
into
Course
(name)
values
(?)
Hibernate:
insert
into
student_course
(student_id, course_id)
values
(?, ?)
Hibernate:
insert
into
student_course
(student_id, course_id)
values
(?, ?)
Hibernate:
insert
into
student_course
(student_id, course_id)
values
(?, ?)
Hibernate:
delete
from
student_course
where
student_id=?
Hibernate:
insert
into
student_course
(student_id, course_id)
values
(?, ?)
Hibernate:
insert
into
student_course
(student_id, course_id)
values
(?, ?)
通过日志我们可以看出如下两点:
- 我们保存student实例的同时会级连保存新创建出来的course实体,这是因为我们在Student实体的级连中设置了:Cascade.Persist
- 在删除student的某一个course的时后,Hibernate会把所有student和course的中间表中的记录都删除掉之后,然后重新插入还保留的数据,这种方式并不高效,因为它生成了额外的代码。
如果我们把Student中的courses属性的类型调整为Set之后,看看会生成怎样的SQL, 具体调整Student如下:
@Entity
@Setter
@Getter
@EqualsAndHashCode
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
@ManyToOne
@JoinColumn(name = "school_id")
private School school;
@ManyToMany(cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
@JoinTable(
name = "student_course",
joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_id")
)
private Set<Course> courses = new HashSet<Course>();
public void addCourse(Course course) {
courses.add(course);
course.getStudents().add(this);
}
public void deleteCourse(Course course) {
courses.remove(course);
course.getStudents().remove(this);
}
}
@Entity
@Getter(AccessLevel.PUBLIC)
@Setter(AccessLevel.PUBLIC)
public class Course {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
@ManyToMany(mappedBy = "courses",
cascade = {CascadeType.MERGE,CascadeType.PERSIST}
)
private Set<Student> students = new HashSet<Student>();
}
再运行上面的测试代码,删除Student中的某一个course对象,于是生成了如下的SQL:
Hibernate:
insert
into
Student
(name, school_id)
values
(?, ?)
Hibernate:
insert
into
Course
(name)
values
(?)
Hibernate:
insert
into
Course
(name)
values
(?)
Hibernate:
insert
into
Course
(name)
values
(?)
Hibernate:
insert
into
student_course
(student_id, course_id)
values
(?, ?)
Hibernate:
insert
into
student_course
(student_id, course_id)
values
(?, ?)
Hibernate:
insert
into
student_course
(student_id, course_id)
values
(?, ?)
Hibernate:
delete
from
student_course
where
student_id=?
and course_id=?
可以看出删除的course只生成了一个sql语句,因此符合我们的预期。
总结
- 为了高效的使用ManyToMany标注,我们最好在表示集合属性对象的时候,使用Set而不是 List,否则 Hibernate会多生成冗余的语句;
- 关于级连设置,我们最好不要设置为CascadeType.ALL,因为这样会默认继承CascadeType.Remove,这样会删除被关联的对象