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

Spring 整合 JOTM 實現跨數據庫 JTA 分布式事務管理實戰

     編輯:Java 相關知識

當業務涉及多個數據庫,且要求跨庫操作具備原子性(要麼全成功,要麼全回滾)時,本地事務無法滿足需求,必須引入 JTA(Java Transaction API)分布式事務。本文詳細記錄 Spring 整合 ObjectWeb JOTM 實現跨數據庫 JTA 事務的完整配置與驗證過程。

核心依賴

將以下 JAR 放入類路徑:

  • jotm.jar - JOTM 核心
  • xapool.jar - XA 連接池
  • jotm_jrmp_stubs.jar - RMI 存根
  • jta-spec1_0_1.jar - JTA 規範 API
  • connector-1_5.jar - JCA 連接器規範
  • MySQL JDBC 驅動(需支援 XA,如 mysql-connector-java 5.1+)

JOTM 配置

創建 carol.properties 置於類路徑根目錄:

# JNDI 協議
carol.protocols=jrmp
# 不使用 CAROL JNDI 包裝器
carol.start.jndi=false
# 不啟動命名服務
carol.start.ns=false

數據庫準備

創建兩個測試庫 jtatesta、jtatestb,各建一表:

-- jtatesta
CREATE TABLE `user` (
  `user_id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `user_name` VARCHAR(45) NOT NULL,
  `user_password` VARCHAR(45) NOT NULL,
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- jtatestb
CREATE TABLE `grade` (
  `grade_id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `user_id` INT(10) UNSIGNED NOT NULL,
  `grade` DOUBLE NOT NULL,
  PRIMARY KEY (`grade_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

關鍵點:表引擎必須為 InnoDB,MyISAM 不支援事務。

領域模型與 DAO

// User.java
public class User implements Serializable {
    private int userId;
    private String userName;
    private String userPassword;
    // getter/setter 省略
}

// Grade.java
public class Grade implements Serializable {
    private int gradeId;
    private User user;
    private double grade;
    // getter/setter 省略
}

// UserJdbcDao.java
@Repository
public class UserJdbcDao extends JdbcDaoSupport implements UserDao {
    public void addUser(User user) {
        String sql = "INSERT INTO user(user_id, user_name, user_password) VALUES(?,?,?)";
        getJdbcTemplate().update(sql, user.getUserId(), user.getUserName(), user.getUserPassword());
    }
}

// GradeJdbcDao.java
@Repository
public class GradeJdbcDao extends JdbcDaoSupport implements GradeDao {
    public void addGrade(Grade grade) {
        String sql = "INSERT INTO grade(user_id, grade) VALUES(?,?)";
        getJdbcTemplate().update(sql, grade.getUser().getUserId(), grade.getGrade());
    }
}

業務服務層 - 宣告式事務

Spring JTA 配置核心

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://www.springframework.org/schema/beans"
    xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="https://www.springframework.org/schema/aop"
    xmlns:tx="https://www.springframework.org/schema/tx"
    xsi:schemaLocation="
        https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
        https://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd
        https://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd">

  <!-- JOTM 本地實例 -->
  <bean id="jotm" class="org.springframework.transaction.jta.JotmFactoryBean"/>

  <!-- JTA 事務管理器 -->
  <bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager">
    <property name="userTransaction" ref="jotm"/>
  </bean>

  <!-- XA 數據源 A:jtatesta -->
  <bean id="jtaTestADS" 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://localhost/jtatesta"/>
      </bean>
    </property>
    <property name="user" value="root"/>
    <property name="password" value="885123"/>
  </bean>

  <!-- XA 數據源 B:jtatestb -->
  <bean id="jtaTestBDS" 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://localhost/jtatestb"/>
      </bean>
    </property>
    <property name="user" value="root"/>
    <property name="password" value="885123"/>
  </bean>

  <!-- JdbcTemplate 綁定對應 XA 數據源 -->
  <bean id="jtaTestATemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="jtaTestADS"/>
  </bean>
  <bean id="jtaTestBTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="jtaTestBDS"/>
  </bean>

  <!-- DAO 注入對應 Template -->
  <bean id="userDao" class="com.dao.jdbc.UserJdbcDao">
    <property name="jdbcTemplate" ref="jtaTestATemplate"/>
  </bean>
  <bean id="gradeDao" class="com.dao.jdbc.GradeJdbcDao">
    <property name="jdbcTemplate" ref="jtaTestBTemplate"/>
  </bean>

  <!-- 業務服務 -->
  <bean id="myService" class="com.service.impl.MyServiceImpl">
    <property name="userDao" ref="userDao"/>
    <property name="gradeDao" ref="gradeDao"/>
  </bean>

  <!-- 註解驅動事務 -->
  <tx:annotation-driven transaction-manager="txManager" proxy-target-class="true"/>
</beans>

驗證測試

public class TestMain {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext ctx = 
            new ClassPathXmlApplicationContext("app_jta.xml");
        MyService ms = ctx.getBean("myService", MyService.class);
        
        User user = new User();
        user.setUserId(1);          // 故意設置重複主鍵
        user.setUserName("tufu");
        user.setUserPassword("tufu");
        
        Grade grade = new Grade();
        grade.setGrade(100);
        grade.setUser(user);
        
        ms.addGrade(user, grade);   // 預期:user 插入失敗,grade 同步回滾
    }
}

關鍵驗證點

  1. 開啟 DEBUG 日誌:log4j.rootLogger=DEBUG, console 觀察 JTA 事務邊界、XAResource 登記、兩階段提交/回滾日誌
  2. 數據庫檢查:執行後查詢 user、grade 表,應均無新增記錄
  3. 異常堆棧:控制台應拋出 DataIntegrityViolationException(主鍵衝突),事務管理器捕獲後觸發全局回滾

常見坑點提示

  • MySQL 驅動版本需支援 XA(5.1.13+),且連接串建議加 ?pinGlobalTxToPhysicalConnection=true
  • JOTM 非生產級事務管理器,高併發場景建議遷移至 Atomikos / Bitronix 或應用服務器內建 JTA
  • StandardXAPoolDataSource 的 destroy-method="shutdown" 必須配置,否則應用關閉時連接池不釋放
  • 兩個數據源的 transactionManager 屬性必須引用同一 jotm 實例,確保同一全局事務上下文

通過 Spring 的 JotmFactoryBean 與 JtaTransactionManager,配合 XAPool 封裝 XADataSource,我們以最小依賴實現了輕量級的跨庫分布式事務。這套方案適合中小型應用、開發測試環境,或作為理解 JTA 原理的入門範例。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved