使用Spring和Atomikos集成JTA分布式事务

分布式事务


分布式事务是指事务位于不同的分布式系统的不同节点之上。目的是保证分布式系统事务的原子性和一致性。
XA规范是由X/Open DTP提出的分布式事务规范。XA规范包含四大角色:
  • 应用程序( AP )
  • 事务管理器( TM )
  • 资源管理器( RM )
  • 通信资源管理器( CRM )
常见的事务 管理器( TM )是交易中间件,常见的资源管理器( RM )是数据库,常见的通信资源管理器( CRM )是消息 中间件。XA的优点是大多的数据库厂商都实现这个规范,拥有强原子性和一致性,使用它实现分布式事务相对的简单。但缺点也显示而见,各节点通信次数过多,受网络传输影响大,这些因素往往很容易造成长事务,数据行锁定时间过长,使得分布式事务性能低下。虽然分布式事务在性能上存在具大的劣势,不能满足高并发的性能要求,但基于其使用简单以及不侵入业务代码的优点,我们还是可以在适当的场景下使用它的。

JTA简介


JTA是Java平台的分布式事务规范(Java Transaction API),目前开源独立(不依赖应用服务器)的JTA实现有
  • JOTM (Java Open Transaction Manager)
  • Atomikos TransactionsEssentials
JOTM目前已停止维护,而Atomikos TransactionsEssentials是Atomikos公司JTA产品开源版,其社区较活跃、文档较丰富。

Spring与Atomikos的集成

本文的例子中的两个mysql数据库实例:
  • 192.168.1.106  bank1
  • 192.168.1.107  bank2
分别在bank1和bank2两个数据库执行以下sql脚本:

DROP TABLE IF EXISTS `customer`;
CREATE TABLE `customer` (
  `customer_id` int(11) NOT NULL AUTO_INCREMENT,
  `balance` int(11) DEFAULT NULL,
  PRIMARY KEY (`customer_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

SET FOREIGN_KEY_CHECKS = 1;

INSERT INTO customer (balance) values(100);

加入MAVEN依赖

 <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>3.2.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>3.2.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>3.2.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>3.2.2.RELEASE</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.7.0</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.34</version>
        </dependency>
        <dependency>
            <groupId>javax.transaction</groupId>
            <artifactId>jta</artifactId>
            <version>1.1</version>
        </dependency>
        <dependency>
            <groupId>com.atomikos</groupId>
            <artifactId>transactions-jta</artifactId>
            <version>4.0.3</version>
        </dependency>

        <dependency>
            <groupId>com.atomikos</groupId>
            <artifactId>transactions</artifactId>
            <version>4.0.3</version>
        </dependency>
        <dependency>
            <groupId>com.atomikos</groupId>
            <artifactId>transactions-jdbc</artifactId>
            <version>4.0.3</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>

applicationContext.xml

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

	<!-- Annotation Config -->
	<context:annotation-config/>

	<context:component-scan base-package="org.massive.jta.*"/>

	<bean id="dataSource1" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close">
		<property name="uniqueResourceName" value="dataSource1"/>
		<property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource"/>
		<property name="xaProperties">
			<props>
				<prop key="url">jdbc:mysql://192.168.1.106:3306/bank1</prop>
				<prop key="user">root</prop>
				<prop key="password">root</prop>
			</props>
		</property>
		<property name="minPoolSize" value="10" />
		<property name="maxPoolSize" value="20" />
		<property name="borrowConnectionTimeout" value="30" />
		<property name="testQuery" value="select 1" />
		<property name="maintenanceInterval" value="60" />
	</bean>

	<bean id="dataSource2" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close">
		<property name="uniqueResourceName" value="dataSource2"/>
		<property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource"/>
		<property name="xaProperties">
			<props>
				<prop key="url">jdbc:mysql://192.168.1.107:3306/bank2</prop>
				<prop key="user">root</prop>
				<prop key="password">root</prop>
			</props>
		</property>
		<property name="minPoolSize" value="10" />
		<property name="maxPoolSize" value="20" />
		<property name="borrowConnectionTimeout" value="30" />
		<property name="testQuery" value="select 1" />
		<property name="maintenanceInterval" value="60" />
	</bean>

	<bean id="userTransactionService"
		  class="com.atomikos.icatch.config.UserTransactionServiceImp"
		  init-method="init" destroy-method="shutdownForce">
		<constructor-arg>
			<!-- IMPORTANT: specify all Atomikos properties here -->
			<props>
				<prop key="com.atomikos.icatch.service">
					com.atomikos.icatch.standalone.UserTransactionServiceFactory
				</prop>
			</props>
		</constructor-arg>
	</bean>

	<!--
         Construct Atomikos UserTransactionManager,
         needed to configure Spring
    -->
	<bean id="AtomikosTransactionManager"
		  class="com.atomikos.icatch.jta.UserTransactionManager"
		  init-method="init" destroy-method="close"
		  depends-on="userTransactionService">

		<!-- IMPORTANT: disable startup because the userTransactionService above does this -->
		<property name="startupTransactionService" value="false"/>
		<!--
                 when close is called,
                 should we force transactions to terminate or not?
           -->
		<property name="forceShutdown" value="false" />
	</bean>

	<!--
         Also use Atomikos UserTransactionImp,
         needed to configure Spring
    -->
	<bean id="AtomikosUserTransaction"
		  class="com.atomikos.icatch.jta.UserTransactionImp"
		  depends-on="userTransactionService">
		<property name="transactionTimeout" value="300" />
	</bean>

	<!--
       Configure the Spring framework to use JTA transactions from Atomikos
    -->
	<bean id="JtaTransactionManager"
		  class="org.springframework.transaction.jta.JtaTransactionManager"
		  depends-on="userTransactionService">
		<property name="transactionManager" ref="AtomikosTransactionManager" />
		<property name="userTransaction" ref="AtomikosUserTransaction" />
		<property name="allowCustomIsolationLevels" value="true"/>
	</bean>


	<bean id="jdbcTemplate1" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name = "dataSource" ref="dataSource1"/>
	</bean>
	<bean id="jdbcTemplate2" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name = "dataSource" ref="dataSource2"/>
	</bean>

	<tx:annotation-driven transaction-manager="JtaTransactionManager"/>

</beans>

编写Service

在这里模拟银行的交易,bank1的customer转给bank2的customer 金额10RMB
package org.massive.jta.service;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;

/**
 * Created by Massive on 2017/1/4.
 */
@Service
public class SimpleService {

    @Resource
    JdbcTemplate jdbcTemplate1;

    @Resource
    JdbcTemplate jdbcTemplate2;

    @Transactional
    public void updateSample1() {
        jdbcTemplate1.update("update customer set balance = (balance - 10) where customer_id = 1");
        jdbcTemplate2.update("update customer set balance = (balance + 10) where customer_id = 1");
    }

    @Transactional
    public void updateSample2() {
        //---------------------------------------------------------
        // 使第一个更新操作正常,使第二个更新的操作出现异常,测试异常回滚
        //---------------------------------------------------------
        jdbcTemplate1.update("update customer set balance = (balance - 10) where customer_id = 1");
        jdbcTemplate2.update("update customer set balance = 'massive'");
    }

}

测试

package org.massive.jta;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.massive.jta.service.SimpleService;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;

/**
 * Created by Massive on 2017/1/3.
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath*:applicationContext.xml"})
public class JtaTestCase {

    @Resource
    SimpleService simpleService;

    @Test
    public void sample1() {
        simpleService.updateSample1();
    }

    @Test
    public void sample2() {
        simpleService.updateSample2();
    }

}

测试结果

执行sample1后 bank1的customer余额为90,bank2的customer余额为110,分布事务成功
执行sample2后 bank1的customer余额为90,bank2的customer余额为110,bank1的操作成功,bank2的操作失败,分布式事务回滚。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值