分布式事务
分布式事务是指事务位于不同的分布式系统的不同节点之上。目的是保证分布式系统事务的原子性和一致性。
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();
}
}
测试结果
执行sample2后 bank1的customer余额为90,bank2的customer余额为110,bank1的操作成功,bank2的操作失败,分布式事务回滚。