前言
在使用jpa持久化框架中,经常要建立关系表,jpa提供级联注解,这里说说其中的一对多@OneToMany和多对一@ManyToOne关系
注解说明
@OneToMany注解来定义具有一对多的多值关联,比如学校和和学生的关系
@ManyToOne注解来定义具有多对一的多值关联,比如学生和和学校的关系
使用说明
(1)建立一个学校实体类
@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)建立一个学生实体类
@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层存在
@Repository
public interface SchoolRepository extends JpaRepository<School, Long>,
JpaSpecificationExecutor<School> {
}
@Repository
public interface StudentRepository extends JpaRepository<Student, Long>,
JpaSpecificationExecutor<Student> {
}
controller写法
@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)但这时候,如果学校和学生数据还存在外键关联,这时候删除学校数据,则会报错
报存在关键关系,不给删除,这个时候,我们要删除学校数据怎么办?就是先断开学校和学生的外键关系,然后再删除学校数据
/**
* 断开学生和学校之间的外键关系
*/
@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);
}
备注:
@OneToMany注解有个orphanRemoval字段,用于标识删除一方(One)数据的同时是否删除掉多方(Many)的数据,默认是false,当设置为true的时候,删除学校,就不会报错了,就会把学生关联数据一起删除
将
orphanRemoval设置为true,这个时候,删除学校数据,就会把学生表一起删除
@ManyToOne有个optional字段,关联是否为可选,如果设置为 false,则必须始终存在非空关系。默认为 true
CascadeType字段
CascadeType有多个类型
CascadeType.ALL:所有操作都会级联执行,包括保存(persist)、更新(merge)、删除(remove)等。
CascadeType.PERSIST:级联保存操作。
CascadeType.MERGE:级联更新操作。
CascadeType.REMOVE:级联删除操作。
CascadeType.REFRESH:级联刷新操作。
CascadeType.DETACH:级联脱管操作。
以以上School实体类为例,
@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实体类
@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写法
@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)如果想断开学生表外键,可以使用
/**
* 断开学生表外键
*/
@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)如果想学生表增删等,也影响学校表,那就一起级联
@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中的一对多和多对一如果相对熟悉的话,可以推荐使用,但是一般在开发过程中,因人而异,大部分不会采用外键级联表,这种根据自己技术决定