延續前文單向映射,本文詳細解析一對一外鍵關聯的雙向映射配置,以 Person(人員)與 IdCard(身分證)為例,展示完整 POJO、映射檔與關鍵註解對照。
業務規則:每人僅持有一張身分證,每張身分證歸屬一人。資料庫採用外鍵關聯,由 t_person 表持有 id_card_id 外鍵指向 t_idcard 主鍵。
// Person.java
public class Person {
private int id;
private String name;
private IdCard idCard; // 關聯對象
// Getter/Setter 省略
}
// IdCard.java
public class IdCard {
private int id;
private String cardNo;
private Person person; // 反向導航
// Getter/Setter 省略
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"https://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="org.apple.hibernate">
<class name="Person" table="t_person">
<id name="id">
<generator class="native"/>
</id>
<property name="name"/>
<!--
unique="true" 確保一對一語義
cascade="save-update,delete" 級聯持久化操作
-->
<many-to-one name="idCard"
class="IdCard"
column="id_card_id"
unique="true"
cascade="save-update,delete"/>
</class>
</hibernate-mapping>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"https://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="org.apple.hibernate">
<class name="IdCard" table="t_idcard">
<id name="id">
<generator class="native"/>
</id>
<property name="cardNo"/>
<!--
property-ref="idCard" 指定 Person 端關聯屬性名
constrained="true" 宣告外鍵約束,確保資料完整性
-->
<one-to-one name="person"
class="Person"
property-ref="idCard"
constrained="true"/>
</class>
</hibernate-mapping>
| 屬性 | 位置 | 作用 | 必要性 |
|---|---|---|---|
| unique="true" | <many-to-one> | 資料庫層面建立唯一索引,強制一對一 | 必填 |
| property-ref | <one-to-one> | 告知 Hibernate 透過 Person.idCard 反向查找,而非主鍵 | 外鍵模式必填 |
| constrained="true" | <one-to-one> | 生成 DDL 時添加外鍵約束;刪除 Person 時級聯刪除 IdCard | 建議啟用 |
| cascade | <many-to-one> | 定義持久化操作傳播範圍 | 視業務而定 |
// Person.java
@Entity
@Table(name = "t_person")
public class Person {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "id_card_id", unique = true)
private IdCard idCard;
// ...
}
// IdCard.java
@Entity
@Table(name = "t_idcard")
public class IdCard {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String cardNo;
@OneToOne(mappedBy = "idCard")
private Person person;
// ...
}
// 正確做法
person.setIdCard(idCard);
idCard.setPerson(person); // 僅為物件圖一致性,不觸發 SQL
one-to-one 預設 fetch=EAGER,易造成 N+1 查詢。建議:
@OneToOne(fetch = FetchType.LAZY)
@LazyToOne(LazyToOneOption.NO_PROXY) // 需啟用位元組碼增強
<one-to-one constrained="true"/> 且無 property-ref。@Test
public void testBidirectionalOneToOne() {
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
IdCard card = new IdCard();
card.setCardNo("A123456789");
Person person = new Person();
person.setName("張三");
person.setIdCard(card);
card.setPerson(person); // 雙向綁定
session.save(person); // 級聯儲存 card
tx.commit();
session.close();
// 驗證反向查詢
Session session2 = sessionFactory.openSession();
IdCard loadedCard = session2.get(IdCard.class, card.getId());
assertNotNull(loadedCard.getPerson());
assertEquals("張三", loadedCard.getPerson().getName());
session2.close();
}
掌握 property-ref 與 constrained 兩大核心屬性,即可駕馭一對一外鍵雙向映射的各種變體場景。