iT邦幫忙

2026 iThome 鐵人賽

DAY 12
0
Software Development

30天打造一套企業PLM系列 第 12

Day 12:Trigger 規則引擎(下)——Spring 交易傳播實戰

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20260829/20161290aCC3gLipDN.jpg

系列:30 天打造企業級 PLM|面向:後端

問題場景

log 印出執行成功,簽核名單卻沒多出半個人。本系列最經典的 bug,凶手不在演算法、不在資料,在一個註解參數:@Transactional(propagation = ...)。今天用同一顆引擎裡三個不同的交易傳播決策,講清楚為什麼 SET_FIELD 用 REQUIRES_NEW、IMPORT_APPROVER 用 REQUIRED、AFTER_COMMIT 路徑又非 REQUIRES_NEW 不可。每一段都有對應的血淚。

商業邏輯設計

  • 自動化失敗時,單子該停還是該過?Day 11 的三種失敗策略(BLOCK / WARN_CONTINUE / DISABLE_ON_ERROR)在交易層各有對應語意:BLOCK 要能回滾主流程,WARN_CONTINUE 要保證不回滾主流程。交易設計是失敗策略的實作基礎,不是獨立議題
  • 「先 trigger 後數人」的順序(自動加簽先跑、會簽人數判定後算)由流程負責單位拍板。技術上這就是 IN_TRANSACTION 時機的存在理由

核心內容:三個傳播決策,三段血淚

架構演進:從 WebLogic JTA 交易黑箱到 Spring 宣告式傳播控制

在以前 Oracle Agile PLM 中,自動化擴充(Java PX / Event Action)是直接跑在 WebLogic JTA 分散式交易環境中的。

在舊 J2EE / EJB 架構下,交易邊界常演變成災難:

  1. 無差別 RollbackOnly:只要某個非核心的 Java PX 拋出未處理的例外,WebLogic 容器就會自動把整個 JTA 交易標記為 RollbackOnly。結果外層剛簽核完的表單、剛放行的資料在 Commit 時全部被迫回滾,整張單據卡死。
  2. 缺乏細緻的傳播控制:在 EJB 時代要精準區分「哪些要跟隨主交易、哪些要開獨立子交易(REQUIRES_NEW)、哪些要在 Commit 後非同步觸發」極度繁瑣,一不小心就引爆分散式交易逾時或資料庫死鎖(Deadlock)。

Mini-PLM 依託 Spring 的宣告式交易機制,將交易控制拆解為三組精準的傳播決策:

https://ithelp.ithome.com.tw/upload/images/20260829/20161290PEmluOgA1N.png

決策一:SET_FIELD 用 REQUIRES_NEW,隔離 sibling

/**
 * 每個 trigger 各自一個獨立 transaction:避免一個 trigger 失敗讓 sibling 全部 rollback。
 */
@Transactional(propagation = Propagation.REQUIRES_NEW)
public TriggerResult execute(ConfigTrigger trigger, TriggerContext context) {

同一個觸發點可能掛五條 SET_FIELD 規則,第三條炸了,前兩條已改的欄位值該不該留?該留,這是 WARN_CONTINUE 的語意。所以每條規則自己一個交易,成敗獨立。

決策二:IMPORT_APPROVER 用 REQUIRED,因為它要讀「還沒 commit 的現在」

這就是「自動加簽永遠找不到人」的案發現場。實際程式碼的 javadoc 把事故報告寫得清清楚楚:

/**
 * 必須加入呼叫端(signOff / autoChangeStep)的同一個 transaction。
 *
 * IMPORT_APPROVER 在 BEFORE_ADD_APPROVERS 觸發,需讀取「當前關卡剛完成、
 * 尚未 commit」的簽核 Action(例如第一關 signoff 後匯入到第二關)。
 * 若改用 REQUIRES_NEW,新交易在 READ COMMITTED 隔離下看不到外層未 commit
 * 的資料,會誤判為「resolved no candidate」。改用 REQUIRED 後,共用同一
 * persistence context,Hibernate 查詢前 auto-flush,即可讀到剛完成的簽核記錄。
 */
@Transactional(propagation = Propagation.REQUIRED, noRollbackFor = Exception.class)

翻成白話:「前一關簽核者的主管」這種規則,要讀的是這一秒剛寫入、還在交易裡的簽核紀錄。REQUIRES_NEW 開新交易,在 READ COMMITTED 隔離級別下看不到外層未 commit 的資料,於是規則永遠解析出零個候選人。log 寫 SUCCESS(規則正常跑完,只是找不到人),名單沒人(真的沒解析到)。每一行程式都對,組合起來全錯。

更陰的是它測不出來。測試環境單步操作,上一關早已 commit,怎麼測都正常;只有生產環境「簽核瞬間連動加簽」的連續路徑會踩中。交易時序 bug 的共同特徵:重現條件藏在交易邊界裡,不在操作步驟裡。

再看第二個參數 noRollbackFor = Exception.class,這是 REQUIRED 的連帶代價。共用交易後,executor 拋例外會把外層交易標記 rollback-only,就算引擎 catch 住不 rethrow(WARN_CONTINUE),外層 signOff 在 commit 時仍會炸 UnexpectedRollbackException。關掉自動 rollback,例外照樣往上傳給失敗策略裁決,但不污染外層交易;BLOCK 策略則由 handleFailure 重新拋出,正確阻擋。傳播模式與 rollback 規則是一組決策,拆開想必出事。

決策三:AFTER_COMMIT 路徑必須 REQUIRES_NEW,「已完成的交易」是個陷阱

AFTER_COMMIT 觸發點透過 @TransactionalEventListener(AFTER_COMMIT) 回呼執行,這裡藏著全系列最詭異的一個坑(TriggerEngine.executeAfterCommit 實碼 javadoc):

/**
 * 必須 REQUIRES_NEW:本方法由 @TransactionalEventListener(AFTER_COMMIT) 的
 * afterCommit 回呼呼叫,此時外層交易已 commit 但交易資源仍綁在執行緒上;
 * 若以預設 REQUIRED 加入該「已完成」交易,executor 內的 repository.save()
 * 永遠不會再被 flush/commit,造成 IMPORT_APPROVER 等寫入被靜默丟棄
 * (log 因 REQUIRES_NEW 獨立交易照常寫入,形成「有觸發紀錄但沒加人」的假象)。
 */
@Transactional(propagation = Propagation.REQUIRES_NEW)
public TriggerResult executeAfterCommit(TriggerContext context) {

afterCommit 回呼的執行緒上還綁著那個已經 commit 完的交易。此時用 REQUIRED 加入它,所有 save 都寫進一個永遠不會再 flush 的 persistence context:靜默丟棄,不報錯。而執行 log 是獨立交易寫的,照常落地。結果就是最折磨人的偵錯現場,log 說做了,資料說沒做,兩邊都是真的。

同一個 catch 區塊還留了一條教訓:早期版本 silent catch 吃掉了 LazyInitialization 等執行期例外,變成「沒 action 也沒 log」。現在一律 log.error 帶完整 context。AFTER_COMMIT 路徑保證不拋出,但保證不拋出不等於保證不出聲。

重複觸發的交易 guard

回頭路(Day 9)讓同一關可能被重複進入,trigger 也就可能重複加人。防線兩道:交易內的 dedupe guard,key 含 stepId,同交易同關只跑一次;跨週期的殘留排除用 workflowRunNo 過濾而非 finishFlag。用 finishFlag 的話,上一輪已完成的 Action 會被當成「這輪已經加過人」,新一輪反而漏加。又一個欄位語意選錯、行為整個歪掉的例子。

小結

同一顆引擎、三種傳播策略,各自對應一個交易事實:REQUIRES_NEW 隔離 sibling 失敗,REQUIRED 看得見未 commit 的現在(配 noRollbackFor 保外層),afterCommit 回呼裡必須開新交易否則寫入蒸發。交易傳播沒有預設安全值,每個 @Transactional 都是一個要被證明的決策,而證明只能靠真實資料庫的整合測試。H2 與單步操作都會騙你。

明日 Day 13:回到資料本身——版本生命週期與改版機制,immutable revision 的實作。


上一篇
Day 11:Trigger 規則引擎(上)——可設定式自動化
系列文
30天打造一套企業PLM12
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言