上一篇
springboot + mybatisplus 實作 動态切換 多資料源
我們說到 利用 SqlSessionFactory 實作 動态 資料源的切換
具體步驟:
- 根據前端入參 conn 值 擷取 資料庫 相關連接配接資訊 如: driver/url/userName/passwd等等
- 根據 資料庫連接配接資訊 擷取 SqlSessionFactory
- 擷取 SqlSession
- 執行個體化 Mapper
- 執行 增删查改 相關操作
以及存在的問題:
直接 通過 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)示範結果
總結:
利用 AbstractRoutingDataSource 路由 可以很好的解決
springboot + mybatisplus 實作 動态切換 多資料源
中 xxxService.list(wrapper) 這個問題。減少對業務侵入性。
這僅隻是一種實作方式,希望諸大佬能夠多多給出建議或者意見。