例子(看完请看下方的补充说明)

@Service
@DB("Master")
public class MasterService {
    @Autowired
    UserService userService;
    @Autowired
    BookService bookService;

    /**必须master库方法先执行,才能回滚,达到事务效果*/
    @Transactional(rollbackFor = Exception.class)
    public void upload(ReqDto reqDto){
        userService.save(reqDto);  //@DB("Master")
        bookService.save(reqDto);  //@DB("common")
    }
}

@Service
@DS("common")
public class BookService extends ServiceImpl<BookMapper, Book> {
    @Resource
    private BookMapper bookMapper;
    
    public void save(ReqDto reqDto) {
        bookMapper.save(reqDto);
    }
}

结果调用报错。


冲突原因

使用动态数据源(@DS)时,@Transactional使用不当会照成@DS失效。

  • BookService的save上面加@Transactional,数据源没有切换
  • 开启事务的同时,会从数据库连接池获取数据库连接;
  • 如果内层的service使用@DS切换数据源,只是又做了一层拦截,但是并没有改变整个事务的连接;
  • 在这个事务内的所有数据库操作,都是在事务连接建立之后,所以会产生数据源没有切换的问题;
  • 为了使@DS起作用,必须替换数据库连接,也就是改变事务的传播机智,产生新的事务,获取新的数据库连接;

解决方法

  • 去除MasterService.upload上面的@Transactional,数据源切换正常,虽然可以解决,但是事务无效。
  • BookService的save上面加@Transactional(propagation =Propagation.REQUIRES_NEW),数据源切换,且事务有效。完美解决。它会重新创建新事务,获取新的数据库连接,从而得到@DS的数据源

最终代码如下,只需要修改的是bookService,其他不变

@Service
@DS("common")
public class BookService extends ServiceImpl<BookMapper, Book> {
    @Resource
    private BookMapper bookMapper;
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void save(ReqDto reqDto) {
        bookMapper.save(reqDto);
    }
}


注意

master:userService
common:bookService

  • common数据库的操作,需要在master之后,这样当bookService.save失败,会使得userService回滚;
  • 如果common的操作先,那当userService失败,无法使bookService回滚,这是因为Propagation.REQUIRES_NEW原因,具体可以百度Propagation.REQUIRES_NEW的作用。

会回滚

@Transactional(rollbackFor = Exception.class)
public void upload(ReqDto respDto){
    userService.save(respDto);
    bookService.save(respDto);
}

不会回滚

@Transactional(rollbackFor = Exception.class)
public void upload(ReqDto respDto){
    bookService.save(respDto);
    userService.save(respDto);
}

补充说明

这里要先感谢一下 @huihui-Arenas 的评论。
上方的方式虽然能解决问题,但是也有不好的地方:

  1. 调用顺序有限制
  2. 前面例子只有2个数据源,如果是10个,前5个成功,第6个失败的时候,前五个回滚困难
  3. 每个都要加 @Transactional 而且还可能需要 Propagation.REQUIRES_NEW 启动新事务

能不能使用一个注解就搞定多数据源的那种呢?答案是有的
没错。就是评论所说的,使用@DSTransactional 注解,就可以完美解决上面的问题。

需要导入包(版本太低的话可能没有@DSTransactional 注解,建议提高版本)

    <dependency>
      <groupId>com.baomidou</groupId>
      <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
      <version>3.5.0</version>
    </dependency>

使用方式如下:

	
  /**
  * 在这里加了@DSTransactional注解,db1,db2,db3基本都不需要加事务注解了
  */
  @DSTransactional
  public void db() throws Exception {
    inqOrderService.db1(); //数据源1
    wxUserService.db2(); //数据源2
    scanServiceImpl.db3(); //数据源3(这里抛出异常)
  }

如上代码所示,当db3抛出异常时,db1,db2会跟着回滚

总结:
词穷,就一句话:只有一个服务,且用到多个数据源时,用@DSTransactional注解比较方便,可以控制多数据源进行回滚。而不使用前面的例子的方式。

另外为什么说只有一个服务才用@DSTransactional注解,多服务不行吗?没错,就是不行,如果系统是微服务架构,db1,db2,db3都来源于不同的服务,那么db3报错时,前面两个并不会回滚,因为他们不在一个服务内,@DSTransactional注解排不上用场。此时只能使用另外一种方式了。网上有人说用seata框架,也有人说用队列,在下有空研究研究,先这样,后会有其。

参考链接:https://blog.csdn.net/weixin_43846916/article/details/110824726

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐