springboot dynamic多数据源demo以及常见切换、事务问题

目录

一:引入依赖

二:配置多数据源

三:切换数据源DS注解

四:切换数据源以及事务相关问题:

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

2.@Transaction开启了事务,为什么多数据源事务不生效?

3.其余问题了解


一:引入依赖

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

二:配置多数据源

yaml配置

通过yaml配置主数据源,这里就只配置了一个主数据源,后续通过代码来自由的切换数据源。

spring:
  datasource:
    dynamic:
      hikari:
        connection-timeout: 5000
        idle-timeout: 30000 # 经过idle-timeout时间如果连接还处于空闲状态, 该连接会被回收
        min-idle: 5 # 池中维护的最小空闲连接数, 默认为 10 个
        max-pool-size: 16 # 池中最大连接数, 包括闲置和使用中的连接, 默认为 10 个
        max-lifetime: 60000 # 如果一个连接超过了时长,且没有被使用, 连接会被回收
        is-auto-commit: true

      primary: master #设置默认的数据源或者数据源组,默认值即为master
      strict: true #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
      datasource:
        master: # 数据源名称
          url: 
          username: 
          password: 
          driver-class-name: com.mysql.cj.jdbc.Driver
          
# 如下,如果你是确定的几个数据源,可以直接都在yaml配置写死即可
#        slave_1:
#          url: 
#          username: 
#          password: 
#          driver-class-name: com.mysql.cj.jdbc.Driver



其中数据库连接池,所有的数据库统一配置,也可以单独配置,例如:
datasource:
        master: # 数据源名称
          url: 
          username: 
          password: 
          driver-class-name: com.mysql.cj.jdbc.Driver
          hikari:
            connection-timeout: 5000
            idle-timeout: 30000 # 经过idle-timeout时间如果连接还处于空闲状态, 该连接会被回收
            min-idle: 5 # 池中维护的最小空闲连接数, 默认为 10 个
            max-pool-size: 16 # 池中最大连接数, 包括闲置和使用中的连接, 默认为 10 个
            max-lifetime: 60000 # 如果一个连接超过了时长,且没有被使用, 连接会被回收
            is-auto-commit: true

三:切换数据源DS注解

DS放在哪里合适?

首先开发者要了解的基础知识是,DS注解是基于AOP的原理实现的,aop的常见失效场景应清楚。 比如内部调用失效,shiro代理失效。 具体见切换数据源

1.通常建议DS放在serviceImpl的方法上,如事务注解一样。(常用方式二:访问第三方库api,单独抽取接口,并作数据处理)

2.注解在Controller的方法上或类上
    并不是不可以,并不建议的原因主要是controller主要作用是参数的检验等一些基础逻辑的处理,这部分操作常常并不涉及数据库


3.注解在service的实现类的方法或类上
    这是建议的方式,service主要是对业务的处理, 在复杂的场景涉及连续切换不同的数据库。 如果你的方法有通用性,其他service也会调用你的方法。 这样别人就不用重复处理切换数据源

4.注解在mapper上。(常用方式一)
    通常如果你某个Mapper对应的表只在确定的一个库,也是可以的。 但是建议只注解在Mapper的类上。

5.其他使用方式
    继承抽象类上的DS

6.继承接口上的DS

示例:

@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);
    }
}

等。

四:切换数据源以及事务相关问题:

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

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

解决方法:

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

2.@Transaction开启了事务,为什么多数据源事务不生效?

        @Transaction开启了事务,为什么多数据源事务不生效? 简单来说:嵌套数据源的service中,如果操作了多个数据源,不能在最外层加上@Transaction开启事务,否则切换数据源不生效,因为这属于分布式事务了,需要用seata方案解决,如果是单个数据源(不需要切换数据源)可以用@Transaction开启事务,保证每个数据源自己的完整性

        加事务不生效的原因:
dynamic-datasource切换数据源的原理就是实现了DataSource接口,实现了getConnection方法,只要在service中开启事务,service中对其他数据源操作只会使用开启事务的数据源,因为开启事务数据源会被缓存下来,可以在DataSourceTransactionManager的doBegin方法中看见那个txObject,如果在一个事务内,就会复用Connection,所以切换不了数据源

解决方法:本地事务

通过本地事务实现很简单,就是循环提交,发生错误,循环回滚。 我们默认的前提是数据库本身不会异常,比如宕机。
如数据在回滚的过程突然宕机,本地事务就会有问题。如果你需要完整分布式方案请使用seata方案。

使用方法
在最外层的方法添加 @DSTransactional,底下调用的各个类就正常切换数据源即可。

简单举例如下:


@DeleteMapping
//只要@DSTransactional注解下任一环节发生异常,则全局多数据源事务回滚。
@DSTransactional()
@ApiOperation("删除数据源")
public String remove(String poolName) {
    DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
    ds.removeDataSource(poolName);
    return "删除成功";
}

但一定要注意Spring事务@Transational和本地事务@DSTransactional,不能混用

3.其余问题了解

一:涉及需要切换数据源时
      1.不能使用事务,否则数据源不会切换,使用的还是是第一次加载的数据源 。
       删除 操作多数据源的方法或者类、接口 上的 注解 @Transactional() 即可。
      2.第一次加载的数据源之后,第二次(第三次...)操作其它数据源,如果数据源不存在,使用的还是第一            
        次加载的数据源
      3.数据源名称最好不要包含下滑线,下滑线的数据源切换不了

二:其他
    1.接口中A、B两个方法,A无@Transactional标签,B有,上层通过A间接调用B,此时事务不生效。
 
    2.接口中异常(运行时异常)被捕获而没有被抛出。
      默认配置下,spring 只有在抛出的异常为运行时 unchecked 异常时才回滚该事务,
      也就是抛出的异常为RuntimeException 的子类(Errors也会导致事务回滚),
      而抛出 checked 异常则不会导致事务回滚 。可通过 @Transactional rollbackFor进行配置。
 
    3.多线程下事务管理因为线程不属于 spring 托管,故线程不能够默认使用 spring 的事务,
      也不能获取spring 注入的 bean 。
      在被 spring 声明式事务管理的方法内开启多线程,多线程内的方法不被事务控制。
      一个使用了@Transactional 的方法,如果方法内包含多线程的使用,方法内部出现异常,
      不会回滚线程中调用方法的事务。

引用:

参考博客:实现多租户动态切换数据源

猜你喜欢

转载自blog.csdn.net/qq_44691484/article/details/129268120
今日推荐