天天看點

springboot + mybatisplus + AbstractRoutingDataSource動态切換DB

作者:雙主雙機熱備

上一篇

springboot + mybatisplus 實作 動态切換 多資料源

我們說到 利用 SqlSessionFactory 實作 動态 資料源的切換

具體步驟:

  1. 根據前端入參 conn 值 擷取 資料庫 相關連接配接資訊 如: driver/url/userName/passwd等等
  2. 根據 資料庫連接配接資訊 擷取 SqlSessionFactory
  3. 擷取 SqlSession
  4. 執行個體化 Mapper
  5. 執行 增删查改 相關操作

以及存在的問題:

直接 通過 service 調用 mybatisplus 提供 通用接口 暫時 沒有 比較好的解決方案

形如:xxxService.list(wrapper);

本篇 将會 利用 AbstractRoutingDataSource 實作動态切換資料庫

大緻思路:

(1)前端 header 增加參數 dsType 辨別該次請求的資料源

(2)後端通過 WebMvcConfigurer 及 HandlerInterceptor 擷取該參數

(3)将 dsType 設值 本地 線程中

(4)線程參數 dsType注入 自定義繼承AbstractRoutingDataSource類 determineCurrentLookupKey方法中

(5)一些資料庫配置類Bean 見下

廢話不多說 直接 上 代碼:

(1)引入 dynamic-datasource-spring-boot-starter

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>${mybatis-plus.version}</version>
</dependency>
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
    <version>${mybatis-plus.version}</version>
</dependency>           

(2)配置檔案

增加多資料源 配置

spring:
  datasource:
    dynamic:
      primary: mysql
      datasource:
        mysql:
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://x.x.x.x:3306/x?characterEncoding=utf8&useSSL=true&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
          username: x
          password: x
        mysqlx:
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://x.x.x.x:3306/o?characterEncoding=utf8&useSSL=true&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
          username: o
          password: o           

(3)啟動類

取消 自動選擇 資料源

// 啟動類 取消 自動選擇 資料源
@SpringBootApplication(exclude={DataSourceAutoConfiguration.class})           

(4)資料庫配置類

對應配置檔案 中 mysql 和 mysqlx 庫

@Configuration
@ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.mysql")
@Data
public class MysqlDSConfig {
    private String url;

    private String username;

    private String password;

    private String driverClassName;

    private DataSource dataSource;

    @PostConstruct
    void init() {
        dataSource = DataSourceBuilder.create()
                .url(url)
                .password(password)
                .username(username)
                .driverClassName(driverClassName)
                .build();
    }
}
           
@Configuration
@ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.mysqlx")
@Data
public class Mysql2DSConfig {
    private String url;

    private String username;

    private String password;

    private String driverClassName;

    private DataSource dataSource;

    @PostConstruct
    void init() {
        dataSource = DataSourceBuilder.create()
                .url(url)
                .password(password)
                .username(username)
                .driverClassName(driverClassName)
                .build();
    }
}
           

(5)多資料源配置類

@Configuration
public class DSConfig {

    @Autowired
    private Mysql2DSConfig mysql2;

    @Autowired
    private MysqlDSConfig mysql;

    /**
     * Mysql2DataSource
     */
    @Bean(name = "Mysql2DS")
    @Qualifier("Mysql2DS")
    public DataSource mysql2DS() {
        return mysql2.getDataSource();
    }

    /**
     * MysqlDataSource
     */
    @Bean(name = "MysqlDS")
    @Qualifier("MysqlDS")
    public DataSource mysqlDS() {
        return mysql.getDataSource();
    }

    @Bean(name = "dynamicDS")
    @Primary
    public DataSource dynamicDataSource() {
        DynamicDS dynamicDS = new DynamicDS();
        dynamicDS.map = new HashMap<>(16);
        dynamicDS.map.put(DbConstant.MYSQL, mysqlDS());
        dynamicDS.map.put(DbConstant.MYSQL2, mysql2DS());
        dynamicDS.setTargetDataSources(dynamicDS.map);
        DynamicDSContextHolder.dsList.addAll(dynamicDS.map.keySet());
        dynamicDS.setDefaultTargetDataSource(mysqlDS());
        return dynamicDS;
    }
}
           
public class DynamicDS extends AbstractRoutingDataSource {

    public Map<Object, Object> map = null;

    @Override
    protected Object determineCurrentLookupKey() {
        /*
         * DynamicDataSourceContextHolder setDataSourceType 設定 目前 資料源
         * DynamicDataSource getDataSourceType 擷取
         * AbstractRoutingDataSource 進行 注入 使用
         */
        return DynamicDSContextHolder.getDsType();

    }

}
           

(6)mvc攔截器

@Configuration
public class DbWebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 自定義攔截器
        registry.addInterceptor(new DbHandlerInterceptor())
                .addPathPatterns("/**");
    }

}
           

(7)請求攔截器

将 dsType 設值到 線程中

@Component
@Slf4j
public class DbHandlerInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
        String dsType = StringUtils.isBlank(request.getHeader(DbConstant.DS_TYPE))
                ? DbConstant.MYSQL
                : request.getHeader(DbConstant.DS_TYPE);
        if (dsType.equals(DbConstant.MYSQL) || dsType.equals(DbConstant.MYSQL2)) {
            DynamicDSContextHolder.setDsType(dsType);
        } else {
            log.error("dsType: {} 參數值 有誤!", dsType);
            throw new Exception(String.format("dsType [%s] 參數值 有誤!", dsType));
        }
        log.info("dsType: {}", dsType);
        return true;
    }
}
           

(8)配置 資料源上下文

本地 線程 保證 請求 之間 的 獨立性 存儲 dsType 多資料源 辨別

@Slf4j
public class DynamicDSContextHolder {

    /**
     * 本地 線程 保證 請求 之間 的 獨立性
     */
    private static final ThreadLocal<String> DS_CONTEXT_HOLDER = new ThreadLocal<>();

    public static List<Object> dsList = new ArrayList<>();

    public static void setDsType(String dsType) {
        DS_CONTEXT_HOLDER.set(dsType);
    }

    public static String getDsType() {
        return DS_CONTEXT_HOLDER.get();
    }

    public static void removeDsType() {
        DS_CONTEXT_HOLDER.remove();
    }

    public static boolean containsDs(String dsId) {
        return dsList.contains(dsId);
    }
}
           

(9)常量類

public class DbConstant {

    public static final String MYSQL = "mysql";

    public static final String MYSQL2 = "mysql2";

    public static final String DS_TYPE = "dsType";

}           

(10)請求案例

@RestController
@RequestMapping("/demo")
public class DemoController {

    @Autowired
    UserMapper mapper;

    @PostMapping("/select")
    public Object query() {
        return mapper.selectById("1");
    }
}
           

(11)示範結果

springboot + mybatisplus + AbstractRoutingDataSource動态切換DB
springboot + mybatisplus + AbstractRoutingDataSource動态切換DB
springboot + mybatisplus + AbstractRoutingDataSource動态切換DB
springboot + mybatisplus + AbstractRoutingDataSource動态切換DB

總結:

利用 AbstractRoutingDataSource 路由 可以很好的解決

springboot + mybatisplus 實作 動态切換 多資料源

中 xxxService.list(wrapper) 這個問題。減少對業務侵入性。

這僅隻是一種實作方式,希望諸大佬能夠多多給出建議或者意見。

繼續閱讀