应用场景
问题描述
解决方法
多数据源配置
单元测试
第一种方法:最大努力一次提交模式
第二种方法:最大努力一次提交模式 但使用ChainedTransactionManager
ChainedTransactionManager处理流程
第三种方法:最大努力一次提交模式 但使用atomikos
遗留问题
应用场景
现在有个项目,要做数据迁移,要把A库中的 数据迁移到B库,以后新的功能都在B库上开发,两个库都是mysql的。但是很多旧的的项目 还在要使用A库的数据,所以需要一个过渡期,在写B库的同时 也要保证能写到A库。为了保证两个数据库的数据完整性和一致性,只能同时操作两个库,并保证操作的原子性。

问题描述
我们要保证多数据源 操作的原子性,就要使用分布式事物。幸好两个数据源都是同类型 mysql 库。不同类型的如 mysql 和 redis 之前同步要复杂点,这个后续给出解决方法。
使用spring 事物管理机制解决分布式事物问题。可以参考博文:http://www.open-open.com/lib/view/open1429863503010.html#articleHeader8。解决方案主要包括两大类:
(1)XA方式
(2)非XA方式
(3)用消息队列消除分布式事务
使用XA方式效率较低,使用消息队列消除分布式式事务又太过复杂。基于效率 和时间成本考虑,我选用spring的链式事务管理器 非XA方式 ChainedTransactionManager。它是最大努力一阶段提交模式中,一个粗糙的事务管理器实现仅仅是将一系列其他的事务管理器链接在一起,去实现事务同步。倘若业务处理成功,所有的事务将会提交, 否则它们都能回滚。最大努力一次提交模式的安全性不如XA事务但也是相当不错,因此能够承受风险获得较高的吞吐量收益。如果我们将关键业务处理服务设计为一个幕等式 (idempotent),这样发生错误的可能性也很小。

解决方法
多数据源配置
(1)数据源配置文件:

spring.datasource.primary.url=jdbc:mysql://localhost:3306/primary
spring.datasource.primary.username=root
spring.datasource.primary.password=
spring.datasource.primary.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.secondary.url=jdbc:mysql://localhost:3306/secondary
spring.datasource.secondary.username=root
spring.datasource.secondary.password=
spring.datasource.secondary.driver-class-name=com.mysql.jdbc.Driver

(2)spring boot加载数据源
以下配置了两个数据源,但是没有配置事务管理。

@Configuration
public class DataSourceConfig {

@Bean(name = "primaryDataSource")
@Qualifier("primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}

@Bean(name = "secondaryDataSource")
@Qualifier("secondaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}

@Bean(name = "dataSource")
@Qualifier("dataSource")
@Primary
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource dataSource(www.089188.cn/) {
return DataSourceBuilder.create().build();
}

@Bean(name = "primaryJdbcTemplate")
public JdbcTemplate primaryJdbcTemplate(
@Qualifier("primaryDataSource"www.dfgj157.com) DataSource dataSource) {
return new JdbcTemplate(dataSource);
}

@Bean(name = "secondaryJdbcTemplate")
public JdbcTemplate secondaryJdbcTemplate(
@Qualifier("secondaryDataSource") DataSource dataSource) {

(3)测试用例
我没有使用单元测试,因为单元测试里面如果加上了@Transactional 会自动回滚事务,需要在单元测试上面加上 @Rollback(false),但是这样就失去了测试的意义,我们就是要测试事务的原子性,不能手动设置事务的回滚方式。所以单独写了一个 Rest 接口用于调试如下:

@RestController
@RequestMapping(value = "/user")
public class UserController {

@Autowired
PrimaryUserService primaryUserService;

@Autowired
SecondaryUserService secondaryUserService;

@Autowired
@Qualifier("primaryJdbcTemplate")
JdbcTemplate primaryJdbcTemplate;

@Autowired
@Qualifier("secondaryJdbcTemplate")
JdbcTemplate secondaryJdbcTemplate;

@RequestMapping(value =www.leyou1178.cn/ "add/{name}", method = RequestMethod.POST)
@ResponseBody
@Transactional
public String addUser(@PathVariable String name) {
int count = secondaryJdbcTemplate.update("insert into USER(NAME) values(?)", name);
Assert.isTrue(count == 0);//会抛异常
primaryJdbcTemplate.update("insert into USER(NAME) values(?)", name);
//用业务层封装下
//int count = primaryUserService.create(name);
//Assert.isTrue(count == 0);//会抛异常
//secondaryUserService.create(name);

33
单元测试
第一种方法:最大努力一次提交模式
但是不使用ChainedTransactionManager
(1)正常情况没有问题 ,都能写入数据。

@RequestMapping(value = "add/{name}", method = RequestMethod.POST)
@ResponseBody
@Transactional
public String addUser(@PathVariable String name) {
int count = secondaryJdbcTemplate.update("insert into USER(NAME) values(?)", name);
primaryJdbcTemplate.update("insert into USER(NAME) values(?)", name);
return name;

(2)如果出现异常,数据不能保持一致了。如下所示。虽然把他们用spring的事务注解放到了一起,但是当secondaryJdbcTemplate执行完并报错后,primaryJdbcTemplate就无法正常添加数据,但是secondaryJdbcTemplate却并不受影响。

@RequestMapping(value = "add/{name}", method = RequestMethod.POST)
@ResponseBody
@Transactional
public String addUser(@PathVariable String name) {
int count = secondaryJdbcTemplate.update("insert into USER(NAME) values(?)", name);
Assert.isTrue(count ==www.dfgjpt.com 0);//会抛异常
primaryJdbcTemplate.update("insert into USER(NAME) values(?)", name);
return name;

备注:事实上上面两个操作根本不受 @Transactional 注解的影响。因为单元测试里面如果加上了@Transactional 会自动回滚事务,需要在单元测试上面加上 @Rollback(false),但是上面的操作完全没有要回滚的意识。

第二种方法:最大努力一次提交模式 但使用ChainedTransactionManager
首先在spring boot 配置中添加如下内容:

@Bean
@Primary
public ChainedTransactionManager transactionManager(@Qualifier("primaryDataSource") DataSource primaryDataSource,
@Qualifier("secondaryDataSource") DataSource secondaryDataSource) {
DataSourceTransactionManager primaryTransactionManager = new DataSourceTransactionManager(primaryDataSource);
DataSourceTransactionManager secondaryTransactionManager = new DataSourceTransactionManager(
secondaryDataSource);
ChainedTransactionManager chainedTransactionManager = new ChainedTransactionManager(primaryTransactionManager,
secondaryTransactionManager);

(1) 正常情况没有问题 ,都能写入数据。如下:

(2) 如果spring业务层出现异常,数据还能保持一致

@RequestMapping(value = "add/{name}", method = RequestMethod.POST)
@ResponseBody
@Transactional
public String addUser(@PathVariable String name) {
int count = secondaryJdbcTemplate.update("insert into USER(NAME) values(?)", name);
Assert.isTrue(count == 0);//会抛异常
primaryJdbcTemplate.update("insert into USER(NAME) values(?)", name);
//用业务层封装下
//int count = primaryUserService.create(name);
//Assert.isTrue(count ==www.tianzunyule178.com 0);//会抛异常
//secondaryUserService.create(name);
return name;

ChainedTransactionManager处理流程
ChainedTransactionManager只是同时开启两个事务,自动的对两个事务,同时开启,同时提交(或回滚)。它对多个数据写数据,并不是一个原子操作,不能像XA那样保证对数据操作的完整性,一致性。
看源代码:它用一个列表来管理多个数据源的事务管理器。

用MultiTransactionStatus封装多个数据源的事务,统一管理。

逐个Commit 事务的时候,一旦发现某个事务不能commit,立刻回滚后面的事务。

最后尽最大努力回滚事务列表

spring分布式事务控制的更多相关文章

  1. Spring分布式事务实现概览

    分布式事务,一直是实现分布式系统过程中最大的挑战.在只有单个数据源的单服务系统当中,只要这个数据源支持事务,例如大部分关系型数据库,和一些MQ服务,如activeMQ等,我们就可以很容易的实现事务. ...

  2. Spring分布式事务

    [如何实现XA式.非XA式Spring分布式事务] [http://www.importnew.com/15812.html] 在JavaWorld大会上,来自SpringSource的David S ...

  3. 13 Spring 的事务控制

    1.事务的概念 理解事务之前,先讲一个你日常生活中最常干的事:取钱.  比如你去ATM机取1000块钱,大体有两个步骤:首先输入密码金额,银行卡扣掉1000元钱:然后ATM出1000元钱.这两个步骤必 ...

  4. 04 Spring:01.Spring框架简介&&02.程序间耦合&&03.Spring的 IOC 和 DI&&08.面向切面编程 AOP&&10.Spring中事务控制

    spring共四天 第一天:spring框架的概述以及spring中基于XML的IOC配置 第二天:spring中基于注解的IOC和ioc的案例 第三天:spring中的aop和基于XML以及注解的A ...

  5. spring分布式事务学习笔记

    最近项目中使用了分布式事务,本文及接下来两篇文章总结一下在项目中学到的知识. 分布式事务对性能有一定的影响,所以不是最佳的解决方案,能通过设计避免最好尽量避免. 分布式事务(Distributed t ...

  6. 如何实现XA式、非XA式Spring分布式事务

    Spring应用的几种事务处理机制 Java Transaction API和XA协议是Spring常用的分布式事务机制,不过你可以选择选择其他的实现方式.理想的实现取决于你的应用程序使用何种资源,你 ...

  7. 非XA式Spring分布式事务

    Spring应用的几种事务处理机制 Java Transaction API和XA协议是Spring常用的分布式事务机制,不过你可以选择选择其他的实现方式.理想的实现取决于你的应用程序使用何种资源,你 ...

  8. spring分布式事务学习笔记(1)

    此文已由作者夏昀授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 分布式事务对性能有一定的影响,所以不是最佳的解决方案,能通过设计避免最好尽量避免. 分布式事务(Distrib ...

  9. Spring分布式事务实现

    分布式事务是指操作多个数据库之间的事务,spring的org.springframework.transaction.jta.JtaTransactionManager,提供了分布式事务支持.如果使用 ...

随机推荐

  1. 复杂软件的考虑点与UITableView

    对象的要素.组织.整体情况: 对象的生成步骤.生成的时间节点. 考虑UITableview.

  2. 掘金 里面 写文章 带目录的时候 用#(空格)标题 后面用## title,一个页面只有一个H1

    掘金 里面 写文章 带目录的时候 用#(空格)标题 后面用## title,一个页面只有一个H1

  3. 使用Timer组件制作左右飘动的窗体

    实现效果: 知识运用: Form类的Left和Top属性 实现代码: private void timer1_Tick(object sender, EventArgs e) { Rectangle ...

  4. Understanding NFS Caching

    Understanding NFS Caching Filesystem caching is a great tool for improving performance, but it is im ...

  5. 安全错误使用CORS在IE10与Node和Express及XMLHttpRequest: 网络错误 0x4c7, 操作已被用户取消

    在IE下:VUE项目,后台替换为https请求之后,vue热更新请求挂起,控制台报错:XMLHttpRequest: 网络错误 0x4c7, 操作已被用户取消.但是chrome与Firefox正常 当 ...

  6. SC || Chapter 1

    第一章的重中之重就是这张图吧 (具体参见笔记) ┉┉∞ ∞┉┉┉┉∞ ∞┉┉┉∞ ∞┉┉┉┉∞ ∞┉┉┉┉∞ ∞┉┉┉∞ ∞┉┉┉┉∞ ∞┉┉┉┉∞ ∞┉┉┉∞ ∞┉┉ 区分哪些属性是外部的(面向用户 ...

  7. 使用jquery.ajax实现省市的二级联动(SSH架构)

    首先实现jquery ajax的二级联动 要下载个jquery.js 我在这里就不准备了 自行百度下载 背景介绍:通过部门的ID来查找部门下的所有班级 我实现二级联动的思路是:先查询所有部门 显示在页 ...

  8. 数组char a[4] 保存了一个数据,怎么转换为unsigned int呢 ?

    [待解决问题] 浏览: 701次 注意char并不表示字符的 a[0]=0; a[1]=0; a[2]=3; a[3]=0; 那么我要的unsigned int b应该等于: b= 0x0000030 ...

  9. cocos2dx 通过jni调用安卓底层方法

    cocos2dx通过封装JniHelper类来调用安卓api底层函数,该文件在cocos/platform/android/jni/JniHelper.h,使用方法如下: 打开eclipse,导入co ...

  10. scipy应用积分操作

    1.什么是scipy? SciPy是一款方便.易于使用.专为科学和工程设计的Python工具包.它包括统计,优化,整合,线性代数模块,傅里叶变换,信号和图像处理,常微分方程求解器等等. integra ...