首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 程式設計入門

脫離 Web 容器在 Java SE 中復用 Hibernate 實體與 DAO 完整實戰指南

     編輯:Java 程式設計入門

前言:為何需要在容器外使用 Hibernate?

企業級應用常將資料存取邏輯封裝在 Hibernate 實體與 Spring DAO 中,運行於 Web 容器 (Tomcat, WebLogic 等) 內。但批次匯入、資料遷移、報表產生、命令列工具等非 Web 場景同樣需要這些邏輯。重寫資料存取層不僅浪費時間,更易引入不一致。本文示範如何在獨立 Java SE 應用程式中,零容器依賴地復用既有 Hibernate 映射、實體類別與 DAO 邏輯。

一、核心挑戰與解決策略

挑戰 容器內方案 Java SE 方案
設定來源 JNDI DataSource, hibernate.cfg.xml 載入由容器管理 手動載入 .properties 檔,程式化建立 Configuration
映射檔發現 類別路徑掃描由容器/框架處理 掃描 CLASSPATH 中的 JAR,自動收集 *.hbm.xml
Session 管理 Spring HibernateTemplate / OpenSessionInView 手動開啟/關閉 Session,或擴充 DAO 接受外部 Session
事務控制 Spring @Transactional / JTA 程式化 API (session.beginTransaction()) 或獨立事務管理器

二、步驟 1:外部化設定 — 使用 properties 檔取代 XML

hibernate.properties (置於 CLASSPATH 根目錄):

hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
hibernate.connection.driver_class=org.postgresql.Driver
hibernate.connection.url=jdbc:postgresql://devserver/devdb
hibernate.connection.username=dbuser
hibernate.connection.password=dbpassword
hibernate.query.substitutions yes 'Y', no 'N'
hibernate.show_sql=true
hibernate.format_sql=true

程式碼載入:

Properties props = new Properties();
try (InputStream is = getClass().getResourceAsStream("/hibernate.properties")) {
    props.load(is);
} catch (IOException e) {
    throw new RuntimeException("無法載入 hibernate.properties", e);
}

Configuration cfg = new Configuration();
cfg.setProperties(props);

三、步驟 2:自動發現並載入所有映射檔

避免手工維護 cfg.addResource(),掃描 CLASSPATH 中的實體 JAR:

List mappingFiles = new ArrayList<>();

String classPath = System.getProperty("java.class.path");
String[] paths = classPath.split(File.pathSeparator); // 跨平台分隔符

for (String path : paths) {
    File file = new File(path);
    if (file.isFile() && file.getName().endsWith(".jar") && file.getName().contains("entity")) {
        try (JarFile jar = new JarFile(file)) {
            Enumeration entries = jar.entries();
            while (entries.hasMoreElements()) {
                JarEntry entry = entries.nextElement();
                String name = entry.getName();
                if (name.endsWith(".hbm.xml")) {
                    mappingFiles.add(name);
                }
            }
        }
    }
}

// 批次載入
for (String mapping : mappingFiles) {
    cfg.addResource(mapping);
}

優點: 新增實體只需打包進 JAR,無需修改啟動程式碼。

四、步驟 3:建立 SessionFactory 與 Session

// 取得 JDBC 連線 (或使用連線池如 HikariCP)
Class.forName(props.getProperty("hibernate.connection.driver_class"));
Connection conn = DriverManager.getConnection(
    props.getProperty("hibernate.connection.url"),
    props.getProperty("hibernate.connection.username"),
    props.getProperty("hibernate.connection.password")
);

// 建立 SessionFactory (重量級,應用程式只建一次)
SessionFactory sessionFactory = cfg.buildSessionFactory();

// 開啟 Session (輕量級,每單元工作一個)
Session session = sessionFactory.openSession(conn); // Hibernate 3/4/5 語法略有不同

五、步驟 4:改造既有 DAO 以支援外部 Session 注入

方案 A:Spring HibernateTemplate 版本

public class PersonDAO extends HibernateTemplate {
    
    // 原本由 Spring 注入 SessionFactory
    // 新增:接受外部 Session 並提取其 SessionFactory
    public void setExternalSession(Session session) {
        setSessionFactory(session.getSessionFactory());
    }
    
    public Person save(Person p) {
        if (p != null) super.save(p);
        return p;
    }
}

使用方式:

PersonDAO dao = new PersonDAO();
dao.setExternalSession(session); // 注入手動建立的 Session
dao.save(new Person("John"));

方案 B:純 Hibernate DAO (無 Spring)

public class PersonDAO {
    private Session session;
    
    // 原有建構子 (容器環境用 JNDI 查找)
    public PersonDAO() { /* JNDI lookup ... */ }
    
    // 新增:接受外部 Session 的建構子/設值器
    public PersonDAO(Session session) {
        this.session = session;
    }
    
    public void setSession(Session session) {
        this.session = session;
    }
    
    public Person save(Person p) {
        if (p != null) session.save(p);
        return p;
    }
}

六、步驟 5:事務管理 — 容器外的責任

容器外沒有宣告式事務,必須手動控制:

Transaction tx = null;
try {
    tx = session.beginTransaction();
    
    PersonDAO dao = new PersonDAO(session);
    dao.save(new Person("Alice"));
    dao.save(new Person("Bob"));
    
    // 批次處理時定期 flush/clear 避免 OOM
    session.flush();
    session.clear();
    
    tx.commit();
} catch (Exception e) {
    if (tx != null) tx.rollback();
    throw e;
} finally {
    session.close(); // 歸還連線
}

批次處理最佳實務: 每 20-50 筆 flush() + clear(),並設定 hibernate.jdbc.batch_size。

七、完整啟動類別範例

public class DataLoaderMain {
    public static void main(String[] args) {
        // 1. 載入設定
        Properties props = loadProperties();
        
        // 2. 建立 Configuration 並自動掃描映射
        Configuration cfg = buildConfiguration(props);
        
        // 3. 建立 SessionFactory (單例)
        SessionFactory sf = cfg.buildSessionFactory();
        
        // 4. 取得連線並開啟 Session
        Connection conn = getConnection(props);
        Session session = sf.openSession(conn);
        
        // 5. 執行業務邏輯 (含事務)
        try {
            Transaction tx = session.beginTransaction();
            new PersonDAO(session).save(new Person("Test"));
            tx.commit();
        } finally {
            session.close();
            sf.close(); // 應用程式結束時關閉
        }
    }
    // ... 輔助方法省略
}

八、避坑指南與效能調校

  • 類別路徑問題:確保實體 JAR、JDBC 驅動、Hibernate 核心 JAR 都在 -cp 中
  • 映射檔路徑:cfg.addResource() 參數為「相對 CLASSPATH 根目錄的路徑」(如 com/example/Person.hbm.xml),非檔案系統路徑
  • 連線洩漏:務必在 finally 區塊關閉 Session 與 SessionFactory
  • 大量資料匯入:使用 StatelessSession、關閉二級快取、設定 hibernate.order_inserts=true、hibernate.jdbc.batch_size=50
  • 版本相容:Hibernate 3/4/5/6 API 差異大 (如 buildSessionFactory() 參數),請對應版本調整

總結

在 Java SE 中復用 Hibernate 核心價值在於「映射元資料與業務邏輯零複製」。關鍵在於:

  1. 以 .properties 外部化資料庫連線設定
  2. 掃描 JAR 自動載入 .hbm.xml 映射檔
  3. 程式化建立 SessionFactory 與 Session
  4. 改造 DAO 接受外部 Session (建構子注入或設值器)
  5. 手動管理事務邊界

此模式適用於批次作業、ETL、遷移工具、測試資料建立等所有「容器外」場景,最大化保護既有投資。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved