基于外键的 1-1 关联关系:在双向的一对一关联中,需要在关系被维护端(inverse side)中的 @OneToOne 注解中指定 mappedBy,以指定是这一关联中的被维护端。同时需要在关系维护端(owner side)建立外键列指向关系被维护端的主键列。

【1】Manager与Department

一个部门与一个经理唯一对应,由部门维持关联关系,即以Department为主,Manager为从。

Manager实体类如下:

@Table(name="JPA_MANAGERS")
@Entity
public class Manager {

private Integer id;
private String mgrName;

private Department dept;

@GeneratedValue
@Id
public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

@Column(name="MGR_NAME")
public String getMgrName() {
return mgrName;
}

public void setMgrName(String mgrName) {
this.mgrName = mgrName;
}

//对于不维护关联关系, 没有外键的一方, 使用 @OneToOne 来进行映射,
//建议设置 mappedBy=true
@OneToOne(mappedBy="mgr")
public Department getDept() {
return dept;
}

public void setDept(Department dept) {
this.dept = dept;
}
}

Department实体类如下:

@Table(name="JPA_DEPARTMENTS")
@Entity
public class Department {

private Integer id;
private String deptName;

private Manager mgr;

@GeneratedValue
@Id
public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

@Column(name="DEPT_NAME")
public String getDeptName() {
return deptName;
}

public void setDeptName(String deptName) {
this.deptName = deptName;
}

//使用 @OneToOne 来映射 1-1 关联关系。
//若需要在当前数据表中添加外键则需要使用 @JoinColumn 来进行映射.
//注意, 1-1 关联关系, 所以需要添加 unique=true
@JoinColumn(name="MGR_ID", unique=true)
@OneToOne(fetch=FetchType.LAZY)
public Manager getMgr() {
return mgr;
}

public void setMgr(Manager mgr) {
this.mgr = mgr;
}
}

【2】双向1对1持久化操作

代码示例如下:

@Test
public void testOneToOnePersistence(){
Manager mgr = new Manager();
mgr.setMgrName("M-BB");

Department dept = new Department();
dept.setDeptName("D-BB");

//设置关联关系
mgr.setDept(dept);
dept.setMgr(mgr);

//执行保存操作
entityManager.persist(mgr);
entityManager.persist(dept);
}

如上所示,先保存不维持关联关系的一端,控制台输出如下:

JPA - 双向1对1映射_双向1对1


如果先保存dept,即维持关联关系的一端:

//执行保存操作
entityManager.persist(dept);
entityManager.persist(mgr);

JPA - 双向1对1映射_jpa_02

综上可知,双向 1-1 的关联关系, 建议先保存不维护关联关系的一方, 即没有外键(@JoinColumn)的一方, 这样不会多出 UPDATE 语句。


【3】双向一对一获取操作

① 先获取维持关联关系的一端(Department),代码示例如下:

@Test
public void testOneToOneFind(){
Department dept = entityManager.find(Department.class, 1);
System.out.println(dept.getDeptName());
System.out.println(dept.getMgr().getClass().getName());
}

  • 如果加载策略为EAGER,控制台输出如下:

JPA - 双向1对1映射_加载_03


默认情况下, 若获取维护关联关系的一方, 则会通过左外连接获取其关联的对象。 但可以通过 @OntToOne 的 fetch 属性来修改加载策略。

  • 如果加载策略为懒加载,控制台输出如下:

JPA - 双向1对1映射_jpa_04

另外对比两次获取的manager对象可知,懒加载是获取的为代理对象,EAGER时获取的为实际对象。


② 如果先获取不维持关联关系的一方。

@Test
public void testOneToOneFind2(){
Manager mgr = entityManager.find(Manager.class, 1);
System.out.println(mgr.getMgrName());

System.out.println(mgr.getDept().getClass().getName());
}

  • EAGER策略时,控制台输出如下:
@OneToOne(mappedBy="mgr",fetch=FetchType.EAGER)
public Department getDept() {
return dept;
}

JPA - 双向1对1映射_关联关系_05


  • 懒加载策略时,控制台输出如下:
@OneToOne(mappedBy="mgr",fetch=FetchType.LAZY)
public Department getDept() {
return dept;
}

JPA - 双向1对1映射_关联关系_06

默认情况下, 若获取不维护关联关系的一方, 则也会通过左外连接获取其关联的对象。

可以通过 @OneToOne 的 fetch 属性来修改加载策略. 但依然会再发送 SQL 语句来初始化其关联的对象。

这说明在不维护关联关系的一方, 不建议修改 fetch 属性。