【Spring Data Jpa 01】Spring Data Jpa中的一对一, 一对多,多对多关系注解

1,987 阅读6分钟

本文主要使用到以下注解

  • @OneToOne
  • @OneToMany
  • @ManyToOne
  • @ManyToMany
  • @JoinTable
  • @JoinColumn

一对一[外键]

单向一对一关系

单向一对一关系如PetMaster之间的关系,一个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的表结构如下

single_not_join_table.png

需要注意的是 在进行如下新增操作时,如果不先添加 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);
​
}

single_one2one_no_mappedby_exception.png

解决方法有两种

第一种是先保存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;

运行结果如下:

single_jointable.png

双向一对一

此时只需要在Cat实体类中,使用如下代码即可,在上述不变的情况下。

@OneToOne(mappedBy = "cat")
private Master master;

注意

  1. 如果在Cat类中的代码如下此时运行结果是
@OneToOne(mappedBy = "cat")
@JoinTable(
    name = "cat_master",
    joinColumns ={ @JoinColumn(name = "cat_id2")},
    inverseJoinColumns = @JoinColumn(name = "master_id2",referencedColumnName = "id")
)
private Master master;

double_jointable_warning.png

可以发现在Cat类中的配置已经生效了但是没有完全生效!

  1. 在双向关联时,如果使用Lombok插件的@ToString,此时在调用对象的toString()时,会导致出现SOF

double_SOF.png

解决方法:

  1. 只要master.setCat()就可以
  2. 使用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_joincolumn.png

仅使用@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;
​

onetomany_table.png

双向关联

双向一对多关系仅需要在Cat类中添加如下代码

@ManyToOne
private Master master;

在一对多的关系上,推荐使用单向关联,因为双向关联实际上生成的表或者外键没有什么不同。但是双向关系 会增加额外的update语句。

注意:如果在多的一方使用了@JoinColum注解且和一的一方配置的JoinColumnname属性值不同则会实际运行结果如下

// 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);

}

manytoone_with_joincolumn.png

多对多关系

涉及的注解:

@ManyToMany: 标识多对多关系

主表:关系的维护端,确定由谁建立联系

从表:关系被维护端,不能建立联系。

MasterCat为例:当使用这样的代码时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;

many2many_with_jointable.png

在被维护端使用了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;

single_one2one_no_mappedby_exception.png

总结

不管是一对一还是一对多甚至多对多关系,维护关系的方式就两种一种是以外键的方式存在主表中,另一种就是以额外的表的方式建立联系。

其中一对多的关系关系主要由多端维护,毕竟根据数据库第一范式而言首先就要消除表中表,因此不可能把多端数据存在一端的表中。

对多对关系必须用其他表来维护,根据外键依赖,如果A表中要插入B表的数据则必须B表中要存在被插入的数据,同理对于B而言,要先插入数据则A表的必须存在,因此如果不用第三个表来建立联系的话,就无法插入数据。

只要理清楚数据库表之间的关系则使用什么注解,属性的配置就不成问题。

另外根据阿里巴巴的Java开发手册

alibaba.png

可以看出级联操作,外键等操作并不推荐。数据之间的级联更新应通过Java代码来实现。