应用场景
问题描述
解决方法
多数据源配置
单元测试
第一种方法:最大努力一次提交模式
第二种方法:最大努力一次提交模式 但使用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. JAVA小游戏之两个物体碰撞产生的碰撞检测

    首先必须了解两个物体,在移动时,会有怎样的效果,比如沪我们小时候耍过的坦克大战.看起来很简单,但是写起代码来,复杂的要多: 下面举个例子: // 构造一个新的 Rectangle,其左上角的坐标为 ( ...

  2. 1066: 输入n个数和输出调整后的n个数

    1066: 输入n个数和输出调整后的n个数 Time Limit: 1 Sec  Memory Limit: 128 MBSubmit: 2739  Solved: 1578[Submit][Stat ...

  3. websocket+订阅发布者模式模拟实现股票价格实时刷新

    1.新建文件夹 2.文件夹中新建index.html 和 index.js index.html <!DOCTYPE html> <html lang="en"& ...

  4. 【转载】Alpha、Beta、RC、GA版本的区别

    转自:http://www.blogjava.net/RomulusW/archive/2008/05/04/197985.html Alpha:是内部测试版,一般不向外部发布,会有很多Bug.一般只 ...

  5. Ajax请求出现406的原因

    一般出现406错误有两种可能: 1.如果后缀是html是不能响应json数据的.需要修改后缀名. 在做伪静态化过程中,以.html结尾的后缀,做post请求时,不能响应json格式,这是spring官 ...

  6. Python基础教程2-3:以正确的宽度在居中的“盒子”内打印一个句子

    代码示例:#获取句子长度sentence = input('Plese input a sentence:')#He's very naughty boyscreen_width =100#获取文本的 ...

  7. Linux-缓存服务

    Memcached 基本操作 解释 命令 安装 yum install memcached 启动 memcached -d -l -m -p 停止 kill pid 查看某个端口是否通:telnet ...

  8. 【jenkins】jenkins执行nohup java报错

    nohup:failed to run command 'java':No such file or directory 这是因为jenkins只认绝对路径.在shell里面有涉及到文件的都应该写成绝 ...

  9. 序列内置方法详解(string/list/tuple)

    一.常用方法集合 1.1.string,字符串常用方法 以下举例是python2.7测试: 函数名称 作用 举例 str.capitalize() 字符串第一个字符如果是字母,则把字母替换为大写字母. ...

  10. python面试题之什么是lambda函数?

    lambda表达式,通常是在需要一个函数,但是又不想费神去命名一个函数的场合下使用,也就是指匿名函数. lambda所表示的匿名函数的内容应该是很简单的,如果复杂的话,干脆就重新定义一个函数了,使用l ...