當需要對大量資料執行更新或刪除 (如將「計算機學院」更名為「計算機工程學院」、刪除整個「技術學院」學生) 時,逐筆載入 Entity 再修改會導致 Session 快取膨脹、記憶體溢位。Hibernate 提供 HQL 批量 DML 與 原生 JDBC 兩種繞過快取的直達資料庫方案。本文以學生表 Student 為例,對比兩種模式的代碼結構、適用場景與注意事項。
| 操作 | 業務描述 | SQL 對應 |
|---|---|---|
| 批量更新 | 將 deptName 從「計算機學院」改為「計算機工程學院」 | UPDATE Student SET deptName=? WHERE deptName=? |
| 批量刪除 | 刪除 deptName 為「技術學院」的所有學生 | DELETE FROM Student WHERE deptName=? |
核心 API:Session.createQuery(hql).executeUpdate()
優勢:類型安全、參數綁定防注入、跨資料庫可攜性、自動參與當前交易
public void updateDeptName(String newName, String oldName) {
Session session = null;
Transaction tx = null;
try {
session = getSession();
tx = session.beginTransaction();
String hql = "UPDATE Student SET deptName = :newName WHERE deptName = :oldName";
int updatedCount = session.createQuery(hql)
.setString("newName", newName)
.setString("oldName", oldName)
.executeUpdate(); // 返回受影響行數
tx.commit();
log.info("批量更新完成,影響行數: {}", updatedCount);
} catch (RuntimeException e) {
if (tx != null) tx.rollback();
throw e;
} finally {
if (session != null) session.close();
}
}
public void deleteByDeptName(String deptName) {
Session session = null;
Transaction tx = null;
try {
session = getSession();
tx = session.beginTransaction();
String hql = "DELETE FROM Student WHERE deptName = :deptName";
int deletedCount = session.createQuery(hql)
.setString("deptName", deptName)
.executeUpdate();
tx.commit();
log.info("批量刪除完成,影響行數: {}", deletedCount);
} catch (RuntimeException e) {
if (tx != null) tx.rollback();
throw e;
} finally {
if (session != null) session.close();
}
}
核心 API:Session.connection() 取得底層 Connection → PreparedStatement
適用場景:HQL 不支援的複雜 SQL (如資料庫特有語法、Hint、分區表操作)、極大批次需微調 Fetch Size/Batch Size
public void updateDeptNameJdbc(String newName, String oldName) {
Session session = null;
Transaction tx = null;
Connection conn = null;
PreparedStatement ps = null;
try {
session = getSession();
tx = session.beginTransaction();
conn = session.connection(); // 取得受 Hibernate 管理的 Connection
String sql = "UPDATE Student SET deptName = ? WHERE deptName = ?";
ps = conn.prepareStatement(sql);
ps.setString(1, newName);
ps.setString(2, oldName);
ps.executeUpdate();
tx.commit();
} catch (RuntimeException e) {
if (tx != null) tx.rollback();
throw e;
} catch (SQLException e) {
if (tx != null) tx.rollback();
throw new RuntimeException("JDBC 批量更新失敗", e);
} finally {
// 必須手動關閉 Statement,Connection 由 Session 管理勿關閉
if (ps != null) try { ps.close(); } catch (SQLException ignore) {}
if (session != null) session.close();
}
}
public void deleteByDeptNameJdbc(String deptName) {
Session session = null;
Transaction tx = null;
Connection conn = null;
PreparedStatement ps = null;
try {
session = getSession();
tx = session.beginTransaction();
conn = session.connection();
String sql = "DELETE FROM Student WHERE deptName = ?";
ps = conn.prepareStatement(sql);
ps.setString(1, deptName);
ps.executeUpdate();
tx.commit();
} catch (RuntimeException e) {
if (tx != null) tx.rollback();
throw e;
} catch (SQLException e) {
if (tx != null) tx.rollback();
throw new RuntimeException("JDBC 批量刪除失敗", e);
} finally {
if (ps != null) try { ps.close(); } catch (SQLException ignore) {}
if (session != null) session.close();
}
}
| 維度 | HQL 批量 DML | 原生 JDBC |
|---|---|---|
| 可攜性 | ✅ 跨資料庫 (方言自動轉換) | ❌ 綁定特定資料庫 SQL 語法 |
| 類型安全 | ✅ 編譯期檢查屬性名 | ❌ 字串 SQL 易拼寫錯誤 |
| 防注入 | ✅ 參數綁定自動處理 | ✅ PreparedStatement 同等防護 |
| 交易整合 | ✅ 自動加入 Hibernate 交易 | ✅ 共用 Session Connection 同交易 |
| 快取一致性 | ⚠️ 不會更新 Session 快取,後續查詢可能讀舊值 | ⚠️ 同左,需手動 session.clear() 或 evict() |
| 觸發器/攔截器 | ❌ 不觸發 Hibernate 生命週期事件 | ❌ 同左 |
| 複雜 SQL 支援 | ❌ 僅支援單表 UPDATE/DELETE | ✅ 任意 SQL (JOIN、子查詢、Hint、MERGE) |
| 效能微調 | ⚠️ 受限於 Hibernate 批次參數 | ✅ 可控制 Fetch Size、Batch Size、游標型別 |
| 維護性 | ✅ 物件導向、重構友善 | ❌ SQL 字串分散、難以重構 |
session.clear() 或開新 Session。PreUpdateEvent、PostDeleteEvent、@PreRemove 等**完全不會被調用**。@Version 欄位**。SET version = version + 1,或改用資料庫 Trigger 維護。DELETE FROM Student WHERE deptName=? AND rownum <= 5000 迴圈執行直到 executeUpdate() == 0。WITH (ROWLOCK))、分區表逐分區清理。若需在物件模型層處理大量資料 (如批量匯入、ETL),可使用 StatelessSession:
insert()、update()、delete() 直接對應 JDBC 批次原則:優先使用 HQL 批量 DML (可維護、可攜),僅在 HQL 無法表達或極致效能需求時落回 JDBC。務必處理快取一致性與觸發器缺失問題。