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

本文介绍了在SpringBoot和MyBatis中处理同时操作两个数据库的复杂场景,通过自定义注解和事务切面实现多数据源事务的精确控制,确保数据一致性。
摘要由CSDN通过智能技术生成

在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. 提交请求后会发现控制台报错,但是数据库里面并没有插入数据。

在这里插入图片描述
在这里插入图片描述

  • 15
    点赞
  • 51
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
要在Spring Boot中配置多数据源使用mybatis-plus,你可以按照以下步骤进行操作: 1. 首先,在`application.properties`(或`application.yml`)文件中配置你的数据源信息。假设你有两个数据源,分别为`datasource1`和`datasource2`,你可以在配置文件中添加以下内容: ```properties # 数据源1 spring.datasource.datasource1.url=jdbc:mysql://localhost:3306/db1 spring.datasource.datasource1.username=username1 spring.datasource.datasource1.password=password1 spring.datasource.datasource1.driver-class-name=com.mysql.jdbc.Driver # 数据源2 spring.datasource.datasource2.url=jdbc:mysql://localhost:3306/db2 spring.datasource.datasource2.username=username2 spring.datasource.datasource2.password=password2 spring.datasource.datasource2.driver-class-name=com.mysql.jdbc.Driver ``` 2. 创建两个数据源的配置类,用于配置和管理数据源。例如,创建`DataSource1Config`和`DataSource2Config`类,并在类上添加注解`@Configuration`。 ```java @Configuration public class DataSource1Config { @Bean(name = "dataSource1") @ConfigurationProperties(prefix = "spring.datasource.datasource1") public DataSource dataSource1() { return DataSourceBuilder.create().build(); } } @Configuration public class DataSource2Config { @Bean(name = "dataSource2") @ConfigurationProperties(prefix = "spring.datasource.datasource2") public DataSource dataSource2() { return DataSourceBuilder.create().build(); } } ``` 3. 创建两个`SqlSessionFactory`,分别指定对应的数据源。 ```java @Configuration @MapperScan(basePackages = "com.example.mapper1", sqlSessionFactoryRef = "sqlSessionFactory1") public class MybatisPlusConfig1 { @Autowired @Qualifier("dataSource1") private DataSource dataSource1; @Bean(name = "sqlSessionFactory1") public SqlSessionFactory sqlSessionFactory1() throws Exception { MybatisSqlSessionFactoryBean factoryBean = new MybatisSqlSessionFactoryBean(); factoryBean.setDataSource(dataSource1); return factoryBean.getObject(); } } @Configuration @MapperScan(basePackages = "com.example.mapper2", sqlSessionFactoryRef = "sqlSessionFactory2") public class MybatisPlusConfig2 { @Autowired @Qualifier("dataSource2") private DataSource dataSource2; @Bean(name = "sqlSessionFactory2") public SqlSessionFactory sqlSessionFactory2() throws Exception { MybatisSqlSessionFactoryBean factoryBean = new MybatisSqlSessionFactoryBean(); factoryBean.setDataSource(dataSource2); return factoryBean.getObject(); } } ``` 4. 创建两个事务管理器,分别指定对应的数据源。 ```java @Configuration public class TransactionManager1Config { @Autowired @Qualifier("dataSource1") private DataSource dataSource1; @Bean(name = "transactionManager1") public DataSourceTransactionManager transactionManager1() { return new DataSourceTransactionManager(dataSource1); } } @Configuration public class TransactionManager2Config { @Autowired @Qualifier("dataSource2") private DataSource dataSource2; @Bean(name = "transactionManager2") public DataSourceTransactionManager transactionManager2() { return new DataSourceTransactionManager(dataSource2); } } ``` 5. 最后,你可以在你的Mapper接口上使用`@Qualifier`注解指定使用哪个数据源。 ```java @Mapper public interface UserMapper1 { @Select("SELECT * FROM user") @Qualifier("sqlSessionFactory1") List<User> findAll(); } @Mapper public interface UserMapper2 { @Select("SELECT * FROM user") @Qualifier("sqlSessionFactory2") List<User> findAll(); } ``` 这样,你就成功配置了多数据源mybatis-plus。请根据实际需求修改配置和代码。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值