Hibernate 將 Java 對象映射為資料庫記錄的過程中,對象會在三種核心狀態間流轉:**瞬時態**、**持久態**、**脫管態**。精準理解狀態轉換規則與對應 Session 方法,是避免 LazyInitializationException、NonUniqueObjectException、資料不一致等經典坑點的基石。
| 狀態 | 英文 | 別稱 | 核心特徵 | 典型來源 |
|---|---|---|---|---|
| 瞬時態 | Transient | VO (Value Object) | 無 ID、無 Session 關聯、無資料庫記錄、GC 可回收 | new Entity() |
| 持久態 | Persistent | PO (Persistence Object) | 有 ID、有 Session 關聯、有資料庫記錄、變更自動同步 | save()、get()、load() |
| 脫管態 | Detached | VO (Value Object) | 有 ID、無 Session 關聯、有資料庫記錄、變更需手動同步 | close()、evict()、clear() |
瞬時態 (new)
│ save() / persist() / saveOrUpdate()
▼
持久態 (Session 管理)
│ │
│ delete() │ close() / evict() / clear()
▼ ▼
瞬時態 (DB 記錄刪除) 脫管態 (游離)
▲ │
│ │ update() / saveOrUpdate() / merge() / lock() / replicate()
└──────────────────────┘
save(),但不保證立即產生 ID (延遲到 flush/commit)persist()NonUniqueObjectExceptionsave();有 ID → update()NonUniqueObjectException 限制NonUniqueObjectException,安全性最高merge() 替代 update()/saveOrUpdate()LockMode.NONE:單純關聯;LockMode.UPGRADE:SELECT ... FOR UPDATE// 錯誤示範
User detached = userService.getById(1L); // Session1 關閉,變脫管
User persistent = session.get(User.class, 1L); // Session2 已載入同 ID
session.update(detached); // 拋 NonUniqueObjectException!
解決: 使用 merge(detached),或先 evict(persistent) 再 update(detached)。
User user = session.get(User.class, 1L);
session.close(); // user 變脫管
user.setName("New Name"); // 修改脫管對象
// 忘記 merge/update → 資料庫未更新!
鐵律: 脫管對象修改後,**必須**調用 merge()、saveOrUpdate() 或 update() 重新關聯。
User user = session.get(User.class, 1L);
session.close();
user.getOrders().size(); // 拋 LazyInitializationException!
解決:
JOIN FETCH / EntityGraph 主動載入| 場景 | 推薦方法 | 理由 |
|---|---|---|
| 新建實體持久化 | persist() | JPA 標準、不立即生成 ID、事務邊界安全 |
| 脫管實體更新 (不確定是否存在) | merge() | 無 NonUniqueObjectException、返回新持久實例、JPA 標準 |
| 脫管實體更新 (確知存在、追求效能) | update() | 避免額外 SELECT、但需自行保證無衝突 |
| 通用持久化 (新/舊混合) | saveOrUpdate() | 便利但有風險,僅用於受控環境 |
| 只讀重新關聯 | lock(obj, LockMode.NONE) | 零 SQL 開銷 |
| 刪除實體 | delete() 或 remove() (JPA) | 標準刪除流程 |
// Hibernate 5/6
Session session = ...;
PersistenceContext pc = session.getSessionImplementation().getPersistenceContext();
boolean isPersistent = pc.getEntityEntry(entity) != null;
boolean isDetached = !isPersistent && entity.getId() != null;
boolean isTransient = entity.getId() == null;
掌握 Hibernate 三態流轉與方法語義,能從根本上消除 80% 的 ORM 疑難雜症。核心原則:**明確對象當前狀態、選擇語義匹配的方法、優先使用 JPA 標準 API (persist/merge)、謹慎處理脫管對象重新關聯。**