多数据源事务处理-涉及分布式事务

news2025/1/24 17:36:05

一. 数据源跨库但是不跨 MySql 实例

这个形式就是数据源在同一个 MySQL 下,但是 jdbc-url 上的数据库配置不同,涉及多个数据库时,如果方法中发生异常,只有开启事务的数据源会发生回滚,其他数据源不会回滚。看到这里可能有点迷惑,什么是 只有开启事务的数据源会发生回滚,其他数据源不会回滚?

下面给出代码验证:

主数据源配置

@Slf4j
@EnableTransactionManagement
@EnableAspectJAutoProxy
@Configuration
@MapperScan(basePackages = "ltd.newbee.mall.core.dao", sqlSessionFactoryRef = "masterSqlSessionFactory")
public class Db1DataSourceConfig {

    @Primary
    @Bean
    @ConfigurationProperties("spring.datasource.druid.master")
    public DataSource masterDataSource(DruidProperties druidProperties) {
        DruidDataSource build = DruidDataSourceBuilder.create().build();
        return druidProperties.dataSource(build);
    }

    /**
     * @param datasource 数据源
     * @return SqlSessionFactory
     * @Primary 默认SqlSessionFactory
     */
    @Primary
    @Bean(name = "masterSqlSessionFactory")
    public SqlSessionFactory masterSqlSessionFactory(@Qualifier("masterDataSource") DataSource datasource,
                                                     Interceptor interceptor) throws Exception {
        MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
        bean.setDataSource(datasource);
        // mybatis扫描xml所在位置
        bean.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath*:mapper/*.xml"));
        bean.setTypeAliasesPackage("ltd.**.core.entity");
        bean.setPlugins(interceptor);
        GlobalConfig globalConfig = new GlobalConfig();
        GlobalConfig.DbConfig dbConfig = new GlobalConfig.DbConfig();
        dbConfig.setLogicDeleteField("isDeleted");
        dbConfig.setLogicDeleteValue("1");
        dbConfig.setLogicNotDeleteValue("0");
        globalConfig.setDbConfig(dbConfig);
        bean.setGlobalConfig(globalConfig);
        log.info("masterDataSource 配置成功");
        return bean.getObject();
    }

    @Primary
    @Bean(name = "masterTransactionManager")
    public DataSourceTransactionManager masterTransactionManager(@Qualifier("masterDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

}
复制代码

从数据源配置

@Slf4j
@ConditionalOnProperty(value = "transactional.mode", havingValue = "seata")
@EnableTransactionManagement
@EnableAspectJAutoProxy
@Configuration
@MapperScan(basePackages = "ltd.newbee.mall.slave.dao", sqlSessionFactoryRef = "slaveSqlSessionFactory")
public class Db2DataSourceConfig {

    @Bean
    @ConfigurationProperties("spring.datasource.druid.slave")
    public DataSource slaveDataSource(DruidProperties druidProperties) {
        DruidDataSource build = DruidDataSourceBuilder.create().build();
        return druidProperties.dataSource(build);
    }


    /**
     * @param datasource 数据源
     * @return SqlSessionFactory
     * @Primary 默认SqlSessionFactory
     */
    @Bean(name = "slaveSqlSessionFactory")
    public SqlSessionFactory slaveSqlSessionFactory(@Qualifier("slaveDataSource") DataSource datasource,
                                                    Interceptor interceptor) throws Exception {
        MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
        bean.setDataSource(datasource);
        // mybatis扫描xml所在位置
        bean.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath*:slavemapper/*.xml"));
        bean.setTypeAliasesPackage("ltd.**.slave.entity");
        bean.setPlugins(interceptor);
        GlobalConfig globalConfig = new GlobalConfig();
        GlobalConfig.DbConfig dbConfig = new GlobalConfig.DbConfig();
        dbConfig.setLogicDeleteField("isDeleted");
        dbConfig.setLogicDeleteValue("1");
        dbConfig.setLogicNotDeleteValue("0");
        globalConfig.setDbConfig(dbConfig);
        bean.setGlobalConfig(globalConfig);
        log.info("slaveDataSource 配置成功");
        return bean.getObject();
    }
    
    @Bean(name = "slaveTransactionManager")
    public DataSourceTransactionManager slaveTransactionManager(@Qualifier("slaveDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

}
复制代码

划重点-上述代码在每个数据源中都配置了 DataSourceTransactionManager(事务管理器),并且在主配置中添加 @Primary 注解,表示默认事务管理器优先使用主数据源的事务管理器。 下面给出测试代码:

/**
 *  Springboot测试类
 */
@Slf4j
@SpringBootTest
@RunWith(SpringRunner.class)
public class MultiDataSourceTest {
    @Autowired
    private MultiDataService multiDataService;
    @Test
    public void testRollback() {
        multiDataService.testRollback();
    }
}
/**
 *  MultiDataService实现类
 */
@Slf4j
@Service
public class MultiDataServiceImpl implements MultiDataService {
    @Autowired
    private TbTable1Service tbTable1Service;
    @Autowired
    private TbTable2Service tbTable2Service;
    @Autowired
    private PlatformTransactionManager transactionManager;
    @Override
    public void testRollback() {
        DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
        TransactionStatus transaction = transactionManager.getTransaction(transactionDefinition);
        try {
            TbTable1 tbTable1 = new TbTable1();
            tbTable1.setName("test1");
            // 插入table1表
            boolean save1 = tbTable1Service.save(tbTable1);
            TbTable2 tbTable2 = new TbTable2();
            tbTable2.setName("test2");
            // 插入table2表
            boolean save2 = tbTable2Service.save(tbTable2);
            int i = 1 / 0;
            transactionManager.commit(transaction);
            Assert.isTrue(save1 && save2);
        } catch (Exception e) {
            log.info(e.getMessage(), e);
            transactionManager.rollback(transaction);
        }
    }
}
复制代码

执行结果:table1表回滚成功,table2表回滚失败。由此结果,对于 只有开启事务的数据源会发生回滚,其他数据源不会回滚? 我们的解释就是 Spring 中默认使用的事务管理器是使用主数据源配置还是从数据源配置由我们通过 @Primary 决定,当我们把 @Primary 切换在从数据源配置上,执行结果:table2表回滚成功,table1表回滚失败。那怎么解决这个问题?

当涉及到跨库或者跨 MySQL 实例,想要保证事务操作,我们这里先给出XA事务解决方案。附 XA 事务的说明:

XA 是由 X/Open 组织提出的分布式事务规范,XA 规范主要定义了事务协调者(Transaction Manager)和资源管理器(Resource Manager)之间的接口。

事务协调者(Transaction Manager),因为 XA 事务是基于两阶段提交协议的,所以需要有一个协调者,来保证所有的事务参与者都完成了准备工作,也就是 2PC 的第一阶段。如果事务协调者收到所有参与者都准备好的消息,就会通知所有的事务都可以提交,也就是 2PC 的第二阶段。

资源管理器(Resource Manager),负责控制和管理实际资源,比如数据库。

(划重点)XA 的 MySQL 实现使 MySQL 服务器能够充当资源管理器,在全局事务中处理 XA 事务。连接到 MySQL 服务器的客户端程序充当事务协调者

XA 事务的执行流程

XA 事务是两阶段提交的一种实现方式,根据 2PC 的规范,XA 将一次事务分割成了两个阶段,即 Prepare 和 Commit 阶段。

Prepare 阶段,TM 向所有 RM 发送 prepare 指令,RM 接受到指令后,执行数据修改和日志记录等操作,然后返回可以提交或者不提交的消息给 TM。如果事务协调者 TM 收到所有参与者都准备好的消息,会通知所有的事务提交,然后进入第二阶段。

Commit 阶段,TM 接受到所有 RM 的 prepare 结果,如果有 RM 返回是不可提交或者超时,那么向所有 RM 发送 Rollback 命令;如果所有 RM 都返回可以提交,那么向所有 RM 发送 Commit 命令,完成一次事务操作。

下面给出两种基于 XA 事务的解决方案:

  • Springboot 项目中可以使用 jta,完成对 XA 协议的支持,缺点就是 jta 需要改造数据源配置
  • Springboot 项目引入 seataseata 支持 XA 协议,且引入 seata-spring-boot-starter 依赖对业务无侵入,缺点需要引入 seata-server 降低了系统可用性

Springboot 项目中可以启用 jta

  1. 引入 spring-boot-starter-jta-atomikos
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
复制代码
  1. 修改主从数据源 DataSource 配置,进行包装添加 XA 数据源支持,如下;

    @Primary
    @Bean
    @ConfigurationProperties("spring.datasource.druid.master")
    public DataSource dataSource(DruidProperties druidProperties) {
        DruidXADataSource dataSource = druidProperties.dataSource(new DruidXADataSource());
        dataSource.setUrl("jdbc:mysql://localhost:3306/xxx?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8");
        dataSource.setUsername("root");
        dataSource.setPassword("");
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();
        atomikosDataSourceBean.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
        atomikosDataSourceBean.setUniqueResourceName("master-xa");
        atomikosDataSourceBean.setXaDataSource(dataSource);
        return atomikosDataSourceBean;
    }
复制代码
  1. 添加 JtaTransactionManager
@Bean
public JtaTransactionManager transactionManager() throws Exception {
    JtaTransactionManager transactionManager = new JtaTransactionManager();
    UserTransactionManager userTransactionManager = new UserTransactionManager();
    userTransactionManager.setForceShutdown(true);
    userTransactionManager.setTransactionTimeout(3000);
    transactionManager.setUserTransaction(userTransactionManager);
    transactionManager.setAllowCustomIsolationLevels(true);
    return transactionManager;
}
复制代码
  1. 完成测试,代码如下:
/**
 *  Springboot测试类
 */
@Slf4j
@SpringBootTest
@RunWith(SpringRunner.class)
public class MultiDataSourceTest {
    @Autowired
    private MultiDataService multiDataService;
    @Test
    public void jtaTestRollback() {
        multiDataService.jtaTestRollback();
    }
}
/**
 *  MultiDataService实现类
 */
@Slf4j
@Service
public class MultiDataServiceImpl implements MultiDataService {
    @Autowired
    private TbTable1Service tbTable1Service;
    @Autowired
    private TbTable2Service tbTable2Service;
    @Autowired
    private JtaTransactionManager jtaTransactionManager;
    @Override
    public void jtaTestRollback() {
        DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
        TransactionStatus transaction = jtaTransactionManager.getTransaction(transactionDefinition);
        try {
            TbTable1 tbTable1 = new TbTable1();
            tbTable1.setName("test1");
            boolean save1 = tbTable1Service.save(tbTable1);
            TbTable2 tbTable2 = new TbTable2();
            tbTable2.setName("test2");
            boolean save2 = tbTable2Service.save(tbTable2);
            int i = 1 / 0;
            jtaTransactionManager.commit(transaction);
            Assert.isTrue(save1 && save2);
        } catch (Exception e) {
            log.info(e.getMessage(), e);
            jtaTransactionManager.rollback(transaction);
        }
    }
}
复制代码

可以看到我们使用的是 JtaTransactionManager, 执行结果:table1表回滚成功,table2表回滚成功。验证OK

引入 seata,添加XA协议支持

  1. 下载安装启动 seata-server,这里给出官网教程:seata.io/zh-cn/docs/…
  2. 在 Springboot中引入seata最新依赖
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>1.5.2</version>
</dependency>
复制代码
  1. 在yml文件中添加 seata 配置
seata:
  config:
    type: file
  registry:
    type: file
  application-id: newbeemall # Seata 应用编号,默认为 ${spring.application.name}
  tx-service-group: newbeemall-group # Seata 事务组编号,用于 TC 集群名
  # 服务配置项,对应 ServiceProperties 类
  service:
    # 虚拟组和分组的映射
    vgroup-mapping:
      newbeemall-group: default
    # 分组和 Seata 服务的映射
    grouplist:
      default: 127.0.0.1:8091
  data-source-proxy-mode: XA
  enabled: true
复制代码
  1. 完成测试,代码如下:
/**
 *  Springboot测试类
 */
@Slf4j
@SpringBootTest
@RunWith(SpringRunner.class)
public class MultiDataSourceTest {
    @Autowired
    private MultiDataService multiDataService;
    @Test
    public void seataTestRollback() {
        multiDataService.seataTestRollback();
    }
}
/**
 *  MultiDataService实现类
 */
@Slf4j
@Service
public class MultiDataServiceImpl implements MultiDataService {
    @Autowired
    private TbTable1Service tbTable1Service;
    @Autowired
    private TbTable2Service tbTable2Service;
    @GlobalTransactional
    @Override
    public void seataTestRollback() {
        log.info("当前 XID: {}", RootContext.getXID());
        TbTable1 tbTable1 = new TbTable1();
        tbTable1.setName("test1");
        boolean save1 = tbTable1Service.save(tbTable1);
        TbTable2 tbTable2 = new TbTable2();
        tbTable2.setName("test2");
        boolean save2 = tbTable2Service.save(tbTable2);
        int i = 1 / 0;
    }
}
复制代码

如上代码,使用 seata 时需要启用 @GlobalTransactional 注解,并且在事务中传递 XIDRootContext.getXID()),执行结果:table1表回滚成功,table2表回滚成功。验证OK

二. 数据源分布在不同 MySql 实例

当数据源分布在不同 MySql 实例时,这时候其实已经进入分布式事务的范畴,由上可知,XA 事务可以解决分布式环境下事务问题,也就是说上述最后两种解决方案都可以解决分布式事务问题,但是实际使用过程中,我们建议使用 seata,理由是他不仅支持 XA 事务还支持 AT、Saga、TCC事务模型。引入 seata 官网介绍

Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。

总结

关于多数据源事务的问题,不管跨不跨库其实都属于分布式事务的问题。推荐使用 seata 解决。

实践代码放在newbeemall项目:github.com/wayn111/new… 分支下

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/105079.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

禅道 删除回收站

回收站的内容只能进入数据库删 如何访问数据库 1、网页登录数据库 禅道数据库管理用的是adminer&#xff0c;但是为了安全&#xff0c;访问adminer的时候需要身份验证&#xff0c;需要运行/opt/zbox/auth/adduser.sh来添加用户(先 cd /opt/zbox/auth/ 然后执行 ./adduser.sh…

线程和进程 / 进程和线程的区别和联系

&#x1f496; 欢迎来阅读子豪的博客&#xff08;JavaEE篇 &#x1f934;&#xff09; &#x1f449; 有什么宝贵的意见或建议可以在留言区留言 &#x1f4bb; 欢迎 素质三连 点赞关注 收藏 &#x1f9d1;‍&#x1f680;码云仓库&#xff1a;补集王子的代码仓库 不要偷走我小…

飞宇医药冲刺创业板:年营收4.56亿 拟募资4.38亿

雷递网 雷建平 12月20日江苏飞宇医药科技股份有限公司&#xff08;简称&#xff1a;“飞宇医药”&#xff09;日前递交招股书&#xff0c;准备在深交所创业板上市。飞宇医药计划募资4.38亿元&#xff0c;其中&#xff0c;1.79亿元用于年产6000吨侧链及6,000吨酰氯扩产项目&…

QT Qmake笔记

文章目录概述QT修改样式qmake概述pro文件常见配置项库引用和库路径指定QT创建动态库和使用&#xff08;重要&#xff09;小例子写动态库用动态库参考资料附录概述 本文记录一些学习QT过程中的笔记。 QT修改样式 styleSheet&#xff1a; background-image:url("/home/o…

大学生转行,毕业一年半,她是如何从工地走向互联网的?

如果专业与工作不匹配&#xff1f;如果觉得现有工作不适合自己&#xff1f;如果想转行&#xff1f;可以看一下她的成长故事。 领英18年数据显示&#xff0c;职场人第一份工作在职时间显著缩短&#xff0c;70后平均超过4年换一次工作&#xff0c;80后是3年半&#xff0c;90后是…

Bitmap64为什么比bitmap32慢

https://www.bilibili.com/video/BV1vU4y1q7KR/?spm_id_from333.788&vd_sourcefa36a95b3c3fa4f32dd400f8cabddeaf 原因跟 RoaringBitmap64 的实现有关&#xff0c;RoaringBitmap64 是由一系列 RoaringBitmap32 表示。实现方式有很多种&#xff0c;一种比较通用的做法用 ma…

【MyBatis】MyBatis Plus的使用

1.Mybatis-Plus 1.1 简介 MyBatis-Plus 是一个 Mybatis 增强版工具&#xff0c;在 MyBatis 上扩充了其他功能没有改变其基本功能&#xff0c;为了简化开发提交效率而存在。 官网文档地址&#xff1a;   https://mp.baomidou.com/guide/ MyBatis-Plus 特性&#xff1a;  …

非科班出身转行IT难吗?好找工作吗?

大家都知道&#xff0c;IT行业是出了名的高薪行业&#xff0c;很多传统行业/专业的小伙伴由于薪资低、就业机会少而有了想转行IT的想法。 他们通常有以下几个问题&#xff1a; 非计算机专业0基础能学会技术吗&#xff1f;非计算机专业如何转行到IT行业&#xff1f;非计算机专…

记录--可视化大屏-用threejs撸一个3d中国地图

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 不想看繁琐步骤的&#xff0c;可以直接去github下载项目&#xff0c;如果可以顺便来个star哈哈 本项目使用vue-cli创建&#xff0c;但不影响使用&#xff0c;主要绘制都已封装成类 1、使用geoJson绘制…

Java并发系列源码分析(四)--StampedLock

简介 ReentrantReadWriteLock是一个悲观的可重入的读写锁,而StampedLock既支持悲观锁也支持乐观锁但不支持锁的重入, 在ReentrantReadWriteLock下如果多个线程同时获取读锁的时候,获取写锁的线程就会被挂起进行等待,在StampedLock乐观锁下如果有线程加了写锁,其它读线程可以获…

英语不好能不能学好python?试试我的方法就知道了

这是本文的目录前言一、交互环境与print输出&#xff08;python编程常用单词&#xff09;二、字符串操作&#xff08;python编程常用单词&#xff09;三、重复\替换\转换\原始字符串&#xff08;python编程常用单词&#xff09;四、去除\查询\计数&#xff08;python编程常用单…

JavaWeb之Servelt学习01

目录 1.Servlet 1.1快速入门 1.2Servlet 执行原理 1.3Servlet执行方法 1.3.1Servlet中的生命周期方法 1.4注解配置 1.5.Servlet体系结构 1.6Servlet相关配置 1.Servlet 概念&#xff1a;运行在服务端的小程序 Servlet就是一个接口&#xff0c;定义了java类被浏览器访问到…

高速高精度半导体运动台设计(二)

高速高精运动平台的性能不仅与运动控制器、伺服驱动相关&#xff0c;也与电机本身的性能密切相关。如图 5-2 所示的运动平台采用了雅科贝思的直线电机&#xff0c;直接驱动负载&#xff0c;刚性高&#xff0c;响应快&#xff0c;同时选用了高性能多轴运动控制卡和 GTHD 系列高性…

【Java 数据结构】树和二叉树

篮球哥温馨提示&#xff1a;编程的同时不要忘记锻炼哦&#xff01;一棵倒立过来的树. 目录 1、什么是树&#xff1f; 1.1 简单认识树 1.2 树的概念 1.3 树的表示形式 2、二叉树 2.1 二叉树的概念 2.2 特殊的二叉树 2.3 二叉树的性质 2.4 二叉树性质相关习题 3、实…

uniapp 多商品sku组件v3版本

如果您正在寻找一款v3版本的多商品sku组件的话&#xff0c;那我想这款组件刚好可以满足各位。 先来简单看一眼效果图: 看起来是不是还不错&#xff0c;如果我告诉你还可以设置主题色会不会显得更加惊艳些&#xff0c;只需要按照数组格式将rgb的颜色传递给组件&#xff0c;…

智慧医疗中人工智能的7大应用|数据标注

从药物研发到预测肾脏疾病&#xff0c;人工智能在智慧医疗领域应用广泛。 人工智能在许多医学领域和专业中的应用正在成为现实。人工智能、机器学习、自然语言处理和深度学习使智慧医疗利益相关者和医疗专业人员能够更快、更准确地明确智慧医疗需求和解决方案&#xff0c;并依…

【WPF绑定2】 ComboBox SelectedValue复杂数据类型绑定

前言 这次绑定是一次非常痛苦的经历&#xff0c;因为SelectedValue总是不能生效&#xff01;我一度怀疑是wpf的Bug。其实还是自己没搞清楚。 在之前的一篇文章中&#xff1a; http://t.csdn.cn/A4W6Ahttp://t.csdn.cn/A4W6A我也写个ComboBox的绑定&#xff0c;但是当时没有指…

css实现两列/三列布局

文章目录css实现两列/三列布局两列布局三列布局css实现两列/三列布局 两列布局 第一种方式&#xff1a;浮动实现 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible"…

点击类化学试剂绿色固体ICG-TCO,取用时保持干燥的环境

一、试剂反应基团&#xff08;Reagent reaction group&#xff09;&#xff1a; 点击类化学试剂包括&#xff1a;DBCO、TCO、Tetrazine、Azide、Alkyne、Auxiliary reagents等。其中TCO又包含了与氨基反应的&#xff0c;与羧基反应的&#xff0c;与荧光素交联的&#xff0c;与…