Jpa 中的一对一、一对多没搞明确的话,总会感觉有点绕,明天咱们来简略聊聊这个话题。
1. 一对一
比如说一个学校有一个地址,一个地址只有一个学校。
那么咱们能够依照如下形式来设计类:
@Data@Entity@Table(name = "t_address")public class Address { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer aid; private String province; private String city; private String area; private String phone; @OneToOne(cascade = CascadeType.ALL) private School school;}@Data@Entity@Table(name = "t_school")public class School { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer sid; private String name; @OneToOne(cascade = CascadeType.ALL) private Address address;}
一对一的关系,能够只在 School 中保护,也能够只在 Address 中保护,也能够两者都保护,具体哪种,那就看需要了。
在下面的例子中,咱们在 School 和 Address 中都通过 @OneToOne 注解来保护了一对一的关系。
cascade 用来配置级联操作,有如下取值:
- ALL:所有操作
- PERSIST:级联增加
- MERGE:级联更新
- REMOVE:级联删除
- REFRESH:级联刷新
依据本人需要抉择适合的就行。
这样,最终创立进去的 t_school 表和 t_address 表中,会别离多进去一个字段 address_aid 和 school_sid,这两个字段都是外键,正是通过外键,将两张表中不同的记录关联起来。
有的人可能不习惯这种主动增加的字段,那也能够自定义该字段,反正该字段总是要有的,自定义的形式如下:
@Data@Entity@Table(name = "t_address")public class Address { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer aid; private String province; private String city; private String area; private String phone; @OneToOne(cascade = CascadeType.ALL) @JoinColumn(name = "sid",referencedColumnName = "sid") private School school; @Column(insertable = false,updatable = false) private Integer sid;}@Data@Entity@Table(name = "t_school")public class School { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer sid; private String name; @OneToOne(cascade = CascadeType.ALL) @JoinColumn(name = "aid",referencedColumnName = "aid") private Address address; @Column(insertable = false,updatable = false) private Integer aid;}
在 Address 中自定义一个 sid,并设置该字段不可增加和批改,而后通过 @JoinColumn 注解去指定关联关系,@JoinColumn 注解中的 name 示意的是以后类中的属性名,referencedColumnName 示意的则是 School 类中对应的属性名。
在 School 类中做类似的操作。
最初启动我的项目去察看 MySQL 中生成的表。
2. 一对多
一个班级中有多个学生,而一个学生只属于一个班级,咱们能够这样来定义实体类:
@Data@Table(name = "t_student")@Entitypublic class Student { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer sid; private String name; @ManyToOne(cascade = CascadeType.ALL) private Clazz clazz;}@Data@Table(name = "t_clazz")@Entitypublic class Clazz { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer cid; private String name; @OneToMany(cascade = CascadeType.ALL,fetch = FetchType.EAGER) private List<Student> students;}
Student 和 Clazz 的关系是多对一,用 @ManyToOne 注解,Clazz 和 Student 的关系是一对多,用 @OneToMany 注解。
Student 和 Clazz 的关系是多对一,未来的 t_student 表中会多进去一个属性 clazz_cid,通过这个外键将 Student 和 Clazz 关联起来。如果咱们不想要主动生成的 clazz_cid,那么也能够自定义,形式如下:
@Data@Table(name = "t_student")@Entitypublic class Student { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer sid; private String name; @ManyToOne(cascade = CascadeType.ALL) @JoinColumn(name = "cid") private Clazz clazz; @Column(insertable = false,updatable = false) private Integer cid;}
定义一个 cid 属性,并设置为不可编辑和不可增加,而后通过 @JoinColumn 注解配置 cid 属性为外键。
Clazz 和 Student 的关系是一对多,这个是通过一个主动生成的第三张表来实现的,如下:
3. 测试
3.1 增加测试
先来个一对一的增加测试,如下:
public interface SchoolRepository extends JpaRepository<School,Integer> {}@SpringBootTestclass JpaOneToManyApplicationTests { @Autowired SchoolRepository schoolRepository; @Test void contextLoads() { School school = new School(); school.setSid(1); school.setName("哈佛大学"); Address address = new Address(); address.setAid(1); address.setProvince("黑龙江"); address.setCity("哈尔滨"); address.setArea("某地"); address.setPhone("123456"); school.setAddress(address); schoolRepository.save(school); }}
在这个测试过程中,关联关系是由 t_school 一方来保护了,因而未来填充的外键是 t_school 中的 aid。增加后果如下图:
这是一个简略的增加案例。
更新也是调用 save 办法,更新的时候会先判断这个 id 是否存在,存在的话就更新,不存在就增加。
再来看班级的增加,如下:
public interface ClazzRepository extends JpaRepository<Clazz,Integer> {}@AutowiredClazzRepository clazzRepository;@Testvoid test02() { Clazz c = new Clazz(); c.setCid(1); c.setName("三年级二班"); List<Student> students = new ArrayList<>(); Student s1 = new Student(); s1.setSid(1); s1.setName("javaboy"); students.add(s1); Student s2 = new Student(); s2.setSid(2); s2.setName("张三"); students.add(s2); c.setStudents(students); clazzRepository.save(c);}
留神,增加的是班级,所以班级和学生之间关系就由第三张表来保护,而不是由学生来保护。
3.2 查问测试
再来一个简略的查问,假如咱们当初想依据省份来搜寻学校,如下:
public interface SchoolRepository extends JpaRepository<School,Integer> { List<School> findSchoolByAddressProvince(String province);}@AutowiredSchoolRepository schoolRepository;@Testvoid test01() { List<School> list = schoolRepository.findSchoolByAddressProvince("黑龙江"); System.out.println("list = " + list);}
松哥给大家捋一下 Spring Data 如何解析下面自定义的查询方法:
- 首先截取掉 findSchoolByAddressProvince 的前缀,剩下 AddressProvince。
- 查看 School 是否有 addressProvince 属性,有就依照该属性查问,对于咱们的案例,并没有 addressProvince 属性,所以持续下一步。
- 从右侧驼峰开始拆分,拆掉第一个驼峰前面的内容,咱们这里拆分之后只剩下 Address 了,判断 School 是否存在 Address 属性,不存在就持续反复该步骤,持续切掉右侧第一个驼峰。
- 在上文案例中,School 中有 address 属性,所以接下来就去查看 address 中是否有 province 属性,因为咱们这里只剩下一个 province 了,如果剩下的字符串相似于 provinceAaaBbb 这种,那么持续依照第三步去解析。
下面这个写法有一个小小的危险,假如 School 中刚好就有一个属性叫做 addressProvince,那么此时的剖析就会出错。所以,对于下面的查问,咱们也能够定义成如下形式:
public interface SchoolRepository extends JpaRepository<School,Integer> { List<School> findSchoolByAddress_Province(String province);}
此时就不会产生歧义了,零碎就晓得 province 是 address 的属性了。
再来一个班级的查问,如下:
public interface ClazzRepository extends JpaRepository<Clazz,Integer> {}@Testvoid test03() { List<Clazz> list = clazzRepository.findAll(); System.out.println("list = " + list);}
如果在查问的过程中,须要对学生进行排序,能够增加如下属性:
@Data@Table(name = "t_clazz")@Entitypublic class Clazz { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer cid; private String name; @OneToMany(cascade = CascadeType.ALL,fetch = FetchType.EAGER) @OrderBy("sid desc") private List<Student> students;}
通过 @OrderBy("sid desc") 能够设置查问的 student 排序。
好啦,几个小小的案例,心愿对大家有所帮忙,公众号后盾回复 jpa02,获取本文案例下载链接。