本文基于 SpringBoot + Hibernate 下的 JPA
总的来说,就是在 @OneToMany
和 @ManyToOne
注解的基础上结合使用两者。下面以 Lead
和 Attachment
两个实体为例进行说明,即一个 Lead
和多个 Attachment
双向关联。
@Entity
@Table(name = "lead")
public class Lead {
@OneToMany
private List<Attachment> attachmentList = new ArrayList<>();
}
@Entity
@Table(name = "attachment")
public class Attachment {
@ManyToOne
private Lead lead;
}
在 One
端,即 Lead
中使用 @OneToMany
注解,而在 Many
端,即 Attachment
中使用 @ManyToOne
注解。
关联媒介
我们知道关系的关联媒介有两种
- 通过中间表关联,由
@JoinColumn
配置 - 通过外键关联,由
@JoinTable
配置
而在双向关系中,这两个注解加在 @OneToMany
端才有效,即此时关联媒介由 One
端决定。下面以 @JoinColumn
为例,其 name 属性指定了 Many
端表中生成的外键的字段名。
@OneToMany
@JoinColumn(name="lead_id")
private List<Attachment> attachmentList = new ArrayList<>();
@ManyToOne
private Lead lead;
如果 @OneToMany
没有这两个注解,则按照 @OneToMany
单向时的默认情况, Hibernate 会自动生成中间表。
关系维护
在上述的配置下,关联关系由两端同时进行维护,这样会产生额外的 update
语句。解决办法就是 One
端将维护权交由 Many
端,通过 @OneToMany
的 mappedBy
属性实现, 其值为 Many
端实体中关系对应的字段名,这里为 lead
。
@OneToMany(mappedBy = "lead"))
private List<Attachment> attachmentList = new ArrayList<>();
@ManyToOne
private Lead lead;
由于 mappedBy
属性和 @JoinColumn
互斥,所以 @OneToMany
不能再加 @JoinColumn
, 何况此时的关联方式转由 @ManyToOne
决定,所以要实现指定外键关联,则给 @ManyToOne
添加 @JoinColumn
。
@OneToMany(mappedBy = "lead"))
private List<Attachment> attachmentList = new ArrayList<>();
@ManyToOne
@JoinColumn(name="lead_id")
private Lead lead;
Many 端的懒加载
上述配置下,还会产生另外一个问题。假设有一个孤立 Attachment
存在数据库中,即没有关联任何 Lead
。此时通过 JPA 接口的 findById
方法是找不到这个 Attachment
的。因为 @ManyToOne
的级联查询默认是饥饿获取,查找的同时会与 Lead
进行 join,而由于关联关系还不存在,所以自然找不到。解决办法就是将 @ManyToOne
的 fetch
属性设置为 FetchType.LAZY
,即懒加载。
@OneToMany(mappedBy = "lead"))
private List<Attachment> attachmentList = new ArrayList<>();
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="lead_id")
private Lead lead;