企業級應用常將資料存取邏輯封裝在 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()) 或獨立事務管理器 |
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);
避免手工維護 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,無需修改啟動程式碼。
// 取得 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 語法略有不同
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"));
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;
}
}
容器外沒有宣告式事務,必須手動控制:
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(); // 應用程式結束時關閉
}
}
// ... 輔助方法省略
}
-cp 中cfg.addResource() 參數為「相對 CLASSPATH 根目錄的路徑」(如 com/example/Person.hbm.xml),非檔案系統路徑finally 區塊關閉 Session 與 SessionFactoryStatelessSession、關閉二級快取、設定 hibernate.order_inserts=true、hibernate.jdbc.batch_size=50buildSessionFactory() 參數),請對應版本調整在 Java SE 中復用 Hibernate 核心價值在於「映射元資料與業務邏輯零複製」。關鍵在於:
.properties 外部化資料庫連線設定.hbm.xml 映射檔SessionFactory 與 SessionSession (建構子注入或設值器)此模式適用於批次作業、ETL、遷移工具、測試資料建立等所有「容器外」場景,最大化保護既有投資。