解决多数据源的事务问题 - 基于springboot--mybatis

这篇具有很好参考价值的文章主要介绍了解决多数据源的事务问题 - 基于springboot--mybatis。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

在Spring Boot和MyBatis中,我们有时需要在方法中同时使用两个不同的数据库,但使用@Transactional注解会变得复杂。这时我们可以用一种更灵活的方法来处理。

想象一下这样的场景:我们有两个数据库,我们希望在一个方法中同时操作它们,但是普通的@Transactional注解变得不太适用。

我们可以采用一种类似于“双提交”的策略来解决这个问题。首先,我们让两个数据库执行所需的操作,然后立即提交。接下来,如果整个方法执行成功,我们就提交这两个数据库的事务。但是,如果在方法执行过程中出现了问题,我们会回滚这两个数据库的事务。

简单来说,我们先让两个数据库做好准备,等到方法完成后,如果一切顺利,我们正式确认这两个数据库的操作。如果出现了错误,我们撤销之前的操作,就像玩一个双关游戏一样。

通过这种方法,我们能够更加灵活地在方法中操作多个数据库,而不用被注解的方式束缚。这种方式让事务的控制更加精准,保证了数据的一致性。

1. 使用实例

首先看一下如何使用,下面的方法里有两条sql,分别向两个不同的数据库插入数据,我们在方法上加自定义注解`@MoreTransaction`,里面传入两个事务管理器的beann名称,当有异常时,自定义注解的切面方法拦截到异常,两条插入语句sql都会被回滚。
@MoreTransaction(value = {"transactionManagerOne","transactionManagerTwo"})
public ResultData getDataSourceList(){
  //向第一个数据库插入数据
  int i=userService.addUser(new User().setUserName("数据库1"));
  //故意制造异常,抛出给事务切面
  int a=1/0;
  //向第二个是数据库插入数据
  int k=userService.addUserInfo(new UserInfo().setUserAccount("数据库2"));
  Map map=new HashMap();
  map.put("k",k);
  return ResultData.success(map);
}

2. 首先分别配置两个数据库的数据源和事务管理器。

  • 定义第一个第一个数据源DataSourceOne,定义事务管理器的bean为 transactionManagerOne
/**
 * 数据源1
 */
@Configuration
@MapperScan(basePackages = "com.example.mybatis.mapper",sqlSessionFactoryRef = "sqlSessionFactoryOne")
public class DataSourceConfigOne {

    //配置第一个数据源的事务管理器,定义bean名称为 transactionManagerOne
    @Bean(name = "transactionManagerOne")
    public PlatformTransactionManager transactionManagerOne(@Qualifier("dataSourceOne") DataSource dataSourceOne) {
        return new DataSourceTransactionManager(dataSourceOne);
    }

		//  --- 下面是配置数据源的代码  --
  
  	@Bean(name = "dataSourceOne")
    @Primary// 表示这个数据源是默认数据源
    // 读取application.properties中的配置参数映射成为一个对象,prefix表示参数的前缀
    @ConfigurationProperties(prefix = "spring.datasource.one")
    public DataSource dataSourceOne() {
        return  DataSourceBuilder.create().build();
    }
  
      @Primary
    public SqlSessionTemplate sqlsessiontemplateOne(@Qualifier("sqlsessiontemplateOne") SqlSessionFactory sessionfactory) {
        return new SqlSessionTemplate(sessionfactory);
    }

    @Bean(name = "sqlSessionFactoryOne")
    @Primary
    public SqlSessionFactory sqlSessionFactoryOne(@Qualifier("dataSourceOne") DataSource datasource)throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(datasource);
        bean.setMapperLocations(
                new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
        bean.getObject().getConfiguration().setMapUnderscoreToCamelCase(true);
        return bean.getObject();
    }
}
  • 定义第一个第一个数据源DataSourceTwo,定义事务管理器的bean为 transactionManagerTwo
/**
 * 数据源2
 */
@Configuration
@MapperScan(basePackages = "com.example.mybatis.mapper2",sqlSessionFactoryRef = "sqlSessionFactoryTwo")
public class DataSourceConfigTwo {
  	//配置第一个数据源的事务管理器,定义bean名称为 transactionManagerOne
  	@Bean(name = "transactionManagerTwo")
    public PlatformTransactionManager transactionManagerTwo(@Qualifier("dataSourceTwo") DataSource dataSourceTwo) {
        return new DataSourceTransactionManager(dataSourceTwo);
    }
  
  
  	//  --- 下面是配置数据源的代码  --
    @Bean(name = "dataSourceTwo")
    @ConfigurationProperties(prefix = "spring.datasource.two")
    public DataSource dataSourceTwo() {
        return  DataSourceBuilder.create().build();
    }

    @Bean(name = "sqlSessionFactoryTwo")
    public SqlSessionFactory sqlSessionFactoryTwo(@Qualifier("dataSourceTwo") DataSource datasource)throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(datasource);
        bean.setMapperLocations(
                // 设置mybatis的xml所在位置
                new PathMatchingResourcePatternResolver().getResources("classpath:mapper2/*.xml"));
        bean.getObject().getConfiguration().setMapUnderscoreToCamelCase(true);//下划线-驼峰映射
        return bean.getObject();
    }
    

    public SqlSessionTemplate sqlsessiontemplateTwo(@Qualifier("sqlsessiontemplateTwo") SqlSessionFactory sessionfactory) {
        return new SqlSessionTemplate(sessionfactory);
    }
}

3. 使用自定义注解

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.PARAMETER})
public @interface MoreTransaction {
    String[] value() default {};
}

4. 事务切面方法,多数据源事务的实现(重点)

@Aspect
@Component
public class TransactionAop {
    @Pointcut("@annotation(com.example.mybatis.config.aop.annotation.MoreTransaction)")
    public void MoreTransaction() {
    }

    @Pointcut("execution(* com.example.mybatis.controller.*.*(..))")
    public void excudeController() {
    }

    @Around(value = "MoreTransaction()&&excudeController()&&@annotation(annotation)")
    public Object twiceAsOld(ProceedingJoinPoint thisJoinPoint, MoreTransaction annotation) throws Throwable {
      	//存放事务管理器的栈
        Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack = new Stack<>();
      	//存放事务的状态,每一个DataSourceTransactionManager 对应一个 TransactionStatus
        Stack<TransactionStatus> transactionStatuStack = new Stack<>();

        try {
          	//判断自定义注解@MoreTransaction 是否传入事务管理器的名字,将自定义注解的值对应的事务管理器入栈
            if (!openTransaction(dataSourceTransactionManagerStack, transactionStatuStack, annotation)) {
                return null;
            }
          	//执行业务方法
            Object ret = thisJoinPoint.proceed();
          	//如果没有异常,说明两个sql都执行成功,两个数据源的sql全部提交事务
            commit(dataSourceTransactionManagerStack, transactionStatuStack);
            return ret;
        } catch (Throwable e) {
          	//业务代码发生异常,回滚两个数据源的事务
            rollback(dataSourceTransactionManagerStack, transactionStatuStack);
            log.error(String.format("MultiTransactionalAspect, method:%s-%s occors error:",
                    thisJoinPoint.getTarget().getClass().getSimpleName(), thisJoinPoint.getSignature().getName()), e);
            throw e;
        }
    }

    /**
     * 开启事务处理方法
     *
     * @param dataSourceTransactionManagerStack
     * @param transactionStatuStack
     * @param multiTransactional
     * @return
     */
    private boolean openTransaction(Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack,
                                    Stack<TransactionStatus> transactionStatuStack,MoreTransaction multiTransactional) {
				// 获取需要开启事务的事务管理器名字
        String[] transactionMangerNames = multiTransactional.value();
     	 // 检查是否有需要开启事务的事务管理器名字
        if (ArrayUtils.isEmpty(multiTransactional.value())) {
            return false;
        }
     	 // 遍历事务管理器名字数组,逐个开启事务并将事务状态和管理器存入栈中
        for (String beanName : transactionMangerNames) {
         	 // 从Spring上下文中获取事务管理器
            DataSourceTransactionManager dataSourceTransactionManager =(DataSourceTransactionManager) SpringContextUtil.getBean(beanName);
          	// 创建新的事务状态
            TransactionStatus transactionStatus = dataSourceTransactionManager
                    .getTransaction(new DefaultTransactionDefinition());
         	 // 将事务状态和事务管理器存入对应的栈中
            transactionStatuStack.push(transactionStatus);
            dataSourceTransactionManagerStack.push(dataSourceTransactionManager);
        }
        return true;
    }

    /**
     * 提交处理方法
     *
     * @param dataSourceTransactionManagerStack
     * @param transactionStatuStack
     */
    private void commit(Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack,
                        Stack<TransactionStatus> transactionStatuStack) {
      	// 循环,直到事务管理器栈为空
        while (!dataSourceTransactionManagerStack.isEmpty()) {
          // 从事务管理器栈和事务状态栈中分别弹出当前的事务管理器和事务状态
          // 提交当前事务状态
            dataSourceTransactionManagerStack.pop()
              .commit(transactionStatuStack.pop());
        }
    }

    /**
     * 回滚处理方法
     * @param dataSourceTransactionManagerStack
     * @param transactionStatuStack
     */
    private void rollback(Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack,
                          Stack<TransactionStatus> transactionStatuStack) {
     	 // 循环,直到事务管理器栈为空
        while (!dataSourceTransactionManagerStack.isEmpty()) {
          // 从事务管理器栈和事务状态栈中分别弹出当前的事务管理器和事务状态
          // 回滚当前事务状态
            dataSourceTransactionManagerStack.pop().rollback(transactionStatuStack.pop());
        }
    }
}

5. 使用事务注解,将两个数据源的事务管理器名字作为参数传入。

@MoreTransaction(value = {"transactionManagerOne","transactionManagerTwo"})
public ResultData getDataSourceList(){
  //向第一个数据库插入数据
  int i=userService.addUser(new User().setUserName("数据库1"));
  //故意制造异常,抛出给事务切面
  int a=1/0;
  //向第二个是数据库插入数据
  int k=userService.addUserInfo(new UserInfo().setUserAccount("数据库2"));
  Map map=new HashMap();
  map.put("k",k);
  return ResultData.success(map);
}

6. 提交请求后会发现控制台报错,但是数据库里面并没有插入数据。

多个数据源如何实现事务,spring,spring boot,mybatis,oracle,mysql,数据库,sql,后端
多个数据源如何实现事务,spring,spring boot,mybatis,oracle,mysql,数据库,sql,后端文章来源地址https://www.toymoban.com/news/detail-790917.html

到了这里,关于解决多数据源的事务问题 - 基于springboot--mybatis的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【万字长文】SpringBoot整合Atomikos实现多数据源分布式事务(提供Gitee源码)

    前言:在最近的实际开发的过程中,遇到了在多数据源的情况下要保证原子性的问题,这个问题当时遇到了也是思考了一段时间,后来通过搜集大量资料与学习,最后是采用了分布式事务来解决这个问题,在讲解之前,在我往期的博客提前搭好了一个SpringBoot整合MyBatis搭建M

    2024年02月14日
    浏览(40)
  • CompletableFuture异步编程事务及多数据源配置问题(含gitee源码)

    仓库地址: buxingzhe: 一个多数据源和多线程事务练习项目 小伙伴们在日常编码中经常为了提高程序运行效率采用多线程编程,在不涉及事务的情况下,使用dou.lea大神提供的CompletableFuture异步编程利器,它提供了许多优雅的api,我们可以很方便的进行异步多线程编程,速度杠杠

    2024年02月05日
    浏览(40)
  • Springboot+mybatis-plus+dynamic-datasource+Druid 多数据源 分布式事务

    背景 处理多数据源事务一直是一个复杂而棘手的问题,通常我们有两种主流的解决方法。 第一种是通过Atomikos手动创建多数据源事务,这种方法更适合数据源数量较少,参数配置不复杂,对性能要求不高的项目。然而,这种方法的最大困难在于需要手动配置大量设置,这可能

    2024年02月11日
    浏览(37)
  • [前车之鉴] SpringBoot原生使用Hikari数据连接池升级到动态多数据源的深坑解决方案 & RocketMQ吞掉异常问题排查

    当前业务场景我们使用原生SpringBoot整合Hikari数据源连接池提供服务,但是近期业务迭代需要使用动态多数据源,很自然想到dynamic-source,结果一系列惨案离奇发生。。。 原生SpringBoot整合HikariCp数据源连接池配置【这个是没问题的配置】 而升级后的动态多数据源配置如下:【

    2024年02月01日
    浏览(52)
  • SpringBoot3整合Druid数据源的解决方案

    druid-spring-boot-3-starter目前最新版本是1.2.20,虽然适配了SpringBoot3,但缺少自动装配的配置文件,会导致加载时报加载驱动异常。 需要手动在resources目录下创建 META-INF/spring/ 目录,并且在 META-INF/spring/ 创建 org.springframework.boot.autoconfigure.AutoConfiguration.imports , 文件中添加如下内容

    2024年03月09日
    浏览(105)
  • 基于注解切换、Hikari实现的SpringBoot动态数据源(支持JNDI)

    先说效果,要实现方法级别注解切换当前数据源,不设置注解时走默认数据源,同时支持JNDI源。 Spring框架中存在一个抽象类 AbstractRoutingDataSource ,他是一个可以动态选择当前DataSource的路由类,我们就是要从这里入手,重新实现数据源的切换选择逻辑。然后借助注解和切面,

    2024年02月08日
    浏览(78)
  • 解决WPF绑定数据源,数据更新,UI不更新的问题

    XAML中已经设置 View中已经实现IDisposable接口。 ViewModel中已经实现INotifyPropertyChanged接口方法RaisePropertyChanged。 Model中已经配置TextValue 如果已经正确设置Model与ViewModel的 INotifyPropertyChanged ,且Model中的数据可以执行 RaisePropertyChanged ,但此时UI仍不能更新,请检查View中是否 DataCont

    2024年02月14日
    浏览(37)
  • 千云物流- 多数据源事务管理

    Spring只是个容器,因此它并不做任何事务的具体实现。他只是提供了事务管理的接口PlatformTransactionManager,具体内容由就由各个事务管理器来实现。 DataSourceTransactionManager:位于org.springframework.jdbc.datasource包中,数据源事务管理器,提供对单个javax.sql.DataSource事务管理,用于S

    2024年02月02日
    浏览(37)
  • SpringBoot+MyBatis-Plus多数据源@DS注解失效的解决方法

    引入 dynamic-datasource: application.yml 数据源配置: 详细使用请看 MyBatis-Plus官网 这种场景还是比较常见,比如在一个为master数据源的调用slave数据源就会失效 slave数据源Service方法 mater数据源Service方法调用slave数据源Service方法 这里会出现没有走slave_1,依然还是master数据源 需要在

    2024年01月18日
    浏览(51)
  • Spring Boot多数据源事务@DSTransactional的使用

    Spring Boot 集成com.baomidou,引入dynamic-datasource依赖,实现多数据源,这里说下事务问题: 1、一个方法中使用同一个数据源; 2、一个方法中使用了多个数据源; 这里把dao、service列出来 1、dao层   2、service层  spring boot实现多数据源:Spring Boot集成Druid实现多数据源的两种方式_涛

    2024年02月11日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包