Spring中使用atomikos+druid實(shí)現(xiàn)經(jīng)典分布式事務(wù)的方法
經(jīng)典分布式事務(wù),是相對互聯(lián)網(wǎng)中的柔性分布式事務(wù)而言,其特性為ACID原則,包括原子性(Atomictiy)、一致性(Consistency)、隔離性(Isolation)、持久性(Durabilit):
- 原子性:事務(wù)是一個包含一系列操作的原子操作。事務(wù)的原子性確保這些操作全部完成或者全部失敗。
- 一致性:一旦事務(wù)的所有操作結(jié)束,事務(wù)就被提交。然后你的數(shù)據(jù)和資源將處于遵循業(yè)務(wù)規(guī)則的一直狀態(tài)。
- 隔離性:因?yàn)橥瑫r在相同數(shù)據(jù)集上可能有許多事務(wù)處理,每個事務(wù)應(yīng)該與其他事務(wù)隔離,避免數(shù)據(jù)破壞。
- 持久性:一旦事務(wù)完成,他的結(jié)果應(yīng)該能夠承受任何系統(tǒng)錯誤(想象一下在事務(wù)提交過程中機(jī)器的電源被切斷的情況)。通常,事務(wù)的結(jié)果被寫入持續(xù)性存儲。
XA是啥?
XA是由X/Open組織提出的分布式事務(wù)的架構(gòu)(或者叫協(xié)議)。XA架構(gòu)主要定義了(全局)事務(wù)管理器(Transaction Manager)和(局部)資源管理器(Resource Manager)之間的接口。XA接口是雙向的系統(tǒng)接口,在事務(wù)管理器(Transaction Manager)以及一個或多個資源管理器(Resource Manager)之間形成通信橋梁。也就是說,在基于XA的一個事務(wù)中,我們可以針對多個資源進(jìn)行事務(wù)管理,例如一個系統(tǒng)訪問多個數(shù)據(jù)庫,或即訪問數(shù)據(jù)庫、又訪問像消息中間件這樣的資源。這樣我們就能夠?qū)崿F(xiàn)在多個數(shù)據(jù)庫和消息中間件直接實(shí)現(xiàn)全部提交、或全部取消的事務(wù)。XA規(guī)范不是java的規(guī)范,而是一種通用的規(guī)范,
目前各種數(shù)據(jù)庫、以及很多消息中間件都支持XA規(guī)范。
JTA是滿足XA規(guī)范的、用于Java開發(fā)的規(guī)范。所以,當(dāng)我們說,使用JTA實(shí)現(xiàn)分布式事務(wù)的時候,其實(shí)就是說,使用JTA規(guī)范,實(shí)現(xiàn)系統(tǒng)內(nèi)多個數(shù)據(jù)庫、消息中間件等資源的事務(wù)。
JTA(Java Transaction API),是J2EE的編程接口規(guī)范,它是XA協(xié)議的JAVA實(shí)現(xiàn)。它主要定義了:
- 一個事務(wù)管理器的接口javax.transaction.TransactionManager,定義了有關(guān)事務(wù)的開始、提交、撤回等>操作。
- 一個滿足XA規(guī)范的資源定義接口javax.transaction.xa.XAResource,一種資源如果要支持JTA事務(wù),就需要讓它的資源實(shí)現(xiàn)該XAResource接口,并實(shí)現(xiàn)該接口定義的兩階段提交相關(guān)的接口。如果我們有一個應(yīng)用,它使用JTA接口實(shí)現(xiàn)事務(wù),應(yīng)用在運(yùn)行的時候,就需要一個實(shí)現(xiàn)JTA的容器,一般情況下,這是一個J2EE容器,像JBoss,Websphere等應(yīng)用服務(wù)器。但是,也有一些獨(dú)立的框架實(shí)現(xiàn)了JTA,例如 Atomikos, bitronix 都提供了jar包方式的JTA實(shí)現(xiàn)框架。這樣我們就能夠在Tomcat或者Jetty之類的服務(wù)器上運(yùn)行使用JTA實(shí)現(xiàn)事務(wù)的應(yīng)用系統(tǒng)。在上面的本地事務(wù)和外部事務(wù)的區(qū)別中說到,JTA事務(wù)是外部事務(wù),可以用來實(shí)現(xiàn)對多個資源的事務(wù)性。它正是通過每個資源實(shí)現(xiàn)的XAResource來進(jìn)行兩階段提交的控制。感興趣的同學(xué)可以看看這個接口的方法,除了commit, rollback等方法以外,還有end(), forget(), isSameRM(), prepare()等等。光從這些接口就能夠想象JTA在實(shí)現(xiàn)兩階段事務(wù)的復(fù)雜性。
本篇以Spring MVC+Maven+Atomikos+Druid+MyBatis演示分布式事務(wù)的實(shí)現(xiàn)。
Mave 的pom.xml
<properties>
<jdk.version>1.8</jdk.version>
<!-- 注mysql的版本和druid的版本一定要搭配,否則會有問題,目前這兩個版本是搭配好的 -->
<mysql.version>8.0.11</mysql.version>
<druid.version>1.1.17</druid.version>
<spring.version>5.1.8.RELEASE</spring.version>
<cglib.version>3.2.12</cglib.version>
<atomikos.version>5.0.0</atomikos.version>
<aspectjweaver.version>1.9.4</aspectjweaver.version>
<aspectjrt.version>1.5.4</aspectjrt.version>
<jta.version>1.1</jta.version>
<mybatise.version>3.2.0</mybatise.version>
<mybatis.spring>1.2.0</mybatis.spring>
<log4j.version>1.2.17</log4j.version>
<junit.version>4.12</junit.version>
<cglib.version>3.2.4</cglib.version>
</properties>
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatise.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>${mybatis.spring}</version>
</dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>atomikos-util</artifactId>
<version>${atomikos.version}</version>
</dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions</artifactId>
<version>${atomikos.version}</version>
</dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions-jta</artifactId>
<version>${atomikos.version}</version>
</dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions-jdbc</artifactId>
<version>${atomikos.version}</version>
</dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions-api</artifactId>
<version>${atomikos.version}</version>
</dependency>
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
<version>${jta.version}</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>${cglib.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- Spring Aop依賴jar -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectjweaver.version}</version>
</dependency>
<dependency>
<groupId>aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${aspectjrt.version}</version>
</dependency>
<!-- CGLIB -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>${cglib.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scopte>test</scope>
</dependency>
</dependencies>
spring-application-context.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:p="http://www.springframework.org/schema/p"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd">
<!-- 1. 數(shù)據(jù)源配置 -->
<context:property-placeholder location="classpath*:*.properties" file-encoding="utf8" />
<bean id="utf8" class="java.lang.String">
<constructor-arg value="utf-8"></constructor-arg>
</bean>
<!-- 開啟異步任務(wù)(同時開啟定時器注解掃描) -->
<task:annotation-driven />
<!-- 使用@AspectJ風(fēng)格的切面聲明 -->
<!-- <aop:aspectj-autoproxy/> -->
<!-- 使用Annotation自動注冊Bean -->
<!-- 在主容器中不掃描@Controller注解,在SpringMvc中只掃描@Controller注解 -->
<context:component-scan base-package="net.xiake6"><!-- base-package 如果多個,用“,”分隔 -->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>
<!-- 引入Mybatis配置 -->
<!-- <import resource="spring-mybatis-atomikos-druid.xml"/> -->
<import resource="spring-mybatis-atomikos-druid.xml"/>
</beans>
spring-mybatis-atomikos-druid.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:jee="http://www.springframework.org/schema/jee"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
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/jee
http://www.springframework.org/schema/jee/spring-jee-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-lazy-init="true">
<context:annotation-config />
<!-- 使用Druid使為XA數(shù)據(jù)源 -->
<bean id="abstractXADataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close" abstract="true">
<property name="xaDataSourceClassName" value="com.alibaba.druid.pool.xa.DruidXADataSource"/>
<property name="xaProperties">
<props>
<prop key="driverClassName">${jdbc.driverClassName}</prop>
<!-- 配置初始化大小、最小、最大 -->
<prop key="initialSize">10</prop>
<prop key="minIdle">3</prop>
<prop key="maxActive">100</prop>
<!-- 配置獲取連接等待超時的時間 -->
<prop key="maxWait">60000</prop>
<!-- 配置間隔多久才進(jìn)行一次檢測,檢測需要關(guān)閉的空閑連接,單位是毫秒 -->
<prop key="timeBetweenEvictionRunsMillis">60000</prop>
<!-- 配置一個連接在池中最小生存的時間,單位是毫秒 -->
<prop key="minEvictableIdleTimeMillis">300000</prop>
<prop key="validationQuery">SELECT 'x'</prop>
<prop key="testWhileIdle">true</prop>
<prop key="testOnBorrow">false</prop>
<prop key="testOnReturn">false</prop>
<!-- 配置監(jiān)控統(tǒng)計(jì)攔截的filters -->
<prop key="filters">stat</prop>
</props>
</property>
</bean>
<!-- 配置數(shù)據(jù)源一 -->
<bean id="dataSourceOne" parent="abstractXADataSource">
<property name="uniqueResourceName">
<value>dataSourceOne</value>
</property>
<property name="xaProperties">
<props>
<prop key="url">${jdbc.url}</prop>
<prop key="username">${jdbc.username}</prop>
<prop key="password">${jdbc.password}</prop>
</props>
</property>
</bean>
<!--配置數(shù)據(jù)源二-->
<bean id="dataSourceTwo" parent="abstractXADataSource">
<property name="uniqueResourceName">
<value>dataSourceTwo</value>
</property>
<property name="xaProperties">
<props>
<prop key="url">${jdbc.two.url}</prop>
<prop key="username">${jdbc.two.username}</prop>
<prop key="password">${jdbc.two.password}</prop>
</props>
</property>
</bean>
<!--mybatis的相關(guān)配置-->
<bean id="sqlSessionFactoryOne" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSourceOne"/>
<property name="mapperLocations" value="classpath*:mapping/ds1/*.xml"/>
</bean>
<bean id="sqlSessionFactoryTwo" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSourceTwo"/>
<property name="mapperLocations" value="classpath*:mapping/ds2/*.xml"/>
</bean>
<!--配置mybatis映射文件自動掃描-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="net.xiake6.dao.ds1"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryOne"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="net.xiake6.dao.ds2"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryTwo"/>
</bean>
<!--配置分布式事務(wù)-->
<bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" init-method="init" destroy-method="close">
<property name="forceShutdown" value="false"/>
</bean>
<bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp">
<property name="transactionTimeout" value="3000"/>
</bean>
<!--JTA事務(wù)管理器-->
<bean id="jtaTransactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManager">
<ref bean="atomikosTransactionManager"/>
</property>
<property name="userTransaction">
<ref bean="atomikosUserTransaction"/>
</property>
<property name="allowCustomIsolationLevels" value="true"/>
</bean>
<aop:config proxy-target-class="true">
<aop:advisor pointcut="execution(* *net.xiake6.service..*(..))" advice-ref="txAdvice" />
</aop:config>
<tx:advice id="txAdvice" transaction-manager="jtaTransactionManager">
<tx:attributes>
<tx:method name="insert*" propagation="REQUIRED" read-only="false" rollback-for="*Exception"/>
<tx:method name="add*" propagation="REQUIRED" read-only="false" rollback-for="*Exception" />
<tx:method name="save*" propagation="REQUIRED" read-only="false" rollback-for="*Exception" />
<tx:method name="delete*" propagation="REQUIRED" read-only="false" rollback-for="*Exception" />
<tx:method name="del*" propagation="REQUIRED" read-only="false" rollback-for="*Exception" />
<tx:method name="update*" propagation="REQUIRED" read-only="false" rollback-for="*Exception" />
<tx:method name="select*" propagation="REQUIRED" read-only="true" />
<tx:method name="query" propagation="REQUIRED" read-only="true" />
</tx:attributes>
</tx:advice>
<!-- 配置事務(wù)管理 -->
<tx:annotation-driven transaction-manager="jtaTransactionManager" />
</beans>
jdbc.properties
#mysql 6.*以上 jdbc.driverClassName = com.mysql.cj.jdbc.Driver jdbc.url = jdbc:mysql://127.0.0.1:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8&pinGlobalTxToPhysicalConnection=true&useSSL=false jdbc.username =root jdbc.password =root jdbc.two.url = jdbc:mysql://127.0.0.1:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8&pinGlobalTxToPhysicalConnection=true&useSSL=false jdbc.two.username =root jdbc.two.password =root
jta.properties
com.atomikos.icatch.service=com.atomikos.icatch.standalone.UserTransactionServiceFactory com.atomikos.icatch.console_file_name=tm.release.out com.atomikos.icatch.log_base_name=tm.releaselog com.atomikos.icatch.tm_unique_name=com.atomikos.spring.jdbc.tm.release com.atomikos.icatch.console_log_level=INFO
TestInsert.java
@ContextConfiguration(value = {"classpath:spring-application-context.xml"})
@RunWith(SpringJUnit4ClassRunner.class)
public class TestInsert {
private Logger logger = LoggerFactory.getLogger(TestInsert.class);
@Autowired
private BatchInsertService batchInsertService;
@Test
public void insert(){
long startTime = System.currentTimeMillis();
User user = new User();
user.setName("User_"+(int)(Math.random()*100));
user.setAge((int)(Math.random()*100));
CustInfo info = new CustInfo();
info.setPhone("123456789"+(int)(Math.random()*100));
batchInsertService.insert(user,info);
long endTime = System.currentTimeMillis();
logger.info("共耗時:{}毫秒",endTime -startTime);
}
}
BatchInsertService.java
@Service
public class BatchInsertService {
private Logger logger = LoggerFactory.getLogger(BatchInsertService.class);
@Autowired
private UserService userService;
@Autowired
private CustInfoService custInfoService;
@Transactional(rollbackFor= {Exception.class,RuntimeException.class})
public void insert(User user,CustInfo custInfo) {
int insertUser = userService.insert(user);
logger.info("insertUser={}",insertUser);
int insertCustInfo = custInfoService.insert(custInfo);
logger.info("insertCustInfo={}",insertCustInfo);
}
}
UserService.java
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public int insert(User record) {
int result = userMapper.insert(record);
return result;
}
}
CustInfoService.java
@Service
public class CustInfoService {
@Autowired
private CustInfoMapper custInfoMapper;
public int insert(CustInfo record) {
int result = custInfoMapper.insert(record);
long now = System.currentTimeMillis();
// 模擬一個異常
if (now % 2 == 0) {
throw new RuntimeException("CustInfoMapper throws test insert exception");
}
return result;
}
}
Mapper和Bean等就不列出來了,完成的示例工程在github: https://github.com/fenglibin/DruidWithAtomikos
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- SpringBoot內(nèi)部調(diào)用事務(wù)不起作用問題的解決方案
- Spring中的事務(wù)傳播行為示例詳解
- SpringBoot+Dubbo+Seata分布式事務(wù)實(shí)戰(zhàn)詳解
- Spring編程式和聲明式事務(wù)實(shí)例講解小結(jié)
- springboot中事務(wù)管理@Transactional的注意事項(xiàng)與使用場景
- Springboot-dubbo-fescar 阿里分布式事務(wù)的實(shí)現(xiàn)方法
- SpringDataMongoDB多文檔事務(wù)的實(shí)現(xiàn)
- Spring事務(wù)傳播行為問題解決
相關(guān)文章
Java?RabbitMQ的持久化和發(fā)布確認(rèn)詳解
這篇文章主要為大家詳細(xì)介紹了RabbitMQ的持久化和發(fā)布確認(rèn),文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-03-03
java通過客戶端訪問服務(wù)器webservice的方法
這篇文章主要介紹了java通過客戶端訪問服務(wù)器webservice的方法,涉及java創(chuàng)建與調(diào)用webservice的相關(guān)技巧,需要的朋友可以參考下2016-08-08
SpringBoot如何接收數(shù)組參數(shù)的方法
這篇文章主要介紹了SpringBoot如何接收數(shù)組參數(shù)的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
帶你了解Java數(shù)據(jù)結(jié)構(gòu)和算法之鏈表
這篇文章主要為大家介紹了Java數(shù)據(jù)結(jié)構(gòu)和算法之鏈表 ,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-01-01
淺談Java變量賦值運(yùn)算符及相關(guān)實(shí)例
這篇文章主要介紹了Java賦值運(yùn)算符的一些知識,需要的朋友可以參考下。2017-09-09

