Spring Boot 分布式事物管理

前言

事务是为了保证数据的一致性而产生的。那么分布式事务,顾名思义,就是我们要保证分布在不同数据库、不同服务器、不同应用之间的数据一致性。

在单体项目下数据是存放在一个数据库上的,采用数据库的事务就能满足我们的要求。
但随着业务的不断扩张,数据的不断增加,单一数据库已经到达了一个瓶颈,因此我们需要对数据库进行分库分表。为了保证数据的一致性,可能需要不同的数据库之间的数据要么同时成功,要么同时失败,不然导致产生一些脏数据,导致数据不一致。

对此,分布式事务就诞生了。

应用场景

在分布式系统中,如,支付系统中,支付成功后需要对买家和卖家同时进行操作,买家减钱,卖家加钱。必须得放在一个事务里执行。不然出现扣钱了,没买着货那就尴尬了哈哈哈哈哈,所以分布式事务这时候就堪大用。还有银行转账等等。

Spring Boot中实现分布式事务

SpringBoot 集成 Atomikos 实现分布式事务

Atomikos 是一个为 Java 平台提供增值服务的开源类事务管理器。

以下是包括在这个开源版本中的一些功能:

  • 全面崩溃 / 重启恢复;
  • 兼容标准的 SUN 公司 JTA API;
  • 嵌套事务;
  • 为 XA 和非 XA 提供内置的 JDBC 适配器。

注释:XA 协议由 Tuxedo 首先提出的,并交给 X/Open 组织,作为资源管理器(数据库)与事务管理器的接口标准。目前,Oracle、Informix、DB2 和 Sybase 等各大数据库厂家都提供对 XA 的支持。XA 协议采用两阶段提交方式来管理分布式事务。XA 接口提供资源管理器与事务管理器之间进行通信的标准接口。XA 协议包括两套函数,以 xa_ 开头的及以 ax_ 开头的。

代码实现

建库

在本地创建两个数据库:test01,test02,并且创建相同的数据库表:

sql脚本

-- ----------------------------
-- Table structure for test_user1
-- ----------------------------
DROP TABLE IF EXISTS `test_user1`;
CREATE TABLE `test_user1`  (`id` int(11) NOT NULL AUTO_INCREMENT,`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,`age` int(11) NULL DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact;SET FOREIGN_KEY_CHECKS = 1;DROP TABLE IF EXISTS `test_user2`;
CREATE TABLE `test_user2`  (`id` int(11) NOT NULL AUTO_INCREMENT,`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,`age` int(11) NULL DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact;SET FOREIGN_KEY_CHECKS = 1;

pom中 添加 Atomikos 依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jta-atomikos</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>1.3.5</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.5</version></dependency>

添加数据库配置


server:port: 8080
spring:redis:host: localhostport: 6379
mysql:datasource:test1:url: jdbc:mysql://localhost:3307/test01?useUnicode=true&characterEncoding=utf-8username: rootpassword: rootminPoolSize: 3maxPoolSize: 25maxLifetime: 20000borrowConnectionTimeout: 30loginTimeout: 30maintenanceInterval: 60maxIdleTime: 60testQuery: select 1test2:url: jdbc:mysql://localhost:3307/test02?useUnicode=true&characterEncoding=utf-8username: rootpassword: rootminPoolSize: 3maxPoolSize: 25maxLifetime: 20000borrowConnectionTimeout: 30loginTimeout: 30maintenanceInterval: 60maxIdleTime: 60testQuery: select 1

数据源配置文件读取

DBConfig1

package com.example.springbootatomikos.config.pojo;import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;/*** @description: 数据源test1* @author: Administrator* @create: 2020-05-02 19:22**/
@ConfigurationProperties(prefix = "mysql.datasource.test1")
@SpringBootConfiguration
public class DBConfig1 {private String url;private String username;private String password;private int minPoolSize;private int maxPoolSize;private int maxLifetime;private int borrowConnectionTimeout;private int loginTimeout;private int maintenanceInterval;private int maxIdleTime;private String testQuery;public String getUrl() {return url;}public void setUrl(String url) {this.url = url;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public int getMinPoolSize() {return minPoolSize;}public void setMinPoolSize(int minPoolSize) {this.minPoolSize = minPoolSize;}public int getMaxPoolSize() {return maxPoolSize;}public void setMaxPoolSize(int maxPoolSize) {this.maxPoolSize = maxPoolSize;}public int getMaxLifetime() {return maxLifetime;}public void setMaxLifetime(int maxLifetime) {this.maxLifetime = maxLifetime;}public int getBorrowConnectionTimeout() {return borrowConnectionTimeout;}public void setBorrowConnectionTimeout(int borrowConnectionTimeout) {this.borrowConnectionTimeout = borrowConnectionTimeout;}public int getLoginTimeout() {return loginTimeout;}public void setLoginTimeout(int loginTimeout) {this.loginTimeout = loginTimeout;}public int getMaintenanceInterval() {return maintenanceInterval;}public void setMaintenanceInterval(int maintenanceInterval) {this.maintenanceInterval = maintenanceInterval;}public int getMaxIdleTime() {return maxIdleTime;}public void setMaxIdleTime(int maxIdleTime) {this.maxIdleTime = maxIdleTime;}public String getTestQuery() {return testQuery;}public void setTestQuery(String testQuery) {this.testQuery = testQuery;}
}

DBConfig2

package com.example.springbootatomikos.config.pojo;import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;/*** @description: 数据源test2* @author: Administrator* @create: 2020-05-02 19:22**/
@ConfigurationProperties(prefix = "mysql.datasource.test2")
@SpringBootConfiguration
public class DBConfig2 {private String url;private String username;private String password;private int minPoolSize;private int maxPoolSize;private int maxLifetime;private int borrowConnectionTimeout;private int loginTimeout;private int maintenanceInterval;private int maxIdleTime;private String testQuery;public String getUrl() {return url;}public void setUrl(String url) {this.url = url;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public int getMinPoolSize() {return minPoolSize;}public void setMinPoolSize(int minPoolSize) {this.minPoolSize = minPoolSize;}public int getMaxPoolSize() {return maxPoolSize;}public void setMaxPoolSize(int maxPoolSize) {this.maxPoolSize = maxPoolSize;}public int getMaxLifetime() {return maxLifetime;}public void setMaxLifetime(int maxLifetime) {this.maxLifetime = maxLifetime;}public int getBorrowConnectionTimeout() {return borrowConnectionTimeout;}public void setBorrowConnectionTimeout(int borrowConnectionTimeout) {this.borrowConnectionTimeout = borrowConnectionTimeout;}public int getLoginTimeout() {return loginTimeout;}public void setLoginTimeout(int loginTimeout) {this.loginTimeout = loginTimeout;}public int getMaintenanceInterval() {return maintenanceInterval;}public void setMaintenanceInterval(int maintenanceInterval) {this.maintenanceInterval = maintenanceInterval;}public int getMaxIdleTime() {return maxIdleTime;}public void setMaxIdleTime(int maxIdleTime) {this.maxIdleTime = maxIdleTime;}public String getTestQuery() {return testQuery;}public void setTestQuery(String testQuery) {this.testQuery = testQuery;}
}

数据源配置

MyBatisConfig1

package com.example.springbootatomikos.config.one;import com.example.springbootatomikos.config.pojo.DBConfig1;
import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;import javax.sql.DataSource;
import java.sql.SQLException;/*** @description: 数据源1配置* @author: Administrator* @create: 2020-05-02 19:25**/
@SpringBootConfiguration
@MapperScan(basePackages = "com.example.springbootatomikos.mapper.one", sqlSessionTemplateRef = "sqlSessionTemplate")
public class MyBatisConfig1 {// 配置数据源@Primary@Bean(name = "dataSource")public DataSource dataSource(DBConfig1 config) throws SQLException {MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();mysqlXaDataSource.setUrl(config.getUrl());mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);mysqlXaDataSource.setPassword(config.getPassword());mysqlXaDataSource.setUser(config.getUsername());mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();xaDataSource.setXaDataSource(mysqlXaDataSource);xaDataSource.setUniqueResourceName("dataSource");xaDataSource.setMinPoolSize(config.getMinPoolSize());xaDataSource.setMaxPoolSize(config.getMaxPoolSize());xaDataSource.setMaxLifetime(config.getMaxLifetime());xaDataSource.setBorrowConnectionTimeout(config.getBorrowConnectionTimeout());xaDataSource.setLoginTimeout(config.getLoginTimeout());xaDataSource.setMaintenanceInterval(config.getMaintenanceInterval());xaDataSource.setMaxIdleTime(config.getMaxIdleTime());xaDataSource.setTestQuery(config.getTestQuery());return xaDataSource;}@Primary@Bean(name = "sqlSessionFactory")public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource)throws Exception {SqlSessionFactoryBean bean = new SqlSessionFactoryBean();bean.setDataSource(dataSource);return bean.getObject();}@Primary@Bean(name = "sqlSessionTemplate")public SqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {return new SqlSessionTemplate(sqlSessionFactory);}
}

项目结构图

MyBatisConfig2

package com.example.springbootatomikos.config.two;import com.example.springbootatomikos.config.pojo.DBConfig2;
import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import org.springframework.context.annotation.Bean;import javax.sql.DataSource;
import java.sql.SQLException;/*** @description: 数据源2配置* @author: Administrator* @create: 2020-05-02 19:26**/
@SpringBootConfiguration
//basePackages 最好分开配置 如果放在同一个文件夹可能会报错
@MapperScan(basePackages = "com.example.springbootatomikos.mapper.two", sqlSessionTemplateRef = "sqlSessionTemplate2")
public class MyBatisConfig2 {// 配置数据源@Bean(name = "dataSource2")public DataSource dataSource(DBConfig2 config) throws SQLException {MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();mysqlXaDataSource.setUrl(config.getUrl());mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);mysqlXaDataSource.setPassword(config.getPassword());mysqlXaDataSource.setUser(config.getUsername());mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();xaDataSource.setXaDataSource(mysqlXaDataSource);xaDataSource.setUniqueResourceName("dataSource2");xaDataSource.setMinPoolSize(config.getMinPoolSize());xaDataSource.setMaxPoolSize(config.getMaxPoolSize());xaDataSource.setMaxLifetime(config.getMaxLifetime());xaDataSource.setBorrowConnectionTimeout(config.getBorrowConnectionTimeout());xaDataSource.setLoginTimeout(config.getLoginTimeout());xaDataSource.setMaintenanceInterval(config.getMaintenanceInterval());xaDataSource.setMaxIdleTime(config.getMaxIdleTime());xaDataSource.setTestQuery(config.getTestQuery());return xaDataSource;}@Bean(name = "sqlSessionFactory2")public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource2") DataSource dataSource)throws Exception {SqlSessionFactoryBean bean = new SqlSessionFactoryBean();bean.setDataSource(dataSource);return bean.getObject();}@Bean(name = "sqlSessionTemplate2")public SqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactory2") SqlSessionFactory sqlSessionFactory) throws Exception {return new SqlSessionTemplate(sqlSessionFactory);}
}

Mapper配置

UserMapper1

@Mapper
public interface UserMapper1 {@Insert("insert into test_user1(name,age) values(#{name},#{age})")void addUser(@Param("name")String name, @Param("age") int age);
}

UserMapper2

@Mapper
public interface UserMapper2 {@Insert("insert into test_user2(name,age) values(#{name},#{age})")void addUser(@Param("name") String name, @Param("age") int age);
}

User类

public class User {private Long id;private String name;private int age;public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}
}

Service类

@Service
public class UserService {@Autowiredprivate UserMapper1 userMapper1;@Autowiredprivate UserMapper2 userMapper2;@Transactionalpublic void addUser(User user)throws Exception{userMapper2.addUser(user.getName(),user.getAge());int a= 1/0;userMapper1.addUser(user.getName(),user.getAge());}
}

启动类和controller


@SpringBootApplication
@RestController
public class SpringbootatomikosApplication {public static void main(String[] args) {SpringApplication.run(SpringbootatomikosApplication.class, args);}@Autowiredprivate UserService userService;@RequestMapping("test")public void test(){User user = new User();user.setName("test");user.setAge(110);try {userService.addUser(user);}catch (Exception e){e.printStackTrace();}}
}

测试

请求 http://127.0.0.1:8080/test
后台异常报错,然后查看两个数据库数据都没有入库表示分布式事务执行成功。

然后将service中的 异常去掉,两个数据库数据入库成功。

Spring Boot 分布式事物管理相关推荐

  1. SpringBoot(2.1.1)本地事物管理和分布式事物管理(五)

    1.SpringBoot整合事物管理 springboot默认集成事物,只主要在方法上加上@Transactional即可 启动类上的@EnableTransactionManagement注解可加可 ...

  2. (39.3) Spring Boot Shiro权限管理【从零开始学Spring Boot】

    在学习此小节之前您可能还需要学习: (39.1) Spring Boot Shiro权限管理[从零开始学Spring Boot] http://412887952-qq-com.iteye.com/b ...

  3. Spring Boot——分布式

    Spring Boot--分布式 一.分布式 1.分布式的基本概念 2.应用架构的发展 二.Dubbo--Zookeeper 1.ZooKeeper(注册中心) 2.Dubbo 3.测试 三.Spri ...

  4. spring boot 分布式session实现

    spring boot 分布式session实现 主要是通过包装 HttpServletRequest 将 session 相关的方法进行代理. 具体是的实现就是通过 SessionRepositor ...

  5. Spring Boot Shiro 权限管理

    Spring Boot Shiro 权限管理 标签: springshiro 2016-01-14 23:44 94587人阅读 评论(60) 收藏 举报 本来是打算接着写关于数据库方面,集成MyBa ...

  6. 39 Spring Boot Shiro权限管理【从零开始学Spring Boot】

    [视频 & 交流平台] à SpringBoot视频 http://study.163.com/course/introduction.htm?courseId=1004329008& ...

  7. 事务 | Spring Cloud 分布式事务管理(二)2pc/3pc

    Spring Cloud 分布式事务管理(二)2pc/3pc 上一篇 Spring Cloud 分布式事务管理 上一章,讲到了微服务带来的优点和缺点以及分布式事务的不确定性.这节说一下2pc/3pc ...

  8. Spring Boot + BPMN流程管理引擎实践

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 Spring Boot + BPMN流程管理引擎实践 前言 一.什么是BPMN? 1. BPMN标准及其核心组件介绍 2. 为什么要使 ...

  9. spring boot分布式文件系统 毕业设计-附源码182251

    摘  要 随着科学技术的飞速发展,各行各业都在努力与现代先进技术接轨,通过科技手段提高自身的优势:对于分布式文件系统当然也不能排除在外,随着网络技术的不断成熟,带动了分布式文件系统,它彻底改变了过去传 ...

最新文章

  1. Numpy 统计变量(平均值、标准差、方差、最大、最小、和、乘积、对角线和)
  2. git 强制更新远程_版本控制管理工具git的常见指令合集
  3. vue - blog开发学习5
  4. enum的介绍以及和#define的区别
  5. Get Requests with Json Data Get Requests with Url Parameters
  6. JAVA入门级教学之(if语句)
  7. android手机游戏开发从入门到精通_unity3d游戏开发如何从入门到精通?
  8. 对于防止按钮重复点击的尝试
  9. 工信部:2015年宽带城市20兆农村4兆
  10. Python 文件读写小结
  11. 模拟集成电路设计(拉扎维)第三章学习笔记
  12. GO语言+区块链视频教程,GO语言+区块链学习线路图(含大纲+视频+资料)
  13. C++动态数组类模板
  14. 【重要】有三AI知识星球不再对外公开!还想加入的有哪些途径?
  15. 值此“程序员节”之际,祭奠那位猝死的程序员兄弟
  16. Intel Me更新
  17. JavaFX Scene Builder 2.0 + IDEA 制作客户端界面
  18. mysql-mmm 故障_mysql-mmm故障解决一例
  19. C#.NET程序设计教程实验指导(清华大学 江红,余青松)实验源码
  20. 深度学习之目标检测(Swin Transformer for Object Detection)

热门文章

  1. 微x怎么设置主题_微信主题怎么设置 微信主题设置方法
  2. 微信气泡主题设置_微信猫和老鼠主题皮肤怎么设置 华为手机设置气泡主题方法...
  3. 【重磅】百度智能运维工程架构
  4. 不写一段代码来获取扇贝单词的接口数据
  5. 什么是机器学习?机器学习与AI的关系?
  6. C# 操作通过word模板合并N个word文档
  7. 微信小程序 收起键盘 wx.hideKeyboard()
  8. 程序员很少上《非诚勿扰》电视节目相亲之分析
  9. 服务最大的并发量是多少?
  10. java i18n utf_Java国际化(i18n)字符串与Unicode转换