前言
在使用jpa持久化框架中,经常要建立关系表,jpa提供级联注解,这里说说其中的一对多@OneToMany
和多对一@ManyToOne
关系
注解说明
css
代码解读
复制代码
@OneToMany注解来定义具有一对多的多值关联,比如学校和和学生的关系
css
代码解读
复制代码
@ManyToOne注解来定义具有多对一的多值关联,比如学生和和学校的关系
使用说明
(1)建立一个学校实体类
less
代码解读
复制代码
@Data @Entity @Table(name = "t_school") @Accessors(chain = true) public class School { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; @OneToMany(mappedBy = "school") private List<Student> studentList; }
(2)建立一个学生实体类
less
代码解读
复制代码
@Data @Entity @Table(name = "t_student") public class Student { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; @ManyToOne @JoinColumn(name = "school_id") private School school; }
备注:如果关系是双向的,则@OneToMany
必须使用 mappedBy 元素来指定作为关系所有者的实体的关系字段或属性,否则jpa在自动建表的时候,会生成另外一张表
使用mappedBy
映射之后,自动生成表只会生成两张表
@JoinColumn
标识,外键关系维护在t_student
数据库表
前提
Student和School中dao层存在
kotlin
代码解读
复制代码
@Repository public interface SchoolRepository extends JpaRepository<School, Long>, JpaSpecificationExecutor<School> { }
kotlin
代码解读
复制代码
@Repository public interface StudentRepository extends JpaRepository<Student, Long>, JpaSpecificationExecutor<Student> { }
controller写法
less
代码解读
复制代码
@Slf4j @RestController @RequestMapping("/school") public class SchoolController { @Autowired private SchoolRepository schoolRepository; @Autowired private StudentRepository studentRepository; @GetMapping("/save") public void save() { School school = new School(); school.setName("aaaaa"); schoolRepository.save(school); } @GetMapping("/save1") public void save1() { Student student = new Student(); student.setName("aaaa"); studentRepository.save(student); } /** * 保存学生和学校之间的外键关系 */ @GetMapping("/save2") public void save2(@RequestParam("studentId") Long studentId, @RequestParam("schoolId") Long schoolId) { studentRepository.findById(studentId).ifPresent(e -> schoolRepository.findById(schoolId).ifPresent(x -> { e.setSchool(x); studentRepository.save(e); })); } /** * 断开学生和学校之间的外键关系 */ @GetMapping("/save3") public void save3(@RequestParam("id") Long id) { studentRepository.findById(id).ifPresent(e -> { e.setSchool(null); studentRepository.save(e); }); } /** * 删除学生数据 */ @GetMapping("/deleteStudentById") public void deleteStudentById(@RequestParam("id") Long id) { studentRepository.deleteById(id); } /** * 删除学校数据 */ @GetMapping("/deleteSchoolById") public void deleteSchoolById(@RequestParam("id") Long id) { schoolRepository.deleteById(id); } }
(1)调用save接口,保存学校数据,数据为:
(2)调用save1接口,保存学生数据,数据为:
(3)调用save2关联两张表外键关系
(4)调用save3接口,可以断开两者之间的外键关系
(5)调用deleteStudentById接口,可以单独删除学生表数据
这时候学生表数据不见了,学校数据还在
(6)但这时候,如果学校和学生数据还存在外键关联,这时候删除学校数据,则会报错
报存在关键关系,不给删除,这个时候,我们要删除学校数据怎么办?就是先断开学校和学生的外键关系,然后再删除学校数据
less
代码解读
复制代码
/** * 断开学生和学校之间的外键关系 */ @GetMapping("/deleteSchoolById1") public void deleteSchoolById1(@RequestParam("id") Long id) { schoolRepository.findById(id).ifPresent(e -> { e.getStudentList().forEach(s -> { s.setSchool(null); studentRepository.save(s); }); }); schoolRepository.deleteById(id); }
备注:
sql
代码解读
复制代码
@OneToMany注解有个orphanRemoval字段,用于标识删除一方(One)数据的同时是否删除掉多方(Many)的数据,默认是false,当设置为true的时候,删除学校,就不会报错了,就会把学生关联数据一起删除
将orphanRemoval
设置为true
,这个时候,删除学校数据,就会把学生表一起删除
arduino
代码解读
复制代码
@ManyToOne有个optional字段,关联是否为可选,如果设置为 false,则必须始终存在非空关系。默认为 true
CascadeType字段
CascadeType有多个类型
arduino
代码解读
复制代码
CascadeType.ALL:所有操作都会级联执行,包括保存(persist)、更新(merge)、删除(remove)等。 CascadeType.PERSIST:级联保存操作。 CascadeType.MERGE:级联更新操作。 CascadeType.REMOVE:级联删除操作。 CascadeType.REFRESH:级联刷新操作。 CascadeType.DETACH:级联脱管操作。
以以上School
实体类为例,
less
代码解读
复制代码
@Data @Entity @Table(name = "t_school") @Accessors(chain = true) public class School { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; @OneToMany(mappedBy = "school", cascade = CascadeType.ALL) private List<Student> studentList; }
Student
实体类
less
代码解读
复制代码
@Data @Entity @Table(name = "t_student") public class Student { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; @ManyToOne @JoinColumn(name = "school_id") private School school; }
就代表School的增删改查,影响Student类,这个时候,可以级联保存了
controller写法
less
代码解读
复制代码
@Slf4j @RestController @RequestMapping("/student") public class StudentController { @Autowired private SchoolRepository schoolRepository; @Autowired private StudentRepository studentRepository; @GetMapping("/save") public void save() { School school = new School(); school.setName("hello"); Student student = new Student(); student.setName("aa1"); List<Student> studentList = new ArrayList<>(10); studentList.add(student); studentList.forEach(e->{ e.setSchool(school); }); school.setStudentList(studentList); schoolRepository.save(school); } /** * 删除学校数据 */ @GetMapping("/deleteSchoolById") public void deleteSchoolById(@RequestParam("id") Long id) { schoolRepository.deleteById(id); } /** * 删除学生数据 */ @GetMapping("/deleteStudentById") public void deleteStudentById(@RequestParam("id") Long id) { studentRepository.deleteById(id); } }
(1)执行save接口,会同时保存School
和Student
数据,
备注:这里需要注意的是,要把School实体类塞到Student实体类,否则不会级联保存
(2)执行deleteSchoolById接口,会同时删除School
和Student
数据。因为School
使用了ALL,也就是所有操作都会级联影响Student
数据
(3)执行deleteStudentById接口,只会删除Student
数据,因为Student没有级联
(4)如果想断开学生表外键,可以使用
less
代码解读
复制代码
/** * 断开学生表外键 */ @GetMapping("/hello") public void hello(@RequestParam("id") Long id) { schoolRepository.findById(id).ifPresent(e -> { e.getStudentList().forEach(s -> { s.setSchool(null); studentRepository.save(s); }); }); }
这个时候再执行接口删除,就不会删除学生表了,但是要注意的是,要分两个接口调用
(5)如果想学生表增删等,也影响学校表,那就一起级联
less
代码解读
复制代码
@Data @Entity @Table(name = "t_student") public class Student { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; @ManyToOne(cascade = CascadeType.ALL) @JoinColumn(name = "school_id") private School school; }
总结
jpa中的一对多和多对一如果相对熟悉的话,可以推荐使用,但是一般在开发过程中,因人而异,大部分不会采用外键级联表,这种根据自己技术决定