採用 Hibernate 的專案若要落實自動化單元測試與持續整合 (CI),常面臨:
inverse="true/false"、lazy="true/false"、fetch="join/select/subselect")需切換映射檔。SessionFactory 導致狀態汙染。hibernate.cfg.xml 破壞自動化、易出錯。本文深入解析 Hibernate 2.1/3.x Configuration 載入機制,示範如何利用程式化 API 實現多配置檔案隔離、按需載入映射、測試專用 SessionFactory,打造零手工干預的自動化測試體系。
new Configuration() 做了什麼?hibernate.properties,存在則載入至 GLOBAL_PROPERTIES,否則輸出 hibernate.properties not found。System.getProperties() 合併入內。configure() / configure(String resource) 做了什麼?hibernate.cfg.xml,或指定資源路徑(如 myexample.xml)。<session-factory>:name 屬性覆蓋 hibernate.session_factory_name。<property> 覆蓋 hibernate.properties 同名屬性(XML 優先級較高)。<mapping>、<jcs-class-cache>、<collection-cache> 等元素。<mapping resource="Cat.hbm.xml"/> 為必須,載入實體映射檔。| 方法 | 用途 | 典型測試場景 |
|---|---|---|
setProperties(Properties) | 一次性替換所有屬性 | 切換資料庫連線參數(URL/Driver/Dialect) |
addProperties(Properties) | 疊加屬性(不覆蓋既有) | 套用共用基礎屬性後再覆蓋測試專屬屬性 |
setProperty(key, value) | 單一屬性覆蓋 | 動態調整 hibernate.show_sql、hibernate.hbm2ddl.auto |
addClass(Class) | 依類別自動推導映射檔路徑 | 單元測試僅載入特定實體(如 Cat.class → Cat.hbm.xml) |
addFile(File/String) | 直接指定映射檔路徑 | 映射檔不在 Classpath 或需測試不同版本映射 |
addURL(URL) | 指定映射檔 URL | 從 Jar/網路/ClassLoader 非標準位置載入 |
configure(String) | 指定替代 cfg.xml | 多環境配置隔離(test-h2.cfg.xml / test-mysql.cfg.xml) |
場景:驗證 one-to-many 端設定 inverse="true" vs "false" 對 SQL 發送次數、級聯行為的影響。
public class FalseInverseTest extends TestCase {
private Session session;
private Transaction tx;
protected void setUp() throws Exception {
// 僅載入特定映射檔,不依賴 cfg.xml
Configuration cfg = new Configuration()
.setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect")
.setProperty("hibernate.connection.driver_class", "org.h2.Driver")
.setProperty("hibernate.connection.url", "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1")
.setProperty("hibernate.connection.username", "sa")
.setProperty("hibernate.connection.password", "")
.setProperty("hibernate.hbm2ddl.auto", "create-drop")
.setProperty("hibernate.show_sql", "true")
.addFile("bidirect.inverse.false.hbm.xml"); // 關鍵:指定映射檔
session = cfg.buildSessionFactory().openSession();
tx = session.beginTransaction();
}
protected void tearDown() throws Exception {
tx.commit();
session.close();
}
public void testInverseFalseBehavior() {
// 驗證 inverse=false 時,雙邊維護關聯會發送額外 UPDATE
Parent p = new Parent();
Child c = new Child();
p.getChildren().add(c);
c.setParent(p);
session.save(p); // 分析 SQL 輸出
}
}
public class TrueInverseTest extends TestCase {
protected void setUp() throws Exception {
Configuration cfg = new Configuration()
// ... 相同資料庫屬性 ...
.addFile("bidirect.inverse.true.hbm.xml"); // 載入 inverse=true 映射
session = cfg.buildSessionFactory().openSession();
tx = session.beginTransaction();
}
// 相同測試邏輯,對比 SQL 差異
}
create-drop 自動建表清表。public abstract class HibernateTestCase extends TestCase {
protected SessionFactory sf;
protected Session session;
protected Transaction tx;
// 子類別覆寫提供映射檔清單
protected abstract List getMappingFiles();
// 子類別可覆寫資料庫屬性
protected Properties getDatabaseProperties() {
Properties p = new Properties();
p.setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
p.setProperty("hibernate.connection.driver_class", "org.h2.Driver");
p.setProperty("hibernate.connection.url", "jdbc:h2:mem:" + getClass().getSimpleName());
p.setProperty("hibernate.hbm2ddl.auto", "create-drop");
return p;
}
@Override
protected void setUp() throws Exception {
Configuration cfg = new Configuration();
cfg.setProperties(getDatabaseProperties());
for (String mapping : getMappingFiles()) {
cfg.addFile(mapping); // 或 addClass / addURL
}
sf = cfg.buildSessionFactory();
session = sf.openSession();
tx = session.beginTransaction();
}
@Override
protected void tearDown() throws Exception {
try { tx.commit(); } finally { session.close(); sf.close(); }
}
}
// 具體測試類別極度精簡
public class CatTest extends HibernateTestCase {
@Override protected List getMappingFiles() {
return Arrays.asList("Cat.hbm.xml");
}
public void testCreate() { /* ... */ }
}
src/test/resources/hibernate 放置測試專用映射檔與 *-test.cfg.xml。mvn test -Ph2 / -Pmysql 切換資料庫屬性檔。<parallel>classes</parallel>,每類別獨立 SessionFactory 無鎖競爭。hibernate.show_sql=true + log4jdbc 捕獲 SQL,斷言預期 SQL 序列(如 assertSqlSequence())。Hibernate Configuration 提供豐富的程式化 API,關鍵在於:
hibernate.properties < hibernate.cfg.xml < 程式化 setProperty/addFile。create-drop 實現自給自足。掌握這些技巧,即可在 Hibernate 專案中建立快速、隔離、可維護、全自動的單元測試體系,為持續整合奠定堅實基礎。