🥪 iDempiere 開發者的「三明治」防禦術:如何阻止欄位被核心代碼偷偷抹除?
發佈日期: 2026-04-19
標籤: #iDempiere #OSGi #插件開發 #Java #開發技巧
😱 遇見了「消失的數據」
在開發 iDempiere 的 Kanban 插件時,我們遇到了一個超級詭異的 Bug:我們明明在程式碼裡設定了 `R_Request` 的 `EndTime`(結束時間),也確實呼叫了 `save()`,但每次存檔後,那個欄位就像被施了「消失術」一樣,自動變回了 `NULL`。
原本以為是自己寫錯了,結果檢查了半天,發現罪魁敗鞍竟然不是我們。
🕵️♂️ 真凶竟然是… `RequestEventHandler`
我們檢查了 `MRequest.beforeSave()`,一切正常。但我們忽略了 iDempiere 核心裡的一個 OSGi 事件處理器:`RequestEventHandler`。
這個傢伙非常「熱心」,它會監聽 `R_Request` 的所有變動事件。最致命的是,它在執行 `beforeSaveRequest()` 時,為了清理暫存欄位,會無條件地執行這行代碼:
r.setEndTime(null); // ← 砰!你的 EndTime 在這裡被抹除了而且它執行時機非常巧妙,是在 `beforeSave()` 之後、SQL 寫入資料庫之前。這意味著,即便你在 `beforeSave()` 裡寫得再好,只要它一出手,你的數據就沒了。
🥪 絕招:Model Sandwich Pattern(模型三明治模式)
既然我們不能去修改 iDempiere 的核心代碼(那是大忌!),那我們只能用「三明治」的方式,把數據夾在中間保護起來。
這個模式的核心思想很簡單:在「麵包」的一層把值藏起來,等風暴過去後,再從另一層把值拿出來還原。
🥪 三明治的結構如下:
1. 第一層麵包 (`beforeSave`):在 `RequestEventHandler` 搗亂之前,先把 `EndTime` 偷偷存進 PO 的屬性(Attribute)裡。
2. 中間的內餡 (核心事件):`RequestEventHandler` 照常執行,把資料庫裡的 `EndTime` 設為 `NULL`。
3. 第二層麵包 (`afterSave`):趁著 SQL 已經寫入完成,我們立刻使用 `DB.executeUpdateEx()` 直接對資料庫進行一次補救性的 `UPDATE`,把藏好的值還原回去!
🛠️ 實作邏輯
我們需要自定義一個 `MRequestKanban` 類別,並透過 `IModelFactory` 讓系統使用它:
@Override
protected boolean beforeSave(boolean newRecord) {
// 1. 趁它還沒抹除,先把 EndTime 藏到 Attribute 裡
Object endTime = get_Value("EndTime");
if (endTime != null) {
set_Attribute("Ninniku_OriginalEndTime", endTime);
}
return super.beforeSave(newRecord);
}
@Override
protected boolean afterSave(boolean newRecord, boolean success) {
boolean ok = super.afterSave(newRecord, success);
if (success) {
Object stashedEndTime = get_Attribute("Ninniku_OriginalEndTime");
if (stashedEndTime != null) {
// 2. 趁 SQL 剛跑完,直接用 SQL 補救,繞過 PO 生命週期,避免無限迴圈
DB.executeUpdateEx(
"UPDATE R_Request SET EndTime=? WHERE R_依據_ID=?",
new Object[]{stashedEndTime, getR_Request_ID()},
get_TrxName()
);
set_ValueNoCheck("EndTime", stashedEndTime);
}
}
return ok;
}
> ⚠️ 為什麼不用 `saveEx()`?
> 如果你在 `afterSave` 裡面又呼叫 `saveEx()`,會導致 `beforeSave` -> `afterSave` -> `beforeSave` 的無窮迴圈,直接讓你的伺服器當機。所以,直接用 SQL 補救才是最乾脆、最安全的做法!
💡 總結
這個「三明治模式」不只可以用在 `EndTime`,只要遇到任何「核心代碼會隨意重設欄位」的情況,你都可以用這個方法來保護你的數據。
重點筆記:
開發 iDempiere 插件時,遇到這種「看不見的敵人」不要慌,用三明治夾住它就對了!🥪💪
本文首發於 [ninniku.tw](https://www.ninniku.tw)