首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

分布式事務實戰:Spring + JTA + iBatis 多数据源 XA 事務配置指南

     編輯:Java 綜合教學

技術背景與選型

單體應用演進至微服務/分庫分表階段,跨多個數據庫的業務操作(如訂單服務同時寫入「訂單庫」與「庫存庫」)要求原子性:要麼全成功,要麼全回滾。傳統 JDBC 事務綁定單一 Connection,無法跨數據源。JTA(Java Transaction API)定義了事務管理器與資源管理器(XADataSource)的標準協議,支持全局事務協調多個 XA 資源的兩階段提交(2PC)。

本方案採用 Spring 声明式事務 + JOTM 嵌入式事務管理器 + iBatis/MyBatis + XAPool 數據源池,適用於不依賴應用服務器(如 WebLogic、JBoss)的獨立部署場景。

核心組件與依賴

<!-- JOTM: Java Open Transaction Manager -->
<dependency>
    <groupId>org.ow2.jotm</groupId>
    <artifactId>jotm</artifactId>
    <version>2.2.1</version>
</dependency>
<!-- XAPool: XA 連接池 -->
<dependency>
    <groupId>org.enhydra.jdbc</groupId>
    <artifactId>xapool</artifactId>
    <version>1.5.0</version>
</dependency>
<!-- MyBatis-Spring 整合 -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.6</version>
</dependency>

JotmFactoryBean:Spring 3+ 缺失的適配器

Spring 3 移除了 JotmFactoryBean,需自行實現 FactoryBean + DisposableBean 管理 JOTM 生命週期:


public class JotmFactoryBean implements FactoryBean<Current>, DisposableBean {
    private Current jotmCurrent;
    private Jotm jotm;

    public JotmFactoryBean() throws NamingException {
        jotmCurrent = Current.getCurrent();
        if (jotmCurrent == null) {
            jotm = new Jotm(true, false); // 獨立模式,不綁定 JNDI
            jotmCurrent = Current.getCurrent();
        }
    }
    public void setDefaultTimeout(int timeout) { ... }
    public Current getObject() { return jotmCurrent; }
    public Class<?> getObjectType() { return Current.class; }
    public boolean isSingleton() { return true; }
    public void destroy() { if (jotm != null) jotm.stop(); }
}

多数据源 DAO 設計模式

基礎單數據源 DAO (IBaseDao)

封裝 SqlSessionTemplate 通用 CRUD:queryForObject、queryForList、insert/update/delete、getTotalCount。關鍵方法暴露 setSqlSession(Session) 供運行時動態切換數據源。

多数据源聚合 DAO (IBaseMDao / BaseMDaoImpl)

持有多個 SqlSession(dmcWeb、idxWeb、rptWeb)與對應的數據源鍵映射。核心邏輯:

  1. 通過 ResourceBundle 讀取 mybaties.properties 映射 sqlId → dataSourceKey
  2. getSqlSession(sqlId) 根據語句 ID 解析目標數據源,返回對應 Session
  3. 寫操作(insert/update/delete)統一使用 JtaTransactionManager.getUserTransaction() 手動控制全局事務邊界:begin() → 多源操作 → commit() / rollback()

public int[] update(Object object, String... sqlIds) {
    UserTransaction tx = transactionManager.getUserTransaction();
    try {
        tx.begin();
        int[] results = new int[sqlIds.length];
        for (int i = 0; i < sqlIds.length; i++) {
            baseDao.setSqlSession(getSqlSession(sqlIds[i]));
            results[i] = baseDao.update(object, sqlIds[i]);
        }
        tx.commit();
        return results;
    } catch (Exception e) {
        try { tx.rollback(); } catch (Exception ignored) {}
        throw new RuntimeException(e);
    }
}

Spring XML 裝配全景


<!-- 1. JOTM 事務管理器 -->
<bean id="jotm" class="com.ai.dimensyn.dynamicDataSoure.JotmFactoryBean" />
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
    <property name="userTransaction" ref="jotm" />
</bean>

<!-- 2. XA 數據源(每個庫一套) -->
<bean id="dataSourceDmcWeb" class="org.enhydra.jdbc.pool.StandardXAPoolDataSource" destroy-method="shutdown">
    <property name="dataSource">
        <bean class="org.enhydra.jdbc.standard.StandardXADataSource" destroy-method="shutdown">
            <property name="transactionManager" ref="jotm" />
            <property name="driverName" value="com.mysql.jdbc.Driver" />
            <property name="url" value="jdbc:mysql://ip:port/dmc?useUnicode=true&amp;characterEncoding=utf-8" />
            <property name="user" value="xxx" />
            <property name="password" value="xxx" />
        </bean>
    </property>
</bean>
<!-- dataSourceIdxWeb、dataSourceRptWeb 同理 -->

<!-- 3. SqlSessionFactory & SqlSessionTemplate -->
<bean id="dmcWebSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="configLocation" value="classpath:orm/Configuration.xml" />
    <property name="dataSource" ref="dataSourceDmcWeb" />
</bean>
<bean id="dmcWebSession" class="org.mybatis.spring.SqlSessionTemplate">
    <constructor-arg index="0" ref="dmcWebSessionFactory" />
</bean>

<!-- 4. 多数据源 DAO 注入所有 Session -->
<bean id="baseMDao" class="com.ai.dimensyn.dao.common.impl.BaseMDaoImpl">
    <property name="transactionManager" ref="transactionManager" />
    <property name="dmcWebSession" ref="dmcWebSession" />
    <property name="dmcWebSrcKey" value="dmcWeb" />
    <property name="idxWebSession" ref="idxWebSession" />
    <property name="idxWebSrcKey" value="indexWeb" />
    <property name="rptWebSession" ref="rptWebSession" />
    <property name="rptWebSrcKey" value="reportWeb" />
    <property name="defaultSrcKey" value="dmcWeb" />
</bean>

MyBatis Mapper 與數據源路由配置


<!-- mybaties.properties: sqlId 前綴映射數據源 -->
dimensyn.deleteDimenTable-testtable_xuchun=indexWeb
dimensyn.insertDimenTable-testtable_xuchun=dmcWeb

<!-- dimensynMapper.xml -->
<mapper namespace="dimensyn">
    <delete id="deleteDimenTable" parameterType="java.util.Map">
        delete from ${table_tb}
    </delete>
    <insert id="insertDimenTable" parameterType="java.util.Map">
        -- 動態 SQL 由應用層組裝
    </insert>
</mapper>

運行時 BaseMDaoImpl 解析 sqlId(如 deleteDimenTable-testtable_xuchun),取前綴 deleteDimenTable 查找 properties 獲取 indexWeb,進而路由到 idxWebSession。

測試驗證與注意事項


// 業務服務注入 baseMDao
public void testDistributedTx() {
    Map<String, String> params = new HashMap<>();
    params.put("table_tb", "test_table");
    params.put(Constants.SQL, "insert into ...; insert into ...;"); // 多條 SQL 以分號分隔
    baseMDao.executeDeleteInsert(params,
        "dimensyn.deleteDimenTable-testtable_xuchun",
        "dimensyn.insertDimenTable-testtable_xuchun");
}

關鍵避坑指南:

  • MySQL 需使用 InnoDB 引擎支持 XA;mysql-connector-java 版本 ≥ 5.1.38(修復 XA 相關 Bug)
  • JOTM 默認超時 60s,長事務需調大 jotm.setDefaultTimeout()
  • XA 事務性能開銷大(兩階段提交、強制刷盤),高併發核心鏈路優先考慮 本地事務+補償/最終一致性(TCC、Saga、Seata AT 模式)
  • Spring 管理 UserTransaction 時,確保同一線程內多次操作共享同一全局事務上下文
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved