經典分布式事務,是相對互聯網中的柔性分布式事務而言,其特性為ACID原則,包括原子性(Atomictiy)、一致性(Consistency)、隔離性(Isolation)、持久性(Durabilit):
-
原子性:事務是一個包含一系列操作的原子操作。事務的原子性確保這些操作全部完成或者全部失敗。
-
一致性:一旦事務的所有操作結束,事務就被提交。然后你的數據和資源將處于遵循業務規則的一直狀態。
-
隔離性:因為同時在相同數據集上可能有許多事務處理,每個事務應該與其他事務隔離,避免數據破壞。
-
持久性:一旦事務完成,他的結果應該能夠承受任何系統錯誤(想象一下在事務提交過程中機器的電源被切斷的情況)。通常,事務的結果被寫入持續性存儲。
XA是啥?
XA是由X/Open組織提出的分布式事務的架構(或者叫協議)。XA架構主要定義了(全局)事務管理器(Transaction Manager)和(局部)資源管理器(Resource Manager)之間的接口。XA接口是雙向的系統接口,在事務管理器(Transaction Manager)以及一個或多個資源管理器(Resource Manager)之間形成通信橋梁。也就是說,在基于XA的一個事務中,我們可以針對多個資源進行事務管理,例如一個系統訪問多個數據庫,或即訪問數據庫、又訪問像消息中間件這樣的資源。這樣我們就能夠實現在多個數據庫和消息中間件直接實現全部提交、或全部取消的事務。XA規范不是java的規范,而是一種通用的規范,
目前各種數據庫、以及很多消息中間件都支持XA規范。
JTA是滿足XA規范的、用于Java開發的規范。所以,當我們說,使用JTA實現分布式事務的時候,其實就是說,使用JTA規范,實現系統內多個數據庫、消息中間件等資源的事務。
JTA(Java Transaction API),是J2EE的編程接口規范,它是XA協議的JAVA實現。它主要定義了:
-
一個事務管理器的接口javax.transaction.TransactionManager,定義了有關事務的開始、提交、撤回等>操作。
-
一個滿足XA規范的資源定義接口javax.transaction.xa.XAResource,一種資源如果要支持JTA事務,就需要讓它的資源實現該XAResource接口,并實現該接口定義的兩階段提交相關的接口。如果我們有一個應用,它使用JTA接口實現事務,應用在運行的時候,就需要一個實現JTA的容器,一般情況下,這是一個J2EE容器,像JBoss,Websphere等應用服務器。但是,也有一些獨立的框架實現了JTA,例如 Atomikos, bitronix 都提供了jar包方式的JTA實現框架。這樣我們就能夠在Tomcat或者Jetty之類的服務器上運行使用JTA實現事務的應用系統。在上面的本地事務和外部事務的區別中說到,JTA事務是外部事務,可以用來實現對多個資源的事務性。它正是通過每個資源實現的XAResource來進行兩階段提交的控制。感興趣的同學可以看看這個接口的方法,除了commit, rollback等方法以外,還有end(), forget(), isSameRM(), prepare()等等。光從這些接口就能夠想象JTA在實現兩階段事務的復雜性。
本篇以Spring MVC+Maven+Atomikos+Druid+MyBatis演示分布式事務的實現。
Mave 的pom.xml
002 | < jdk.version >1.8</ jdk.version > |
004 | < mysql.version >8.0.11</ mysql.version > |
005 | < druid.version >1.1.17</ druid.version > |
006 | < spring.version >5.1.8.RELEASE</ spring.version > |
007 | < cglib.version >3.2.12</ cglib.version > |
008 | < atomikos.version >5.0.0</ atomikos.version > |
009 | < aspectjweaver.version >1.9.4</ aspectjweaver.version > |
010 | < aspectjrt.version >1.5.4</ aspectjrt.version > |
011 | < jta.version >1.1</ jta.version > |
012 | < mybatise.version >3.2.0</ mybatise.version > |
013 | < mybatis.spring >1.2.0</ mybatis.spring > |
014 | < log4j.version >1.2.17</ log4j.version > |
015 | < junit.version >4.12</ junit.version > |
016 | < cglib.version >3.2.4</ cglib.version > |
020 | < groupId >org.mybatis</ groupId > |
021 | < artifactId >mybatis</ artifactId > |
022 | < version >${mybatise.version}</ version > |
025 | < groupId >org.mybatis</ groupId > |
026 | < artifactId >mybatis-spring</ artifactId > |
027 | < version >${mybatis.spring}</ version > |
030 | < groupId >com.atomikos</ groupId > |
031 | < artifactId >atomikos-util</ artifactId > |
032 | < version >${atomikos.version}</ version > |
035 | < groupId >com.atomikos</ groupId > |
036 | < artifactId >transactions</ artifactId > |
037 | < version >${atomikos.version}</ version > |
040 | < groupId >com.atomikos</ groupId > |
041 | < artifactId >transactions-jta</ artifactId > |
042 | < version >${atomikos.version}</ version > |
045 | < groupId >com.atomikos</ groupId > |
046 | < artifactId >transactions-jdbc</ artifactId > |
047 | < version >${atomikos.version}</ version > |
050 | < groupId >com.atomikos</ groupId > |
051 | < artifactId >transactions-api</ artifactId > |
052 | < version >${atomikos.version}</ version > |
055 | < groupId >javax.transaction</ groupId > |
056 | < artifactId >jta</ artifactId > |
057 | < version >${jta.version}</ version > |
060 | < groupId >cglib</ groupId > |
061 | < artifactId >cglib-nodep</ artifactId > |
062 | < version >${cglib.version}</ version > |
065 | < groupId >org.springframework</ groupId > |
066 | < artifactId >spring-test</ artifactId > |
067 | < version >${spring.version}</ version > |
070 | < groupId >org.springframework</ groupId > |
071 | < artifactId >spring-web</ artifactId > |
072 | < version >${spring.version}</ version > |
075 | < groupId >org.springframework</ groupId > |
076 | < artifactId >spring-tx</ artifactId > |
077 | < version >${spring.version}</ version > |
080 | < groupId >org.springframework</ groupId > |
081 | < artifactId >spring-beans</ artifactId > |
082 | < version >${spring.version}</ version > |
085 | < groupId >org.springframework</ groupId > |
086 | < artifactId >spring-jdbc</ artifactId > |
087 | < version >${spring.version}</ version > |
090 | < groupId >org.springframework</ groupId > |
091 | < artifactId >spring-webmvc</ artifactId > |
092 | < version >${spring.version}</ version > |
095 | < groupId >org.springframework</ groupId > |
096 | < artifactId >spring-orm</ artifactId > |
097 | < version >${spring.version}</ version > |
100 | < groupId >org.springframework</ groupId > |
101 | < artifactId >spring-context-support</ artifactId > |
102 | < version >${spring.version}</ version > |
106 | < groupId >org.aspectj</ groupId > |
107 | < artifactId >aspectjweaver</ artifactId > |
108 | < version >${aspectjweaver.version}</ version > |
112 | < groupId >aspectj</ groupId > |
113 | < artifactId >aspectjrt</ artifactId > |
114 | < version >${aspectjrt.version}</ version > |
118 | < groupId >cglib</ groupId > |
119 | < artifactId >cglib</ artifactId > |
120 | < version >${cglib.version}</ version > |
123 | < groupId >mysql</ groupId > |
124 | < artifactId >mysql-connector-java</ artifactId > |
125 | < version >${mysql.version}</ version > |
128 | < groupId >com.alibaba</ groupId > |
129 | < artifactId >druid</ artifactId > |
130 | < version >${druid.version}</ version > |
133 | < groupId >junit</ groupId > |
134 | < artifactId >junit</ artifactId > |
135 | < version >${junit.version}</ version > |
spring-application-context.xml
01 | <? xml version = "1.0" encoding = "UTF-8" ?> |
21 | < context:property-placeholder location = "classpath*:*.properties" file-encoding = "utf8" /> |
22 | < bean id = "utf8" class = "java.lang.String" > |
23 | < constructor-arg value = "utf-8" ></ constructor-arg > |
26 | < task:annotation-driven /> |
31 | < context:component-scan base-package = "net.xiake6" > |
32 | < context:exclude-filter type = "annotation" expression = "org.springframework.stereotype.Controller" /> |
33 | </ context:component-scan > |
36 | < import resource = "spring-mybatis-atomikos-druid.xml" /> |
spring-mybatis-atomikos-druid.xml
001 | <? xml version = "1.0" encoding = "UTF-8" ?> |
018 | default-lazy-init = "true" > |
020 | < context:annotation-config /> |
022 | < bean id = "abstractXADataSource" class = "com.atomikos.jdbc.AtomikosDataSourceBean" init-method = "init" destroy-method = "close" abstract = "true" > |
023 | < property name = "xaDataSourceClassName" value = "com.alibaba.druid.pool.xa.DruidXADataSource" /> |
024 | < property name = "xaProperties" > |
026 | < prop key = "driverClassName" >${jdbc.driverClassName}</ prop > |
028 | < prop key = "initialSize" >10</ prop > |
029 | < prop key = "minIdle" >3</ prop > |
030 | < prop key = "maxActive" >100</ prop > |
032 | < prop key = "maxWait" >60000</ prop > |
034 | < prop key = "timeBetweenEvictionRunsMillis" >60000</ prop > |
036 | < prop key = "minEvictableIdleTimeMillis" >300000</ prop > |
037 | < prop key = "validationQuery" >SELECT 'x'</ prop > |
038 | < prop key = "testWhileIdle" >true</ prop > |
039 | < prop key = "testOnBorrow" >false</ prop > |
040 | < prop key = "testOnReturn" >false</ prop > |
042 | < prop key = "filters" >stat</ prop > |
047 | < bean id = "dataSourceOne" parent = "abstractXADataSource" > |
048 | < property name = "uniqueResourceName" > |
049 | < value >dataSourceOne</ value > |
051 | < property name = "xaProperties" > |
053 | < prop key = "url" >${jdbc.url}</ prop > |
054 | < prop key = "username" >${jdbc.username}</ prop > |
055 | < prop key = "password" >${jdbc.password}</ prop > |
060 | < bean id = "dataSourceTwo" parent = "abstractXADataSource" > |
061 | < property name = "uniqueResourceName" > |
062 | < value >dataSourceTwo</ value > |
064 | < property name = "xaProperties" > |
066 | < prop key = "url" >${jdbc.two.url}</ prop > |
067 | < prop key = "username" >${jdbc.two.username}</ prop > |
068 | < prop key = "password" >${jdbc.two.password}</ prop > |
074 | < bean id = "sqlSessionFactoryOne" class = "org.mybatis.spring.SqlSessionFactoryBean" > |
075 | < property name = "dataSource" ref = "dataSourceOne" /> |
076 | < property name = "mapperLocations" value = "classpath*:mapping/ds1/*.xml" /> |
079 | < bean id = "sqlSessionFactoryTwo" class = "org.mybatis.spring.SqlSessionFactoryBean" > |
080 | < property name = "dataSource" ref = "dataSourceTwo" /> |
081 | < property name = "mapperLocations" value = "classpath*:mapping/ds2/*.xml" /> |
085 | < bean class = "org.mybatis.spring.mapper.MapperScannerConfigurer" > |
086 | < property name = "basePackage" value = "net.xiake6.dao.ds1" /> |
087 | < property name = "sqlSessionFactoryBeanName" value = "sqlSessionFactoryOne" /> |
090 | < bean class = "org.mybatis.spring.mapper.MapperScannerConfigurer" > |
091 | < property name = "basePackage" value = "net.xiake6.dao.ds2" /> |
092 | < property name = "sqlSessionFactoryBeanName" value = "sqlSessionFactoryTwo" /> |
096 | < bean id = "atomikosTransactionManager" class = "com.atomikos.icatch.jta.UserTransactionManager" init-method = "init" destroy-method = "close" > |
097 | < property name = "forceShutdown" value = "false" /> |
099 | < bean id = "atomikosUserTransaction" class = "com.atomikos.icatch.jta.UserTransactionImp" > |
100 | < property name = "transactionTimeout" value = "3000" /> |
103 | < bean id = "jtaTransactionManager" class = "org.springframework.transaction.jta.JtaTransactionManager" > |
104 | < property name = "transactionManager" > |
105 | < ref bean = "atomikosTransactionManager" /> |
107 | < property name = "userTransaction" > |
108 | < ref bean = "atomikosUserTransaction" /> |
110 | < property name = "allowCustomIsolationLevels" value = "true" /> |
114 | < aop:config proxy-target-class = "true" > |
115 | < aop:advisor pointcut = "execution(* *net.xiake6.service..*(..))" advice-ref = "txAdvice" /> |
117 | < tx:advice id = "txAdvice" transaction-manager = "jtaTransactionManager" > |
119 | < tx:method name = "insert*" propagation = "REQUIRED" read-only = "false" rollback-for = "*Exception" /> |
120 | < tx:method name = "add*" propagation = "REQUIRED" read-only = "false" rollback-for = "*Exception" /> |
121 | < tx:method name = "save*" propagation = "REQUIRED" read-only = "false" rollback-for = "*Exception" /> |
122 | < tx:method name = "delete*" propagation = "REQUIRED" read-only = "false" rollback-for = "*Exception" /> |
123 | < tx:method name = "del*" propagation = "REQUIRED" read-only = "false" rollback-for = "*Exception" /> |
124 | < tx:method name = "update*" propagation = "REQUIRED" read-only = "false" rollback-for = "*Exception" /> |
125 | < tx:method name = "select*" propagation = "REQUIRED" read-only = "true" /> |
126 | < tx:method name = "query" propagation = "REQUIRED" read-only = "true" /> |
131 | < tx:annotation-driven transaction-manager = "jtaTransactionManager" /> |
jdbc.properties
2 | jdbc.driverClassName = com.mysql.cj.jdbc.Driver |
jta.properties
1 | com.atomikos.icatch.service=com.atomikos.icatch.standalone.UserTransactionServiceFactory |
2 | com.atomikos.icatch.console_file_name=tm.release.out |
3 | com.atomikos.icatch.log_base_name=tm.releaselog |
4 | com.atomikos.icatch.tm_unique_name=com.atomikos.spring.jdbc.tm.release |
5 | com.atomikos.icatch.console_log_level=INFO |
TestInsert.java
01 | @ContextConfiguration (value = { "classpath:spring-application-context.xml" }) |
02 | @RunWith (SpringJUnit4ClassRunner. class ) |
03 | public class TestInsert { |
04 | private Logger logger = LoggerFactory.getLogger(TestInsert. class ); |
06 | private BatchInsertService batchInsertService; |
11 | long startTime = System.currentTimeMillis(); |
12 | User user = new User(); |
13 | user.setName( "User_" +( int )(Math.random()* 100 )); |
14 | user.setAge(( int )(Math.random()* 100 )); |
16 | CustInfo info = new CustInfo(); |
17 | info.setPhone( "123456789" +( int )(Math.random()* 100 )); |
18 | batchInsertService.insert(user,info); |
20 | long endTime = System.currentTimeMillis(); |
21 | logger.info( "共耗時:{}毫秒" ,endTime -startTime); |
BatchInsertService.java
02 | public class BatchInsertService { |
03 | private Logger logger = LoggerFactory.getLogger(BatchInsertService. class ); |
05 | private UserService userService; |
07 | private CustInfoService custInfoService; |
08 | @Transactional (rollbackFor= {Exception. class ,RuntimeException. class }) |
09 | public void insert(User user,CustInfo custInfo) { |
10 | int insertUser = userService.insert(user); |
11 | logger.info( "insertUser={}" ,insertUser); |
12 | int insertCustInfo = custInfoService.insert(custInfo); |
13 | logger.info( "insertCustInfo={}" ,insertCustInfo); |
UserService.java
02 | public class UserService { |
04 | private UserMapper userMapper; |
06 | public int insert(User record) { |
07 | int result = userMapper.insert(record); |
CustInfoService.java
02 | public class CustInfoService { |
04 | private CustInfoMapper custInfoMapper; |
06 | public int insert(CustInfo record) { |
07 | int result = custInfoMapper.insert(record); |
08 | long now = System.currentTimeMillis(); |
11 | throw new RuntimeException( "CustInfoMapper throws test insert exception" ); |
Mapper和Bean等就不列出來了,完成的示例工程在github: https://github.com/fenglibin/DruidWithAtomikos
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。