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

03-06 7259阅读 0评论

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

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

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

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

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

文章目录

    • 1. 使用实例
    • 2. 首先分别配置两个数据库的数据源和事务管理器。
    • 3. 使用自定义注解
    • 4. 事务切面方法,多数据源事务的实现(**重点**)
    • 5. 使用事务注解,将两个数据源的事务管理器名字作为参数传入。
    • 6. 提交请求后会发现控制台报错,但是数据库里面并没有插入数据。

      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 dataSourceTransactionManagerStack = new Stack();
                	//存放事务的状态,每一个DataSourceTransactionManager 对应一个 TransactionStatus
                  Stack 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 dataSourceTransactionManagerStack,
                                              Stack 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 dataSourceTransactionManagerStack,
                                  Stack transactionStatuStack) {
                	// 循环,直到事务管理器栈为空
                  while (!dataSourceTransactionManagerStack.isEmpty()) {
                    // 从事务管理器栈和事务状态栈中分别弹出当前的事务管理器和事务状态
                    // 提交当前事务状态
                      dataSourceTransactionManagerStack.pop()
                        .commit(transactionStatuStack.pop());
                  }
              }
              /**
               * 回滚处理方法
               * @param dataSourceTransactionManagerStack
               * @param transactionStatuStack
               */
              private void rollback(Stack dataSourceTransactionManagerStack,
                                    Stack 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. 提交请求后会发现控制台报错,但是数据库里面并没有插入数据。

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

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


免责声明
1、本网站属于个人的非赢利性网站,转载的文章遵循原作者的版权声明。
2、本网站转载文章仅为传播更多信息之目的,凡在本网站出现的信息,均仅供参考。本网站将尽力确保所
提供信息的准确性及可靠性,但不保证信息的正确性和完整性,且不对因信息的不正确或遗漏导致的任何
损失或损害承担责任。
3、任何透过本网站网页而链接及得到的资讯、产品及服务,本网站概不负责,亦不负任何法律责任。
4、本网站所刊发、转载的文章,其版权均归原作者所有,如其他媒体、网站或个人从本网下载使用,请在
转载有关文章时务必尊重该文章的著作权,保留本网注明的“稿件来源”,并白负版权等法律责任。

手机扫描二维码访问

文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。

发表评论

快捷回复: 表情:
评论列表 (暂无评论,7259人围观)

还没有评论,来说两句吧...

目录[+]