由于 重复性的操作十分烦琐,尤其是在处理多个彼此关联对象情况下,此时我们可以使用级联(Cascade)操作。级联 在关联映射中是个重要的概念,指当主动方对象执行操作时,被关联对象(被动方)是否同步执行同一操作。
在实际的项目中,经常可以使用到级联的操作。本周也在级联上遇到了问题。
级联类型
jpa的级联类型(Cascade Types)包括:
- ALL
- PERSIST
- MERGE
- REMOVE
- REFRESH
- DETACH
ALL类型包括所有的jpa级联类型和Hibernate的级联类型。具体的使用方法,就是在实体的关联注解上使用级联类型:
@Entity
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
String name;
@OneToMany(cascade = CascadeType.ALL)
List courseList = new ArrayList<>();
.......
}
@Entity
public class Course {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
String name;
.....
}
PERSIST
PERSIST操作会将对象持久化,当使用CascadeType.PERSIST
时,代表持久化父对象时,子对象也相应的持久化,也就是级联保存。
Student student = new Student("s1");
student.setCourseList(Arrays.asList(new Course("c1")));
studentRepository.save(student);
对应的sql语句为:
insert into student (name) values (?)
insert into course (name) values (?)
insert into student_course_list (student_id, course_list_id) values (?, ?)
MERGE
MERGE操作会将具有相同标识符的对象进行更新,当时用CascadeType.MERGE
时,代表当父对象更新里的子对象更新时,更新操作会传递到子对象。
Student student = new Student("s1");
student.setCourseList(Arrays.asList(new Course("c1")));
studentRepository.save(student);
student.setName("s2");
student.getCourseList().get(0).setName("c2");
studentRepository.save(student);
对应的sql语句为:
select student0_.id as id1_1_1_, student0_.name as name2_1_1_, courselist1_.student_id as student_1_2_3_, course2_.id as course_l2_2_3_, course2_.id as id1_0_0_, course2_.name as name2_0_0_ from student student0_ left outer join student_course_list courselist1_ on student0_.id=courselist1_.student_id left outer join course course2_ on courselist1_.course_list_id=course2_.id where student0_.id=?
update course set name=? where id=?
update student set name=? where id=?
具体流程为:
- 从学生表中查找学生并连接课程表
- 更新课程中改变的属性,更新学生中改变的属性
REMOVE
REMOVE操作会删除数据库的实体,使用CascadeType.REMOVE
时,会级联删除所有子对象。
Student student = new Student("s1");
student.setCourseList(Arrays.asList(new Course("c1"), new Course("c2")));
studentRepository.save(student);
studentRepository.delete(student);
对应的sql语句为:
delete from student_course_list where student_id=?
delete from course where id=?
delete from course where id=?
delete from student where id=?
REFRESH
REFRESH操作代表重新从数据库中获取实体信息,使用了CascadeType.REFRESH
后,会级联的获取子对象在数据库的信息。
Student student = new Student("s1");
student.setCourseList(Arrays.asList(new Course("c1"), new Course("c2")));
studentRepository.save(student);
entityManager.refresh(student);
DETACH
DETACH操作表示从持久化的上下文移除对象,当使用了CascadeType.DETACH
后,子对象也会从持久化的上下文中移除。
Student student = new Student("s1");
Course course = new Course("c1");
student.setCourseList(Arrays.asList(course));
studentRepository.save(student);
assertThat(entityManager.contains(student)).isTrue();
assertThat(entityManager.contains(course)).isTrue();
entityManager.detach(student);
assertThat(entityManager.contains(student)).isFalse();
assertThat(entityManager.contains(course)).isFalse();
orphanRemoval
之前的级联代表的都是从父操作到子操作的延伸,在写项目的时候,就遇到了这样的问题:学生选择了政治、英语课,然后修改学生课程为语文、数学。然而级联并不能做到当学生课程集合改变时,自动删除原先课程并创建新课程。这时候就需orphanRemoval
了,官方文档给出的解释为:
When a target entity in one-to-one or one-to-many relationship is removed from the relationship, it is often desirable to cascade the remove operation to the target entity. Such target entities are considered “orphans,” and theorphanRemovalattribute can be used to specify that orphaned entities should be removed. For example, if an order has many line items and one of them is removed from the order, the removed line item is considered an orphan. IforphanRemovalis set totrue, the line item entity will be deleted when the line item is removed from the order.TheorphanRemovalattribute in@OneToManyand@oneToOnetakes a Boolean value and is by default false.
The following example will cascade the remove operation to the orphaned customer entity when it is removed from the relationship:
@OneToMany(mappedBy="customer", orphanRemoval="true")
public ListgetOrders() { ... }
大致就是说,当一个one-to-one或一个one-to-many的关系发生改变的时候,他会自动删除相关联的数据。
Student student = new Student("s1");
List courses1 = new ArrayList<>();
courses1.add(new Course("c1"));
student.setCourseList(courses1);
studentRepository.save(student);
student.getCourseList().clear();
student.getCourseList().add(new Course("c2"));
studentRepository.save(student);
对应的sql语句为:
insert into course (name) values (?)
insert into student_course_list (student_id, course_list_id) values (?, ?)
delete from course where id=?