文章目录

  • 前言
  • 准备阶段
  • 具体配置
  • 功能展示
  • 注解方式切换数据源
  • 代码方式切换数据源
  • 优化方式
  • 动态添加删除数据源
  • 事务问题
  • 参考文章


前言

最近公司的权限项目要实现多租户的功能,于是就要做数据隔离以确保每个租户的数据的安全性,但是项目中也要动态的提供能够添加租户和删除租户的功能,这就需要动态的添加数据源(数据库连接地址。。),于是就去找了找相关的资料,今天就整理总结一下如何实现这个功能。

准备阶段

项目的框架是springboot+JDBC,那么多数据源这个也有框架,使用的是dynamic-datasource这个框架,这个框架和mybatis-plus是同一家的,这里使用的是4.2.0版本,这个版本官方推荐使用的:官方地址

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

具体配置

这里是基础的配置演示,这里配置了两个数据源,分别是master和slave1,配置文件所有以下划线 _ 分割的数据源 首部 即为组的名称,相同组名称的数据源会放在一个组下。当然如果没有这种分组的需要就不需要。同时这个 primary: master #设置默认的数据源或者数据源组,默认值即为master这个必须要设置,因为当你的一些方法或者类上没有标识使用那个数据源去执行SQL时会执行这个默认的数据源,在多说一句就是这个框架支持一主多从,多主多从这些配置,同时如果使用了分组的配置,分组中还有相应的负载均衡的策略去访问这个分组下的数据库。

项目应用多数据源动态切换(动态切换数据库连接)_多数据源

spring:
  datasource:
    #dynamic开始多数据源配置
    dynamic:
      primary: master #设置默认的数据源或者数据源组,默认值即为master
      datasource:
        master:  # 数据源的名字:master
          username: root
          password: guyanshuang.
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql:///test1
        slave1:   # 数据源的名字:slave_1
          username: root
          password: guyanshuang.
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql:///test2

功能展示

注解方式切换数据源

框架提供的 @DS("数据源名称")注解可以帮助我们实现切换数据源的效果具体使用。

这是标记在方法上的,同时也可以标记到类上,那么如果方法和类都有标记注解,那么在调用标记注解的方法时,有限使用方法上注解标记的数据源。当然这种方式并不动态,因为上面说到了还要支持添加数据源的方式,那么这种方式肯定不行。

项目应用多数据源动态切换(动态切换数据库连接)_bc_02


项目应用多数据源动态切换(动态切换数据库连接)_bc_03

代码方式切换数据源

使用这种方式切换数据源才能算是动态的实现我上面提到的效果。
下面这两行代码就是关键,这是框架提供的方法,直接放到我们的业务代码之前即可。当然这种方式还是太繁琐了因为这两行代码的重复性太高了,作为一个程序员肯定不能做一个简单的CP工程师。

//清除当前线程的数据源信息
		
        DynamicDataSourceContextHolder.clear();
        //切换到对应poolName(数据源名称)的数据源
        DynamicDataSourceContextHolder.push("slave_1");

同一个方法来回切换数据源也不必担心,这是线程级别的,不会相互影响

项目应用多数据源动态切换(动态切换数据库连接)_多数据源_04

优化方式

既然这两行代码要在执行我们的业务代码前那么就写一个拦截器在请求进来前将这两行代码给执行了不就解决了我们重复的问题嘛。spring提供的一个拦截器HandlerInterceptor 接口

public class MyConfig implements HandlerInterceptor {
    /**
     * 请求处理程序(controller)执行之前调用。
     * @param request current HTTP request
     * @param response current HTTP response
     * @param handler chosen handler to execute, for type and/or instance evaluation
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        DynamicDataSourceContextHolder.clear();
        //切换到对应poolName的数据源
        DynamicDataSourceContextHolder.push("slave_1");
        return true;
    }
}

这样就做到了动态切换并且把切换的这个操作封装起来了

项目应用多数据源动态切换(动态切换数据库连接)_多数据源_05

动态添加删除数据源

使用代码配置这个数据源,代码中提供的是添加的数据源操作,移除也很简单,根据数据源名称移除ds.removeDataSource(poolName);

public void test3(){
        // 为DataSourceProperty提供相应的数据源配置信息
        DataSourceProperty dataSourceProperty=new DataSourceProperty();
        DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;

        DataSource dataSource = dataSourceCreator.createDataSource(dataSourceProperty);
        // PoolName就是我们yaml配置中说的数据源名称
        ds.addDataSource("PoolName", dataSource);
    }

DataSourceProperty类就是我们在配置文件中配置的datasource相关的信息,可以看一下这个类的内部

项目应用多数据源动态切换(动态切换数据库连接)_bc_06

项目应用多数据源动态切换(动态切换数据库连接)_动态切换数据源_07

事务问题

嵌套数据源的service中,如果操作了多个数据源,不能在最外层加上@Transaction开启事务,否则切换数据源不生效,因为这属于分布式事务了,需要用seata方案解决,如果是单个数据源(不需要切换数据源)可以用@Transaction开启事务,保证每个数据源自己的完整性。使用@Transaction注解开启的事务传播机制是required,如果需要每一个子service的事务生效就需要使用require_new,但是这又涉及到一个问题就是如果父service中的事务回滚并不会影响子service的事务回滚,所以需要使用seata。
框架也提供了一个@DSTransactional注解,只要@DSTransactional注解下任一环节发生异常,则全局多数据源事务回滚。但是@DSTransactional和@Transaction不可以混用

参考文章

入门推荐

深挖底层