系列第二部分深入剖析 Bean 管理事務 (BMT):JTA 與 JDBC 兩種實作模式、回滾規則、禁止方法、Geronimo (OpenEJB) 部署描述符與 XDoclet 設定完整示範。
| 方法 | 用途 |
begin() | 啟動新事務,關聯當前執行緒 |
commit() | 提交當前執行緒關聯事務 |
rollback() | 強制回滾當前事務 |
setRollbackOnly() | 標記僅能回滾 |
setTransactionTimeout(seconds) | 設定超時秒數 |
getStatus() | 取得事務狀態 |
取得 UserTransaction 參考
// 方式一:從 EJBContext (Session Bean)
UserTransaction utx = ejbContext.getUserTransaction();
// 方式二:從 MessageDrivenContext (MDB)
UserTransaction utx = mdc.getUserTransaction();
// 方式三:JNDI 查找 (通用)
Context ctx = new InitialContext();
UserTransaction utx = (UserTransaction) ctx.lookup("java:comp/UserTransaction");
完整範例:跨兩資料庫更新
public class MySessionEJB implements SessionBean {
private EJBContext ejbContext;
public void updateTwoDatabases(...) {
DataSource ds1 = null, ds2 = null;
Connection conn1 = null, conn2 = null;
PreparedStatement stmt1 = null, stmt2 = null;
UserTransaction utx = null;
try {
InitialContext ic = new InitialContext();
// 取得資料源與連線
ds1 = (DataSource) ic.lookup("java:comp/env/jdbc/dbOneDS");
conn1 = ds1.getConnection();
stmt1 = conn1.prepareStatement(sql1);
ds2 = (DataSource) ic.lookup("java:comp/env/jdbc/dbTwoDS");
conn2 = ds2.getConnection();
stmt2 = conn2.prepareStatement(sql2);
// 事務控制
utx = ejbContext.getUserTransaction();
utx.begin(); // 開始事務
stmt1.executeUpdate();
stmt2.executeUpdate();
utx.commit(); // 提交
} catch (Exception e) {
try { if (utx != null) utx.rollback(); }
catch (SystemException se) {
throw new EJBException("Rollback failed: " + se.getMessage());
}
throw new EJBException("Transaction failed: " + e.getMessage());
} finally {
// 關閉資源 (略)
}
}
public void setSessionContext(SessionContext ctx) { this.ejbContext = ctx; }
// 其他 EJB 生命週期方法...
}
三、JDBC 事務實作 (舊代碼遷移專用)
直接操作 java.sql.Connection,由資料庫事務管理器控制,非 EJB 容器。
public void updateFirstName(int customerId, String firstName) {
Connection con = null;
try {
con = getConnection(); // 取得 JDBC 連線
con.setAutoCommit(false); // 關閉自動提交
String sql = "UPDATE customer SET firstName = ? WHERE customerId = ?";
PreparedStatement ps = con.prepareStatement(sql);
ps.setString(1, firstName);
ps.setInt(2, customerId);
ps.executeUpdate();
con.commit(); // 提交
} catch (Exception ex) {
try {
if (con != null) con.rollback();
} catch (SQLException sqx) {
throw new EJBException("Rollback failed: " + sqx.getMessage());
}
throw new EJBException("Update failed: " + ex.getMessage());
} finally {
// 關閉連線...
}
}
適用場景: 必須重用現有 JDBC 代碼。新開發優先用 JTA。
四、事務提交/回滾規則 (依 Bean 類型)
| Bean 類型 | 規則 | 違規後果 |
| Stateless Session Bean | 業務方法返回前必須 commit/rollback | 容器回滾並拋出異常 |
| Stateful Session Bean | 可跨多個客戶端調用,不必單一方法結束 | 容器等待 Bean 結束事務 |
| Message-Driven Bean | onMessage() 或 ejbTimeout() 返回前必須 commit | 容器回滾並拋出異常 |
關鍵差異: JTA 事務跨連線開關保持事務上下文;JDBC 事務隨連線關閉而結束。
五、BMT 中禁止呼叫的方法
java.sql.Connection.commit()、rollback()、setAutoCommit() (JTA 模式下)
javax.jms.Session.commit()、rollback()
EJBContext.getRollbackOnly()、setRollbackOnly() (CMT 專用)
UserTransaction 以外的任何事務管理 API
替代方案:
- 事務狀態:
utx.getStatus()
- 標記回滾:
utx.setRollbackOnly()
- 回滾:
utx.rollback()
六、Geronimo (OpenEJB) 部署設定
ejb-jar.xml 關鍵元素:
SampleSession
...
Bean
XDoclet 設定 (建議方式)
/**
* @ejb.bean
* name="SampleSession"
* type="Stateless"
* transaction-type="Bean"
* ...
*/
public abstract class SampleSessionBean implements SessionBean { ... }
生成的 ejb-jar.xml 將包含 <transaction-type>Bean</transaction-type>。
注意區別:
- CMT 方法屬性:
@ejb.transaction type="Required"
- BMT Bean 屬性:
@ejb.bean transaction-type="Bean"
七、結語
BMT 賦予開發者事務邊界完全控制權,JTA 模式支援跨資源分布式事務,JDBC 模式服務遷移舊代碼。Geronimo/OpenEJB 透過 transaction-type="Bean" 宣告啟用 BMT。關鍵在於:遵守 Bean 類型的提交/回滾規則、避免呼叫禁止 API、正確處理異常回滾路徑。下一部分將綜合 CMT 與 BMT 進階選項。
- 上一篇文章: Apache Geronimo EJB 事務選項權衡 (三):綜合實戰 — 並發控制、隔離級別、超時與分布式事務
- 下一篇文章: Apache Geronimo EJB 事務選項權衡 (一):容器管理事務 (CMT) 核心機制與設定實戰
Copyright ©
程式碼之家 All Rights Reserved