Hibernate 提供 save() 與 persist() 兩個看似相同的持久化方法,實則在返回值、SQL 發送時機、長會話場景有關鍵差異。選錯會導致主鍵獲取失敗、事務邊界混亂、效能退化。本文深度對比並給出選型建議。
| 維度 | save() | persist() |
|---|---|---|
| 返回值 | 可序列化的主鍵值 (即生成的 ID) | void (無返回值) |
| INSERT 發送時機 | 立即執行 (為獲取 ID) | 可延遲至 flush() 或事務提交 |
| 事務邊界要求 | 無特殊要求 | 必須在事務中,否則拋異常 |
| 長會話適用性 | 差 (過早 INSERT 佔用連接、鎖) | 優 (延遲 INSERT,批次優化) |
| JPA 標準 | 非標準 (Hibernate 專有) | 標準 (EntityManager.persist()) |
| 游離對象調用 | 嘗試 INSERT (可能重複主鍵) | 拋 EntityExistsException |
public Serializable save(Object object) {
// 1. 立即執行 SaveOrUpdateEvent
// 2. 若 id 為 null,執行 Insert 生成 id
// 3. 返回生成的 id
return getSession().save(object); // 內部調用 ActionQueue.addInsertAction
}
關鍵點:為了返回 ID,必須立即執行 INSERT,無法受益於批次插入 (hibernate.jdbc.batch_size)。
public void persist(Object object) {
// 1. 檢查是否在事務中,否則拋異常
// 2. 標記實體為 PERSISTENT,加入 ActionQueue
// 3. INSERT 延遲至 flush() / commit()
getSession().persist(object);
}
// save() 適用
Long id = (Long) session.save(user);
return ResponseEntity.ok(id); // 立即返回 ID
// persist() 適用,配合批次插入
session.setFlushMode(FlushMode.MANUAL);
for (Order o : orders) session.persist(o);
if (i % 50 == 0) { session.flush(); session.clear(); } // 批次刷新
session.flush(); // 統一發送 INSERT
save() 會導致每次循環立即 INSERT,無法批次,效能差 10-100 倍。
// 持久化上下文保持開啟
@Transactional
public void saveDraft(Article draft) {
if (draft.getId() == null) session.persist(draft); // 延遲 INSERT
else session.merge(draft);
}
// 用戶點「發布」時才 commit,期間草稿可多次修改無額外 INSERT
TransactionRequiredException。@Transactional。
save()= 立即 INSERT + 返回 ID (Hibernate 專有,適合即時需 ID 場景)
persist()= 延遲 INSERT + 無返回值 (JPA 標準,適合批次、長會話、規範專案)
理解兩者在 ActionQueue 排隊時機 的本質差異,是 Hibernate 效能調優與架構設計的關鍵一環。