示例 代码 Java JTA XA transaction 分布式事务

关于XID的实现有个需要注意的细节,如果要保证数据的事务一致性,除了全局事务ID一致外,还必需保证formatid一致,这个formatid一般是不会变的,但不同的厂商一般是不一样的,在xid传播过程中,应该保证使用一样的formatid才可以。

下面的代码,包含了 JTA XA的几乎全部的 API和注意事项,帮助搭建分布式事务平台,以及解惑多进程间事务协调问题:

 

package xa;

import oracle.jdbc.xa.client.OracleXADataSource;

import javax.sql.XAConnection;
import javax.sql.XADataSource;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class XATest {
    public static XADataSource createXADataSource() throws Exception {
        OracleXADataSource dataSource = new OracleXADataSource();
        dataSource.setURL("jdbc:oracle:thin:@168.1.55.111:1521:orcl");
        dataSource.setUser("user");
        dataSource.setPassword("pass");
        return dataSource;
    }

    public static void insertWithConn(Connection conn, String name) throws Exception {
        String sql = "INSERT INTO TEST_EJB VALUES('" + name + "')";
        Connection connection = null;
        Statement statement = null;
        try {
            connection = conn;
            statement = connection.createStatement();
            statement.executeUpdate(sql);
        } finally {
            if (statement != null) statement.close();
            if (connection != null) connection.close();// should be returned to the xa ds pool
        }
    }

    public static void queryWithConn(Connection conn) throws SQLException {
        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;
        try {
            connection = conn;
            statement = connection.createStatement();
            resultSet = statement.executeQuery("SELECT count(1) FROM TEST_EJB");
            while (resultSet.next()) {
                System.out.println("query: " + resultSet.getString(1));
            }
        } finally {
            if (resultSet != null) resultSet.close();
            if (statement != null) statement.close();
            if (connection != null) connection.close();// should be returned to the xa ds pool
        }
    }

    static class MyXid implements Xid {
        int formatId;
        byte[] globalTransactionId;
        byte[] branchQualifier;

        public MyXid(int formatId, byte[] globalTransactionId, byte[] branchQualifier) {
            this.formatId = formatId;
            this.globalTransactionId = globalTransactionId;
            this.branchQualifier = branchQualifier;
        }

        public int getFormatId() {
            return formatId;
        }

        public byte[] getGlobalTransactionId() {
            return globalTransactionId;
        }

        public byte[] getBranchQualifier() {
            return branchQualifier;
        }
    }

    public static void main(String[] args) throws Exception {
//        1.
//        suspend();
//        resume();
//        2.
//        reUseXaRes();
//        3.
//        reUseXaRes2();
//        4.
//        join();
//        5.
//        recover();
//        6.
//        mutiProcess(1);
        mutiProcess(2);
    }

    public static void mutiProcess(int flag) throws Exception {
        int len = 4;
        XAConnection[] xaCons = new XAConnection[len];
        for (int i = 0; i < len; i++) {
            xaCons[i] = createXADataSource().getXAConnection();
        }
        MyXid xid1 = new MyXid(100, new byte[]{0x01}, new byte[]{0x04});

        if (flag == 1) {
            XAResource xaResource = xaCons[0].getXAResource();
            xaResource.start(xid1, XAResource.TMNOFLAGS);// 全局id一样,新的分支id的,则使用 TMNOFLAGS
//            xaResource.start(xid1, XAResource.TMJOIN);// 全局id和分支id都一样,则使用 TMJOIN
            xaCons[0].getConnection().createStatement().executeUpdate("INSERT INTO TEST_EJB VALUES('11')");
            xaResource.end(xid1, XAResource.TMSUCCESS);
        } else {
            XAResource xaResource = xaCons[1].getXAResource();
//            xaResource.start(xid1, XAResource.TMNOFLAGS);// 全局id一样,新的分支id的,则使用 TMNOFLAGS
            xaResource.start(xid1, XAResource.TMJOIN);// 全局id和分支id都一样,则使用 TMJOIN
            ResultSet resultSet = xaCons[1].getConnection().createStatement().executeQuery("select * from TEST_EJB");
            while (resultSet.next()) {
                String string = resultSet.getString(1);
                System.out.println(string);
            }
            xaResource.end(xid1, XAResource.TMSUCCESS);
        }

//        int ret = xaCons[2].getXAResource().prepare(xid1);
//        if (ret == XAResource.XA_OK) {
//            xaCons[3].getXAResource().commit(xid1, false);
//        }

    }

    public static void recover() throws Exception {
        XADataSource xaDS;
        XAConnection xaCon;
        XAResource xaRes;
        xaDS = createXADataSource();
        xaCon = xaDS.getXAConnection();
        xaRes = xaCon.getXAResource();

        Xid[] xids = xaRes.recover(XAResource.TMSTARTRSCAN | XAResource.TMENDRSCAN);
        System.out.println("共扫描到可恢复个数:" + xids.length);
        for (Xid id : xids) {
            if (id.getFormatId() == 100) {
                System.out.println("待恢复事务格式:" + id.getFormatId());
                try {
                    // 可通过注释掉 前面业务的 commit/rollback方法,然后通过恢复的方式进行 commit
                    xaRes.rollback(id);//对于其他没有调用 commit/rollback的可以在这里调用
//                    xaRes.commit(id, false);//对于其他没有调用 commit/rollback的可以在这里调用
                } catch (Exception e) {
                    xaRes.forget(id);
                }
            } else {
//                System.out.println("无需恢复格式:" + id.getFormatId());
            }
        }
    }

    /**
     * 本测试的结论是 start-业务sql-end 必需使用相同的连接, prepa,commit,rollback可以使用不同的连接
     * 连接 start之后必需自己进行end,其它进程不可,如果本连接直接调用close会丢失事务,而end则会保留事务
     */
    public static void join() throws Exception {
        int len = 2;
        XAConnection[] xaCons = new XAConnection[len];
        for (int i = 0; i < len; i++) {
            xaCons[i] = createXADataSource().getXAConnection();
        }
        MyXid xid1 = new MyXid(100, new byte[]{0x01}, new byte[]{0x02});

        // 连接1
        XAResource xaResource0 = xaCons[0].getXAResource();
        xaResource0.start(xid1, XAResource.TMNOFLAGS);
        xaCons[0].getConnection().createStatement().executeUpdate("INSERT INTO TEST_EJB VALUES('00')");
        xaResource0.end(xid1, XAResource.TMSUCCESS);

        // 连接2
        XAResource xaResource1 = xaCons[1].getXAResource();
        if (!xaResource1.isSameRM(xaResource0)) {
            xaResource1.start(xid1, XAResource.TMJOIN);// join
            xaCons[1].getConnection().createStatement().executeUpdate("INSERT INTO TEST_EJB VALUES('10')");
            xaResource1.end(xid1, XAResource.TMSUCCESS);
        } else {
            MyXid xid2 = new MyXid(100, new byte[]{0x01}, new byte[]{0x03});
            xaResource1.start(xid2, XAResource.TMNOFLAGS);
            xaCons[1].getConnection().createStatement().executeUpdate("INSERT INTO TEST_EJB VALUES('20')");
            xaResource1.end(xid2, XAResource.TMSUCCESS);
            int ret = xaResource1.prepare(xid2);
            if (ret == XAResource.XA_OK) {
                xaResource1.commit(xid2, false);
            } else if (ret == XAResource.XA_RDONLY) {
                System.out.println("此分支的全局事务id和上面一样,所以不用单独提交!!!");
            }
        }
        int ret = xaResource0.prepare(xid1);
        if (ret == XAResource.XA_OK) {
            xaResource0.commit(xid1, false);
        }
    }

    /**
     * 本测试的结论是 start-业务sql-end 必需使用相同的连接, prepa,commit,rollback可以使用不同的连接
     * 连接 start之后必需自己进行end,其它进程不可,如果本连接直接调用close会丢失事务,而end则会保留事务
     */
    public static void reUseXaRes2() throws Exception {
        int len = 5;
        XAConnection[] xaCons = new XAConnection[len];
        for (int i = 0; i < len; i++) {
            xaCons[i] = createXADataSource().getXAConnection();
        }
        MyXid xid1 = new MyXid(100, new byte[]{0x01}, new byte[]{0x02});

        // 0 .start
        xaCons[0].getXAResource().start(xid1, XAResource.TMNOFLAGS);
        Statement stmt = xaCons[0].getConnection().createStatement();
        stmt.executeUpdate("INSERT INTO TEST_EJB VALUES('1')");
        xaCons[0].getXAResource().end(xid1, XAResource.TMSUCCESS);

        int ret = xaCons[3].getXAResource().prepare(xid1);
        if (ret == XAResource.XA_OK) {
            xaCons[4].getXAResource().commit(xid1, false);
        }

        for (int i = 0; i < len; i++) {
            xaCons[i].close();
        }
    }

    public static void reUseXaRes() throws Exception {
        XADataSource xaDS;
        XAConnection xaCon;
        XAResource xaRes;
        Xid xid;
        Connection con;
        Statement stmt;
        int ret;
        xaDS = createXADataSource();
        xaCon = xaDS.getXAConnection();
        xaRes = xaCon.getXAResource();
        con = xaCon.getConnection();
        stmt = con.createStatement();

        MyXid xid1 = new MyXid(100, new byte[]{0x01}, new byte[]{0x02});
        MyXid xid2 = new MyXid(100, new byte[]{0x11}, new byte[]{0x22});
        xaRes.start(xid1, XAResource.TMNOFLAGS);
        stmt.executeUpdate("INSERT INTO TEST_EJB VALUES('1')");
        xaRes.end(xid1, XAResource.TMSUCCESS);

        xaRes.start(xid2, XAResource.TMNOFLAGS);
        ret = xaRes.prepare(xid1);
        if (ret == XAResource.XA_OK) {
            xaRes.commit(xid1, false);
        }

        stmt.executeUpdate("INSERT INTO TEST_EJB VALUES('2')");
        xaRes.end(xid2, XAResource.TMSUCCESS);
        ret = xaRes.prepare(xid2);
        if (ret == XAResource.XA_OK) {
            xaRes.rollback(xid2);
        }
    }

    public static void suspend() throws Exception {
        XADataSource xaDS;
        XAConnection xaCon;
        XAResource xaRes;
        Xid xid;
        Connection con;
        Statement stmt;
        int ret;
        xaDS = createXADataSource();
        xaCon = xaDS.getXAConnection();
        xaRes = xaCon.getXAResource();

        // oracle 查询是否有锁语句:SELECT * FROM v$locked_object;
        System.out.println("默认超时时间:" + xaRes.getTransactionTimeout() + " 秒");
        xaRes.setTransactionTimeout(30);//10秒,在超时之前不提交或回滚,数据库会有行锁,锁住业务操作的数据行,此时不能进行truncate等影响该行锁操作

        System.out.println("设置超时时间:" + xaRes.getTransactionTimeout() + " 秒");
        con = xaCon.getConnection();
        stmt = con.createStatement();
        xid = new MyXid(100, new byte[]{0x01}, new byte[]{0x02});
        try {
            System.out.println("start 之前 AutoCommit:" + con.getAutoCommit());
            xaRes.start(xid, XAResource.TMNOFLAGS);// 当上一个 xid 没有commit或rollback时,不能start相同的
            System.out.println("start 之后 AutoCommit:" + con.getAutoCommit());

            stmt.executeUpdate("INSERT INTO TEST_EJB VALUES('事务内部,受到事务控制')");//受到 commit/rollback控制
            //xaRes.end(xid, XAResource.TMSUCCESS);//表示要结束,不能再开启
            xaRes.end(xid, XAResource.TMSUSPEND);//表示暂时挂起,可以再开启
            System.out.println("  end(标记挂起) 之后 AutoCommit:" + con.getAutoCommit());

            stmt.executeUpdate("INSERT INTO TEST_EJB VALUES('事务外部,不受事务控制')");//在end之后,不会受到 commit/rollback控制

//            xaRes.start(xid, XAResource.TMRESUME);//重新开启之前挂起的事务,注意如果当前连接已经建立了则不能进行,需要一个新的 xa 连接来进行
        } finally {
            stmt.close();
            con.close();
            xaCon.close();
        }
    }

    public static void resume() throws Exception {
        XADataSource xaDS;
        XAConnection xaCon;
        XAResource xaRes;
        Xid xid;
        Connection con;
        Statement stmt;
        int ret;
        xaDS = createXADataSource();
        xaCon = xaDS.getXAConnection();
        xaRes = xaCon.getXAResource();
        con = xaCon.getConnection();
        stmt = con.createStatement();
        xid = new MyXid(100, new byte[]{0x01}, new byte[]{0x02});
        try {

            xaRes.start(xid, XAResource.TMRESUME);//重新开启之前挂起的事务,注意如果当前连接已经建立了则不能进行
            System.out.println("resume 之后 AutoCommit:" + con.getAutoCommit());

            stmt.executeUpdate("INSERT INTO TEST_EJB VALUES('重新开启事务内部,再次受到事务控制')");//受到 commit/rollback控制
            xaRes.end(xid, XAResource.TMSUCCESS);//表示要结束,不能再开启
            System.out.println("  end(标记成功) 之后 AutoCommit:" + con.getAutoCommit());

//            xaRes.start(xid, XAResource.TMRESUME);//重新开启之前挂起的事务

            ret = xaRes.prepare(xid);
            if (ret == XAResource.XA_OK) {
                System.out.println("prepare 结果为 XA_OK,可以提交或回滚");
                xaRes.commit(xid, false);
//                xaRes.rollback(xid);
            } else if (ret == XAResource.XA_RDONLY) {//没有执行 insert/update等语句,只是select则不用提交
                System.out.println("prepare 结果为 XA_RDONLY,表示无事务,无需也不能进行后续commit/rollback");
                // xaRes.commit(xid, false); // XA_RDONLY,不能进行提交或回滚,此时事务不存在
            }
        } finally {
            stmt.close();
            con.close();
            xaCon.close();
        }
    }
}


  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Spring+iBatis+JOTM实现JTA事务: 如何处理跨库事物:spring + jtom 的jta事务是个很好的选择. 这个源码示例非常不错,包括所有的源码和jar包,下载后eclipse 或 myeclipse 导入就能用。 里面有详细的说明和注释,欢迎下载传播。有问题请在评价留言,我会及时回复的。 <bean id="jotm" class="org.springframework.transaction.jta.JotmFactoryBean"/> <!-- JTA事务管理器 --> <bean id="myJtaManager" class="org.springframework.transaction.jta.JtaTransactionManager"> <property name="userTransaction"> <ref local="jotm"/> </property> </bean> <!-- 数据源A --> <bean id="dataSourceA" 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="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> </bean> </property> <property name="user" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <!-- 数据源B --> <bean id="dataSourceB" 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="${jdbc2.driver}"/> <property name="url" value="${jdbc2.url}"/> </bean> </property> <property name="user" value="${jdbc2.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <!-- 事务切面配置 --> <aop:config> <aop:pointcut id="serviceOperation" expression="execution(* *..servi1ce*..*(..))"/> <aop:advisor pointcut-ref="serviceOperation" advice-ref="txAdvice"/> </aop:config> <!-- 通知配置 --> <tx:advice id="txAdvice" transaction-manager="myJtaManager"> <tx:attributes> <tx:method name="delete*" rollback-for="Exception"/> <tx:method name="save*" rollback-for="Exception"/> <tx:method name="update*" rollback-for="Exception"/> <tx:method name="*" read-only="true" rollback-for="Exception"/> </tx:attributes> </tx:advice ...... ...... ......
分布式事务问题在Java可以通过使用分布式事务管理框架来解决,如Atomikos、Bitronix、Narayana等。以下是使用Atomikos实现分布式事务示例代码: 1. 配置Atomikos 在项目添加Atomikos的依赖,并在Spring配置文件添加如下配置: ```xml <bean id="dataSource1" class="com.atomikos.jdbc.AtomikosDataSourceBean"> <property name="uniqueResourceName" value="dataSource1" /> <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" /> <property name="xaProperties"> <props> <prop key="user">root</prop> <prop key="password">root</prop> <prop key="URL">jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8</prop> </props> </property> <property name="poolSize" value="10" /> </bean> <bean id="dataSource2" class="com.atomikos.jdbc.AtomikosDataSourceBean"> <property name="uniqueResourceName" value="dataSource2" /> <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" /> <property name="xaProperties"> <props> <prop key="user">root</prop> <prop key="password">root</prop> <prop key="URL">jdbc:mysql://localhost:3306/test2?useUnicode=true&characterEncoding=utf-8</prop> </props> </property> <property name="poolSize" value="10" /> </bean> <bean id="transactionManager" class="com.atomikos.icatch.jta.UserTransactionManager"> <property name="forceShutdown" value="false" /> </bean> <bean id="userTransaction" class="com.atomikos.icatch.jta.J2eeUserTransaction"> <property name="transactionTimeout" value="300" /> </bean> ``` 2. 编写分布式事务代码 在需要执行分布式事务的方法上添加@Transactional注解,并指定transactionManager属性为上述配置文件定义的transactionManager: ```java @Service public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; @Transactional(transactionManager = "transactionManager") public void transfer(String fromUser, String toUser, int amount) { userDao.decreaseBalance(fromUser, amount); userDao.increaseBalance(toUser, amount); } } ``` 3. 测试分布式事务 编写测试代码,调用上述transfer方法,模拟转账操作。如果转账过程出现异常,事务会自动回滚,保证数据的一致性。 ```java @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "classpath:spring-context.xml" }) public class UserServiceTest { @Autowired private UserService userService; @Test public void testTransfer() { userService.transfer("user1", "user2", 100); } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值