在之前的文章"如何合理的使用动态数据源"中,其实也提到了分布式事务相关的场景如:利用多数据源实现读写分离,但直接使用动态数据源频繁其实是很消耗资源的,而且就是当业务service一个方法中的业务涉及到多数据源来回操作的时候会存在没法保证事务的ACID,基于多数据源这个事务问题,找到了一个比较好的解决方案,能进行分布式的处理,还能保住事务的ACID,首先我们先了解一下什么事务?事务的四大特性分别都是什么意思?
事务:是一组SQL组成的"逻辑处理单元"。
原子性(Atomicity):原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚。
一致性(Consistency):一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
隔离性(Isolation):隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
持久性(Durability):持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
1. 那什么是分布式事务?
其实简单的理解就是为了保证"不同数据库的数据的一致性"。
2. 分布式事务产生的场景"数据库的分库分表"和"SOA服务化"。
3. 那要想做到做的分布式事务,我们应当怎么做了,最好的办法我们先去了解什么是XA协议。
个人对上面文字理解如下(不一定对,只是个人的理解):
正确理解如下:
3. XA模式的优缺点:
优点:简单,使用分布式成本低。
缺点:性能不理想,XA无法满足高并发的场景,许多Nosql是不支持XA协议的。
4. 其实除了XA这种解决分布式事务的方法外,还有如下的解决方案,其他的解决方案等有时间再整理。
解决方案:XA,可靠性消息模式,TCC,补偿模式
5. 重点来了,就是Atomikos了,Atomikos是一个为Java平台提供的开源事务管理器。
主要功能:全面奔溃/重启恢复,嵌套事务,为XA和非XA提供内置的JDBC适配器,是标准SUM公司的JTA API的实现。
6. 既然都已经介绍了Atomikos强大功能了,那我们一起搭建一个基于SSM框架的测试代码,测试这个Atomikos的功能是不是都是吹的,核心配置如下:
pom.xml 配置如下:
<!-- 第一步:实现了XA协议的开源第三方支持包 -->
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>atomikos-util</artifactId>
<version>3.7.0</version>
</dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions-jta</artifactId>
<version>3.7.0</version>
</dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions</artifactId>
<version>3.7.0</version>
</dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions-api</artifactId>
<version>3.7.0</version>
</dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions-jdbc</artifactId>
<version>3.7.0</version>
</dependency>
<dependency>
<groupId>org.codehaus.btm</groupId>
<artifactId>btm</artifactId>
<version>2.1.4</version>
</dependency>
<!-- 第一步:实现了XA协议的开源第三方支持包 -->
config.properties 配置如下:
# =====================本地环境=====================
his.db.url=jdbc:mysql://localhost:3306/his?useUnicode=true&characterEncoding=UTF-8
his.db.user=root
his.db.password=root
hispay.db.url=jdbc:mysql://localhost:3306/his_pay?useUnicode=true&characterEncoding=UTF-8
hispay.db.user=root
hispay.db.password=root
# =====================本地环境=====================
jta.properties 配置如下:
com.atomikos.icatch.service=com.atomikos.icatch.standalone.UserTransactionServiceFactory
com.atomikos.icatch.console_file_name = tm.out
com.atomikos.icatch.log_base_name = tmlog
com.atomikos.icatch.tm_unique_name = com.atomikos.spring.jdbc.tm
com.atomikos.icatch.console_log_level = INFO
# out log
com.atomikos.icatch.output_dir=/hello/atomikos
com.atomikos.icatch.log_base_dir=/hello/atomikos
com.atomikos.icatch.serial_jta_transactions=false
spring-dispatcher.xml 核心配置如下:
<context:property-placeholder location="classpath:config.properties" />
<bean id="abstractXADataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init"
destroy-method="close" abstract="true">
<property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource"/>
<property name="poolSize" value="10" />
<property name="minPoolSize" value="10"/>
<property name="maxPoolSize" value="30"/>
<property name="borrowConnectionTimeout" value="60"/>
<property name="reapTimeout" value="20"/>
<!-- 最大空闲时间 -->
<property name="maxIdleTime" value="60"/>
<property name="maintenanceInterval" value="60"/>
<property name="loginTimeout" value="60"/>
<property name="testQuery">
<value>select 1</value>
</property>
</bean>
<!-- his数据源 -->
<bean id="hisDataSource" parent="abstractXADataSource">
<!-- value只要两个数据源不同就行,随便取名 -->
<property name="uniqueResourceName" value="mysql/his" />
<property name="xaDataSourceClassName"
value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" />
<property name="xaProperties">
<props>
<prop key="URL">${his.db.url}</prop>
<prop key="user">${his.db.user}</prop>
<prop key="password">${his.db.password}</prop>
<prop key="pinGlobalTxToPhysicalConnection">true</prop>
</props>
</property>
</bean>
<bean id="hispayDataSource" parent="abstractXADataSource">
<!-- value只要两个数据源不同就行,随便取名 -->
<property name="uniqueResourceName" value="mysql/hispay" />
<property name="xaDataSourceClassName"
value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" />
<property name="xaProperties">
<props>
<prop key="URL">${hispay.db.url}</prop>
<prop key="user">${hispay.db.user}</prop>
<prop key="password">${hispay.db.password}</prop>
<prop key="pinGlobalTxToPhysicalConnection">true</prop>
</props>
</property>
</bean>
<bean id="hisSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="hisDataSource" />
<property name="mapperLocations" value="classpath:mybatis/xml/his/*Mapper.xml" />
</bean>
<bean id="hispaySqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="hispayDataSource" />
<property name="mapperLocations" value="classpath:mybatis/xml/hispay/*Mapper.xml" />
</bean>
<bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager"
init-method="init" destroy-method="close">
<!-- atomikosTransactionManager 这个事务管理器可以关闭事务true -->
<property name="forceShutdown">
<value>true</value>
</property>
</bean>
<bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp">
<property name="transactionTimeout" value="300" />
</bean>
<bean id="transactionManager"
class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManager">
<ref bean="atomikosTransactionManager"/>
</property>
<property name="userTransaction">
<ref bean="atomikosUserTransaction"/>
</property>
<!-- 必须设置,否则程序出现异常 JtaTransactionManager does not support custom isolation levels by default -->
<property name="allowCustomIsolationLevels" value="true"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="cn.edu.his.pay.mapper.his"/>
<property name="sqlSessionFactoryBeanName" value="hisSqlSessionFactory" />
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="cn.edu.his.pay.mapper.hispay"/>
<property name="sqlSessionFactoryBeanName" value="hispaySqlSessionFactory" />
</bean>
<!-- 一定要开启注解事务支持 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
总结:到这里Atomikos集成到SSM框架就算完成了,这篇文章就不进行详细的,对于Atomikos(分布式事务管理框架)的特性,我会在下篇文章中写测试代码去分别测试Atomikos的强大功能。