JBoss EAPSpring Framework交易企業系統升級

JBoss EAP 10:MDB 到資料庫,讀懂交易失敗、重送與重複處理

··閱讀約 6 分鐘
回到 JBoss EAP 系列導讀

訊息重送兩次,資料庫就一定新增兩筆嗎?不一定。但如果只看 onMessage 沒有拋錯,也無法知道資料是否提交、MQ 是否完成確認。這一課沿 MDB → Service → DB 拆開責任,再用本機模型觀察兩個失敗位置:DB 提交前,以及 DB 已提交但確認紀錄尚未完成。

下載第 10 課:H2 交易與合成重送模型

下載包會真的執行 Spring JDBC/H2 交易;傳遞次數、ack 與隔離失敗訊息則是測試裡的合成模型,沒有 MDB、RAR、JMS client 或 MQ broker。它能幫你重現資料庫側的結果,不能拿來證明 EAP 的 CMT/XA 或 IBM MQ 重送行為。

先找到每一段由誰接手

text
IBM MQ → Jakarta resource adapter → EAP EJB container
       → MDB.onMessage → managed service → enlisted DB resource

Spring bean proxy and EJB container interception are different mechanisms.

MDB 的生命週期由 EJB 容器管理。它呼叫的 Service 若是 EJB,要查 EJB 的交易屬性;若是 Spring Bean,要查怎麼取得 Bean、選中哪個管理器,以及資源有沒有加入預期交易。直接 new 一個帶 @Transactional 的 Spring 物件,不能讓 EAP 的交易自動接上它;第九課的手動建立反例已示範 annotation 本身不夠。

Jakarta Enterprise Beans 對 container-managed MDB message listener 的 transaction attribute 限定為 REQUIRED 或 NOT_SUPPORTED。這是在說規範允許的選項,實際設定、descriptor 覆寫、adapter 能力與資源參與仍要核對。Spring 的 annotation 與 EJB 的交易屬性不能只因名字相似就互換。

兩段提交和同一筆 XA,不能混為一談

text
Local model in this kit:
  DB transaction commits → model records acknowledgment
                         ↑ failure window

Target CMT/XA investigation:
  transaction coordinator → enlisted MQ + DB resources
  verify actual outcome and recovery, not this model alone

教材刻意選兩段式的本機模型,讓「DB 已提交、確認遺失」的窗口看得見。若你的 MDB、MQ 與 DB 確實參與同一筆容器協調交易,應依那個模型驗證提交與復原,不能直接套用這裡的預期。也不要為了做練習,把原本企業系統的交易架構改成這一套。

同一筆本機交易寫兩張表

java
return transaction.execute(status -> {
    // First detect an existing business key and compare its payload.
    jdbc.update("INSERT INTO processed_events(business_key,title) VALUES (?,?)", key, title);
    jdbc.update("INSERT INTO orders(business_key,title) VALUES (?,?)", key, title);
    if (failBeforeCommit) throw new IllegalStateException("synthetic failure");
    return Result.APPLIED;
});

這是 OrderProcessor 的核心節錄,完整程式還包含輸入檢查與重複判斷。processed_events 記錄已處理的業務鍵,orders 是業務資料,兩者在同一筆 H2 交易內寫入。若中途失敗,兩張表都回到零筆;不然只留下已處理標記,下一次可能誤以為工作早就完成。

再次收到相同業務鍵時,範例先比較 title;相同才回 DUPLICATE,內容不同就報衝突。這是順序執行的教學實作,SELECT 後 INSERT 並不是完整的併發去重演算法。雖然 primary key 防止重複列,競爭失敗的處理、重試、隔離等級與資料保留期限都還沒驗證。

跑六個案例,觀察失敗位置

bash
mvn test
mvn package
情境資料庫模型結果
正常處理訂單與標記各一筆已確認
提交前拋錯,再重送先各零筆,再各一筆第二次成功
已提交,但未記錄確認維持各一筆重送判為 DUPLICATE
同業務鍵、內容不同原資料保留報衝突
連續三次合成失敗各零筆標為隔離
缺業務鍵各零筆拒絕輸入
六個 JUnit 測試;DB 是 H2,確認與重送是模型。

第三個案例會建立新的 OrderProcessor,再對同一份 H2 處理一次,確認判斷依據來自資料庫,不是 Service 物件的記憶。但兩次仍在同一個 JVM、同一個記憶體資料庫;沒有真的殺程序,也沒有驗證斷電後的耐久性或 XA recovery。

Poison message 不是永遠多試一次

第五個案例的三次重試是刻意固定的教學條件,quarantined 也只是一個 boolean。IBM MQ 文件另外說明 backout count、BOTHRESH 與 BOQNAME 等機制;實際 adapter、目的地權限、無法搬移訊息時的行為,都必須回到目標組合查證。不能把模型裡第三次停止,抄成正式站應使用的設定。

如果決定把失敗訊息移到別處,還要保留業務鍵、原始內容、錯誤分類與可追蹤的重送方式。只是清掉 queue 或吞掉例外,可能讓「看不到訊息」冒充「業務已完成」。容器已標記回滾的交易,也不會因為應用 catch 後寫一行 log 就被修復。

把本機結果接回公司的驗收案例

下載包的 TARGET-ENVIRONMENT.md 已列出要填的 adapter、destination、交易管理器、資源參與與八種目標案例。真正執行時保留 DB 前後狀態、MQ 業務鍵與次數、交易和錯誤 log,再交給第七課的比對工具;不要把這裡的合成結果填成公司環境的實測資料。

自我檢查:DB 一筆、queue 沒訊息,能證明剛好處理一次嗎?不能。你還缺投遞、消費、業務副作用與復原的完整關聯。這個範例能確定的是六個本機情境下的 H2 結果;它把下一步需要觀察的位置指出來,並沒有替真實容器回答。

回顧第 09 課:先辨認 Service 的代理與交易第 07 課:用同一案例比對四種證據

參考資料