在使用 Hibernate 實現單向一對多關聯時,常遇到 Field 'sid' doesn't have a default value 異常。本文透過具體案例剖析根因:inverse="false" (預設) 導致 Hibernate 分兩條 SQL 執行插入與更新,與資料庫外鍵 NOT NULL 約束衝突,並給出正確配置方案。
異常堆疊:
java.sql.SQLException: Field 'sid' doesn't have a default value
Hibernate: insert into hbql.score (score, type) values (?, ?)
Hibernate: update hbql.score set sid=? where id=?
測試代碼:
Student student = new Student("huizhi", "2708", 200);
Score score1 = new Score(98, "1");
Score score2 = new Score(90, "2");
Set<Score> set = new HashSet<>();
set.add(score1); set.add(score2);
student.setScores(set);
saveObject(student);
<set name="scores" table="score" cascade="save-update" inverse="false">
<key>
<column name="sid" not-null="true" />
</key>
<one-to-many class="score.Score" />
</set>
<class name="score.Score" table="score">
<id name="id"><generator class="native"/></id>
<property name="score"/>
<property name="type"/>
<!-- 無對 Student 的反向引用 -->
</class>
CREATE TABLE `score` (
`id` int(10) unsigned NOT NULL auto_increment,
`score` int(10) unsigned NOT NULL,
`type` varchar(20) NOT NULL,
`sid` int(10) unsigned NOT NULL, -- 外鍵非空
PRIMARY KEY (`id`),
CONSTRAINT `FK_score_1` FOREIGN KEY (`sid`) REFERENCES `student` (`id`)
) ENGINE=InnoDB;
<set inverse="false"> (預設) 表示**關聯關係由一端 (Student) 維護**。Hibernate 執行流程:
Score 記錄,**外鍵 sid 為 NULL** (因多端無反向引用,不知學生 ID)。Student 並取得生成 ID。UPDATE score SET sid = ? WHERE id = ? 更新外鍵。但資料庫定義 sid NOT NULL,步驟 1 直接違反約束,拋出異常。
將 sid 改為 NULL DEFAULT NULL。但違反關聯完整性設計,不建議。
inverse="true" (推薦)將關聯維護權移交給多端 (Score),但單向一對多**無法**在多端維護關聯 (因無反向引用屬性)。故單向一對多**必須**保持 inverse="false"。
在 Score 加入 Student student 屬性與對應映射:
<!-- Score.hbm.xml -->
<many-to-one name="student" class="student.Student" column="sid" not-null="true"/>
<!-- Student.hbm.xml -->
<set name="scores" inverse="true" cascade="save-update">
<key column="sid" not-null="true"/>
<one-to-many class="score.Score"/>
</set>
此時 inverse="true",由多端控制外鍵插入,Hibernate 會在插入 Score 時直接帶上 sid,單條 SQL 完成,完美解決問題。
在 <key> 移除 not-null="true",資料庫同步修改為允許 NULL。但語義上削弱了強制關聯約束。
| 場景 | inverse 設定 | 外鍵策略 | 建議 |
|---|---|---|---|
| 雙向一對多 | inverse="true" (多端維護) | NOT NULL 可行 | ✅ 最佳 |
| 單向一對多 | inverse="false" (一端維護) | 需允許 NULL 或雙向改造 | ⚠️ 建議改雙向 |
單向一對多雖簡單,但受限於 Hibernate 持久化順序,易與資料庫約束衝突。實務上**強烈建議採用雙向關聯**,將關聯維護權交給多端,兼顧效能與數據完整性。