數據庫測試需保證每個測試方法獨立、數據庫狀態穩定。DbUnit 雖能通過固定數據集實現,但無法處理非空外鍵約束下的刪除順序問題,尤其自引用表(如組織表的上級組織外鍵指向自身)會導致刪除失敗或級聯刪除誤傷數據。
摒棄 Hibernate,直接用 JDBC SQL,保持測試基礎設施獨立穩定。
public class SqlRunner {
static { Class.forName(\"org.hsqldb.jdbcDriver\"); }
public static void executeUpdate(String sql) { /* JDBC 執行 update */ }
/** 判斷表是否存在被引用記錄(自引用場景) */
public static boolean isUndeletableForSelfReference(String table, String fkColumn) {
// 查詢 count(*) WHERE fkColumn IS NOT NULL
// 有記錄引用其他記錄 → true(不能直接刪表)
}
}
@Before
public void setUp() {
SqlRunner.executeUpdate(\"INSERT INTO 組織分類 VALUES(1, '教育系統')\");
SqlRunner.executeUpdate(\"INSERT INTO 組織分類 VALUES(2, '商貿系統')\");
SqlRunner.executeUpdate(\"INSERT INTO 組織 VALUES(1, '中國貿易部', '北京', 2, NULL)\");
SqlRunner.executeUpdate(\"INSERT INTO 組織 VALUES(2, '北京貿易廳', '北京', 2, 1)\");
SqlRunner.executeUpdate(\"INSERT INTO 組織 VALUES(5, '北京昌平貿易局', '北京', 2, 2)\");
// 5→2→1 鏈式自引用
SqlRunner.executeUpdate(\"INSERT INTO 部門 VALUES(1, '財務科', 2)\");
}
@After
public void tearDown() {
SqlRunner.executeUpdate(\"DELETE FROM 部門\"); // 先刪被引用表
// 遞歸刪除自引用表:先刪葉子節點(無人引用的記錄)
while (SqlRunner.isUndeletableForSelfReference(\"組織\", \"上級組織編號\")) {
SqlRunner.executeUpdate(
\"DELETE FROM 組織 WHERE 上級組織編號 IS NOT NULL \" +
\"AND 編號 NOT IN (SELECT 上級組織編號 FROM 組織 WHERE 上級組織編號 IS NOT NULL)\"
);
}
SqlRunner.executeUpdate(\"DELETE FROM 組織\");
SqlRunner.executeUpdate(\"DELETE FROM 組織分類\");
}
while 循環語義:只要存在「被引用的記錄」(即有其他記錄的外鍵指向它),就先刪除「引用了其他記錄但自己不被引用的葉子節點」。循環結束後,表中無任何被引用記錄,安全執行全表刪除。
jdbc:hsqldb:mem:testingdb,毫秒級啟動,測試隔離天然滿足,無需清理磁盤。TestDataManager:XML/JSON 定義數據集、拓撲排序自動生成刪除順序。自研 DbUnit 不足百行代碼,卻精準解決了傳統工具在複雜約束下的無力感,是測試基礎設施「小而美」的典範。