Hibernate 作為經典 ORM 框架,SessionFactory 是重量級、線程安全的核心接口,通常作為單例存在。但在多租戶、多数據源、運行時切換配置等場景下,需要動態構建、重建、隔離 SessionFactory。本文解析一個支援動態配置文件、線程本地 Session 管理、優雅關閉的工具類實現。
hibernate.cfg.xml 路徑ThreadLocal 綁定 Session,實現「一請求一 Session」rebuildSessionFactory()DisposableBean 風格關閉釋放package com.example.hibernate;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class DynamicHibernateSessionFactory {
// 線程本地存儲 Session,實現請求級隔離
private final ThreadLocal threadLocal = new ThreadLocal<>();
// 配置對象,持有映射元數據
private Configuration configuration = new Configuration();
// 核心工廠,線程安全,重量級
private SessionFactory sessionFactory;
// 構建鎖,防止併發重建
private final Object buildLock = new Object();
/**
* 設置配置文件路徑(classpath 相對路徑或絕對路徑)
* 如: "hibernate.cfg.xml" 或 "config/hibernate-mysql.cfg.xml"
*/
public void setConfigFile(String configFile) {
// 重置配置,允許切換不同數據源
this.configuration = new Configuration();
this.configuration.configure(configFile);
// 配置變更後需重建工廠
this.sessionFactory = null;
}
/**
* 獲取當前線程綁定的 Session
* 首次調用會觸發 SessionFactory 構建
*/
public Session getSession() throws HibernateException {
Session session = threadLocal.get();
// Session 不存在或已關閉,創建新 Session
if (session == null || !session.isOpen()) {
// 雙重檢查鎖構建 SessionFactory
if (sessionFactory == null) {
synchronized (buildLock) {
if (sessionFactory == null) {
rebuildSessionFactory();
}
}
}
session = (sessionFactory != null) ? sessionFactory.openSession() : null;
threadLocal.set(session);
}
return session;
}
/** 重建 SessionFactory(配置變更後調用) */
public void rebuildSessionFactory() {
try {
sessionFactory = configuration.buildSessionFactory();
} catch (Exception e) {
System.err.println("====== SessionFactory 構建失敗 ======");
e.printStackTrace();
throw new HibernateException("構建 SessionFactory 失敗", e);
}
}
/** 關閉當前線程 Session 並從 ThreadLocal 移除 */
public void closeSession() throws HibernateException {
Session session = threadLocal.get();
threadLocal.set(null); // 先移除引用,防止洩漏
if (session != null && session.isOpen()) {
try {
session.close();
} catch (HibernateException e) {
// 忽略關閉異常,記錄日誌即可
System.err.println("Session 關閉異常:" + e.getMessage());
}
}
}
/** 關閉 SessionFactory,釋放所有資源(連接池、緩存等) */
public void shutdown() {
if (sessionFactory != null) {
try {
sessionFactory.close(); // 關鍵:釋放連接池、二級緩存等
} catch (Exception e) {
System.err.println("SessionFactory 關閉異常:" + e.getMessage());
} finally {
sessionFactory = null;
configuration = null;
}
}
// 清理可能殘留的 ThreadLocal
threadLocal.remove();
}
// ========== Getter 供高級用法 ==========
public SessionFactory getSessionFactory() { return sessionFactory; }
public Configuration getConfiguration() { return configuration; }
// ========== 使用示例 ==========
public static void main(String[] args) {
DynamicHibernateSessionFactory factory = new DynamicHibernateSessionFactory();
// 1. 切換配置(如按租戶ID加載不同 cfg.xml)
factory.setConfigFile("hibernate-tenant-A.cfg.xml");
// 2. 獲取 Session 進行操作
Session session = null;
try {
session = factory.getSession();
session.beginTransaction();
// 業務操作:session.save/update/delete/query...
session.getTransaction().commit();
} catch (Exception e) {
if (session != null) session.getTransaction().rollback();
e.printStackTrace();
} finally {
factory.closeSession(); // 歸還 Session
}
// 3. 應用關閉時釋放資源
Runtime.getRuntime().addShutdownHook(new Thread(factory::shutdown));
}
}
Web 環境中,通常在 Filter/Interceptor 中:
// 請求開始
Session session = factory.getSession();
session.beginTransaction();
try {
// Controller/Service 邏輯
} finally {
// 請求結束
if (session.getTransaction().isActive()) session.getTransaction().rollback();
factory.closeSession();
}
優勢:同一線程內多次 getSession() 返回同一實例,實現事務邊界內的 Session 共享。
rebuildSessionFactory() 耗時較長(解析映射、建立連接池),使用 synchronized + 雙重檢查避免併發重複構建。
// 租戶 A 請求
factory.setConfigFile("hibernate-tenant-A.cfg.xml");
Session sessionA = factory.getSession();
// 租戶 B 請求(需先關閉舊工廠)
factory.shutdown(); // 釋放 A 的連接池
factory.setConfigFile("hibernate-tenant-B.cfg.xml");
Session sessionB = factory.getSession();
⚠️ 生產環境多租戶建議使用 AbstractRoutingDataSource + 共享 SessionFactory,而非頻繁重建。
@Bean(destroyMethod = "shutdown")
public DynamicHibernateSessionFactory hibernateFactory() {
DynamicHibernateSessionFactory factory = new DynamicHibernateSessionFactory();
factory.setConfigFile("hibernate.cfg.xml");
return factory;
}
| 問題 | 原因 | 解決 |
|---|---|---|
| SessionFactory 重建後舊 Session 报錯 | ThreadLocal 仍持有舊 Session | rebuild 前調用 closeSession() 清理 |
| 連接池耗盡 | Session 未關閉、事務未提交/回滾 | 強制 finally 塊 closeSession()、rollback() |
| 內存洩漏 | ThreadLocal 未 remove,線程池線程複用 | closeSession() 首行 threadLocal.set(null) |
| 配置修改不生效 | Configuration 對象被複用 | setConfigFile() 中 new Configuration() |
EntityManagerFactory 替代 SessionFactory,@Transactional 管理事務SqlSessionFactory + SqlSession,輕量級無二級緩存本工具類展示了 Hibernate 核心組件的底層管理邏輯:Configuration → SessionFactory → Session 的生命周期控制,ThreadLocal 實現線程隔離,動態配置支持多環境切換。雖然現代項目多使用 Spring Data JPA,但理解這些底層機制對排查連接洩漏、事務邊界、多租戶架構設計至關重要。