应用场景
问题描述
解决方法
多数据源配置
单元测试
第一种方法:最大努力一次提交模式
第二种方法:最大努力一次提交模式 但使用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. CF Gym 100637J Superfactorial numeral system (构造)

    题意:给一个式子,ak,k>2时,0<=ak<k:ai都是整数,给你p,q让你求一组ak. 题解:构造,每次除掉q取整得到ai,然后减一减 #include<cstdio> ...

  2. TextView中使用Linkify添加超链接

       首先,在TextView所属xml配置文件中,直接添加android:autoLink特性即可,它支持一个或多个(用分割线)自定义的值:none.web.email.phone或all. 另外, ...

  3. ssh整合思想 Spring分模块开发 crud参数传递 解决HTTP Status 500 - Write operations are not allowed in read-only mode (FlushMode.MANUAL): Turn your Session into FlushMode.COMMIT/AUTO or(增加事务)

    在Spring核心配置文件中没有增加事务方法,导致以上问题 Action类UserAction package com.swift.action; import com.opensymphony.xw ...

  4. 关于UINavigationController的一些技巧

    未自定义任何东西的导航条效果如下: 1.自定义了 leftBarButtonItem 之后,左滑返回手势失效了,解决办法: self.navigationController.interactiveP ...

  5. 洛谷 P3328 【[SDOI2015]音质检测】

    这题我做的好麻烦啊... 一开始想分块来着,后来发现可以直接线段树 首先考虑一个性质,我们如果有数列的相邻两项f[i]和 f[i+1]那么用这两项向后推k项其线性表示系数一定(表示为f[i+k]=a∗ ...

  6. ccf 201803-4 棋局评估(Python实现)

    一.原题 问题描述 试题编号: 201803-4 试题名称: 棋局评估 时间限制: 1.0s 内存限制: 256.0MB 问题描述: 问题描述 Alice和Bob正在玩井字棋游戏. 井字棋游戏的规则很 ...

  7. 封装,封装的原理,Property ,setter ,deleter,多态,内置函数 ,__str__ , __del__,反射,动态导入模块

    1,封装 ## 什么是封装 what 对外隐藏内部的属性,以及实现细节,并给外部提供使用的接口 学习封装的目的:就是为了能够限制外界对内部数据的方法 注意 :封装有隐藏的意思,但不是单纯的隐藏 pyt ...

  8. 这一千个Python库,总有你想要的!

    环境管理 管理 Python 版本和环境的工具 p – 非常简单的交互式 python 版本管理工具. pyenv – 简单的 Python 版本管理工具. Vex – 可以在虚拟环境中执行命令. v ...

  9. JdbcTemplate实验

    实验1:测试数据源 @Test public void test() throws SQLException { ApplicationContext ioc = new ClassPathXmlAp ...

  10. Liunx将私密代理添加到环境变量

    .bash_profile文件存在于用户主目录下,绝对路径为/home/$name/.bash_profile.bash_profile文件是隐藏文件,里面包含的是用户的用户的环境变量. 注意: 这个 ...