本文主要使用到以下注解
@OneToOne@OneToMany@ManyToOne@ManyToMany@JoinTable@JoinColumn
一对一[外键]
单向一对一关系
单向一对一关系如Pet和Master之间的关系,一个Master拥有一个Pet 通过Master可以找到Pet;
涉及的注解:
@OneToOne: 在单向关系中, 用于关系的发起者, 如下面的Master上的Pet属性上
mappedBy属性:存在于@ManyToMany, @OneToMany, 主要使用在双向关系中,表明关系由谁维护。cascade: 级联,可设置不同级联操作,如级联更新,删除等。
@JoinColumn: 关联字段,用于在关联的从表关系以外键的存在于主表中时,
name: 关联字段在当前实体所对应的表中的字段名称,默认是表名_外键referencedColumnName: 默认是从表的主键,在不想使用从表主键的情况下可以指定为其他字段
实体类代码:
@Entity
@Table(name = "master")
public class Master {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
private Long id;
@Column(name = "master_name")
private String masterName;
@OneToOne(cascade = CascadeType.PERSIST)
@JoinColumn(name = "cat_id",referencedColumnName = "id")
private Cat cat;
}
@Entity
@Table(name = "cat")
public class Cat {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
private Long id;
@Column(name = "cat_name")
private String catName;
@Column(name = "age")
private Integer age;
}
生成的Mater的表结构如下
需要注意的是 在进行如下新增操作时,如果不先添加 Pet将会出现如下异常:
@Test
public void test(){
Cat cat = new Cat();
Master master = new Master();
master.setMasterName("super");
cat.setCatName("一二");
master.setCat(cat);
// catRepository.save(cat); // 打开这句代码则不会出现
masterRepository.save(master);
}
解决方法有两种
第一种是先保存Pet
第二种是在Master的Pet属性上添加级联操作: @OneToOne( cascade = CascadeType.PERSIST)
一对一双向关联
如果想通过Pet 找到 Master,则他们之间应该建立联系,修改Cat如下
public class Cat {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
private Long id;
@Column(name = "cat_name")
private String catName;
@Column(name = "age")
private Integer age;
@OneToOne(mappedBy = "cat")
private Master master;
}
一对一 [额外的表]
如果想通过非外键的方式,通过创建额外的表方式可以采用@JoinTable注解,
@JoinTable注解
name:指定生成的表面joinColumns: 关联的字段,可以在{}设置多个@JoinColum, 表示多个关联字段inverseJoinColumns: 从表的关联字段,也可以设置多个 用{}包起来
单向一对一
Master实体类代码修改如下:
@OneToOne( cascade = CascadeType.PERSIST)
@JoinTable(
name = "master_cat",
joinColumns ={ @JoinColumn(name = "master_id")},
inverseJoinColumns = @JoinColumn(name = "cat_id")
)
private Cat cat;
运行结果如下:
双向一对一
此时只需要在Cat实体类中,使用如下代码即可,在上述不变的情况下。
@OneToOne(mappedBy = "cat")
private Master master;
注意:
- 如果在
Cat类中的代码如下此时运行结果是
@OneToOne(mappedBy = "cat")
@JoinTable(
name = "cat_master",
joinColumns ={ @JoinColumn(name = "cat_id2")},
inverseJoinColumns = @JoinColumn(name = "master_id2",referencedColumnName = "id")
)
private Master master;
可以发现在Cat类中的配置已经生效了但是没有完全生效!
- 在双向关联时,如果使用Lombok插件的
@ToString,此时在调用对象的toString()时,会导致出现SOF
解决方法:
- 只要
master.setCat()就可以 - 使用Lombok的
@ToString(exclude = "master")排除掉级联属性也可。
一对多 [外键]
单向一对多
涉及的注解
@OneToMany: 标注一对多关系, 在Jpa中, 一丢多的关系通常由多的一端维护。
- 其属性和
@OneToOne类似- 配合
@JoinCloumn的注解使用,在只使用此注解的情况下会在多的一端生成外键- 仅使用
@OneTOMany默认会生成一端表名_多端表名的表,取各自主键建立一对多关系
@ManyToOne: 用在多端,和@OneToMany同时使用表示双向关联的一对多关系
在一对多的关系中,虽然在一方配置的@JoinColumn,但由于关系由多的一端存储,所以一的外键会存在多的一端的表中。
修改实体类代码如下
// Master.java
@OneToMany(cascade = CascadeType.ALL)
@JoinColumn(name = "master_id")
private List<Cat> cat;
// Cat.java
public class Cat {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
private Long id;
@Column(name = "cat_name")
private String catName;
@Column(name = "age")
private Integer age;
}
在配合使用@JoinColumn的情况下
仅使用@OneToMany或者配合@JoinTable的情况下
@OneToMany(cascade = CascadeType.ALL)
// @JoinColumn(name = "master_id")
@JoinTable(
name = "master_pet",
joinColumns = {@JoinColumn(name = "master_id")},
inverseJoinColumns = {@JoinColumn(name = "pet_id")}
)
private List<Cat> cat;
双向关联
双向一对多关系仅需要在Cat类中添加如下代码
@ManyToOne
private Master master;
在一对多的关系上,推荐使用单向关联,因为双向关联实际上生成的表或者外键没有什么不同。但是双向关系 会增加额外的update语句。
注意:如果在多的一方使用了@JoinColum注解且和一的一方配置的JoinColumn的name属性值不同则会实际运行结果如下
// master
@OneToMany(cascade = CascadeType.ALL)
@JoinColumn(name = "master_id2")
private List<Cat> cat;
// cat
@ManyToOne
@JoinColumn(name = "pet_id")
private Master master;
//
@Test
public void test(){
Cat cat = new Cat();
Cat cat2 = new Cat();
Master master = new Master();
master.setMasterName("super");
cat.setCatName("一二");
cat2.setCatName("super2");
LinkedList<Cat> cats = new LinkedList<>();
cats.add(cat);
cats.add(cat2);
master.setCat(cats);
masterRepository.save(master);
}
多对多关系
涉及的注解:
@ManyToMany: 标识多对多关系
主表:关系的维护端,确定由谁建立联系
从表:关系被维护端,不能建立联系。
以Master和Cat为例:当使用这样的代码时master.setCats(cats);则表明master是关系的维护端。因此需要在被维护端的@ManyToMany中使用mappedBy属性。其值为被维护端的集合属性名。如果不想使用mappedBy属性,则可以使用@JoinTable指定。
// master 端
@ManyToMany
@JoinTable(
name = "master_cat_many",
joinColumns = {@JoinColumn(name = "master_id_many",referencedColumnName = "id")},
inverseJoinColumns = {@JoinColumn(name = "cat_id_many",referencedColumnName = "id")}
)
private List<Master> master;
// cat端
@ManyToMany
@JoinTable(
name = "master_cat_many",
joinColumns = {@JoinColumn(name = "cat_id_many",referencedColumnName = "id")},
inverseJoinColumns = {@JoinColumn(name = "master_id_many",referencedColumnName = "id")}
)
private List<Cat> cat;
在被维护端使用了mappedBy属性后,将不能是@JoinTable,这两者互斥。会出现这类异常。
// cat.java
@ManyToMany(mappedBy = "cat")
@JoinTable(
name = "master_cat_many",
joinColumns = {@JoinColumn(name = "cat_id_many",referencedColumnName = "id")},
inverseJoinColumns = {@JoinColumn(name = "master_id_many",referencedColumnName = "id")}
)
private List<Master> master;
总结
不管是一对一还是一对多甚至多对多关系,维护关系的方式就两种一种是以外键的方式存在主表中,另一种就是以额外的表的方式建立联系。
其中一对多的关系关系主要由多端维护,毕竟根据数据库第一范式而言首先就要消除表中表,因此不可能把多端数据存在一端的表中。
对多对关系必须用其他表来维护,根据外键依赖,如果A表中要插入B表的数据则必须B表中要存在被插入的数据,同理对于B而言,要先插入数据则A表的必须存在,因此如果不用第三个表来建立联系的话,就无法插入数据。
只要理清楚数据库表之间的关系则使用什么注解,属性的配置就不成问题。
另外根据阿里巴巴的Java开发手册
可以看出级联操作,外键等操作并不推荐。数据之间的级联更新应通过Java代码来实现。