Springboot+mybatis-plus+dynamic-datasource+Druid 手动切换数据源
文章目录
- Springboot+mybatis-plus+dynamic-datasource+Druid 手动切换数据源
- 0.前言
- 1. 多数据源核心类浅析
- 1. 1. DynamicDataSourceContextHolder切换数据源核心类
- 1.2. DynamicRoutingDataSource
- 2.基于核心类的理解我们实现自定义的数据源切换方案
- 2.1. 在过滤器[filter]里切换
- 从Header 中获取数据库标识
- 2.2. 拦截器里切换数据源
- DataSourceInterceptor的拦截器
- 2.2. 方法内部硬编码切换
- 3. 参考资料
0.前言
苞米豆团队 dynamic-datasource
支持多种数据源切换方案,核心都是基于DynamicDataSourceContextHolder
。本文我们利用filter和拦截器,以及方法中硬编码 这三种方式动态手动切换数据源。
<dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot-starter</artifactId><version>${dynamic.datasource.version}</version></dependency>
1. 多数据源核心类浅析
在写切换方法之前,我们先来了解一下dynamic-datasource
的两个核心类。
1. 1. DynamicDataSourceContextHolder切换数据源核心类
DynamicDataSourceContextHolder
用于动态切换数据源的上下文工具类。在使用多数据源的情况下,它可以帮助在运行时选择要使用的数据源。
- 存储当前线程的数据源标识:
DynamicDataSourceContextHolder
使用线程本地变量(ThreadLocal)来存储当前线程所使用的数据源标识。这允许在同一应用程序中的不同线程使用不同的数据源。
ThreadLocal<Deque<String>> LOOKUP_KEY_HOLDER = new NamedThreadLocal<Deque<String>>("dynamic-datasource")
-
通过调用
DynamicDataSourceContextHolder.setDataSourceKey(String dataSourceKey)
方法,可以将当前线程的数据源标识设置为指定的值。数据源标识通常是一个字符串,用于识别要使用的具体数据源。 -
通过调用
DynamicDataSourceContextHolder.getDataSourceKey()
方法,可以获取当前线程正在使用的数据源标识。这对于在代码中动态选择数据源非常有用。 -
在使用完特定数据源后,通过调用
DynamicDataSourceContextHolder.clearDataSourceKey()
方法,可以清除当前线程的数据源标识。这将避免数据源标识被错误地保留在其他线程中。
1.2. DynamicRoutingDataSource
基于Spring 的JDBC 提供的AbstractDataSource
类来创建自定义的数据源实现.
·DynamicRoutingDataSource·通过扩展AbstractRoutingDataSource
,可以自定义路由规则。您可以实现determineCurrentLookupKey()
方法,根据特定的规则选择要使用的数据源标识(如数据库名称、租户ID等)。根据路由规则,每个数据访问操作将使用相应的数据源。DynamicRoutingDataSource
可根据运行时的条件或业务需求动态切换数据源。通过更新或改变路由规则,您可以实现动态切换数据源,以适应不同的场景或需求。例如,根据请求的租户ID选择不同的数据库,或者根据时间段选择不同的读写数据源。我们打开源码可以看到此处已经包含了所有数据库。
2.基于核心类的理解我们实现自定义的数据源切换方案
2.1. 在过滤器[filter]里切换
在使用 dynamic-datasource 库时,您可以通过过滤器(Filter)来实现在请求处理过程中切换数据源。下面是一种基本的实现方式:
- 创建
DynamicDataSourceFilter
在过滤器的doFilter
方法中,获取当前请求的上下文信息,例如请求参数、请求头等。
根据上下文信息,判断应该使用哪个数据源,然后调用DynamicDataSourceContextHolder.push(dataSourceKey )
方法设置数据源标识。
@Component@WebFilter(filterName = "dsFilter", urlPatterns = {"/userInfo/*"})public class DynamicDataSourceFilter implements Filter {@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {// 获取上下文信息,判断应该使用哪个数据源String dataSourceKey = determineDataSourceKey(request);// 设置数据源标识DynamicDataSourceContextHolder.push(dataSourceKey );try {// 继续处理请求chain.doFilter(request, response);} finally {// 清除数据源标识DynamicDataSourceContextHolder.poll();}}// 根据请求信息确定数据源标识private String determineDataSourceKey(ServletRequest request) {// 根据请求参数、请求头等进行逻辑判断,返回相应的数据源标识// ...}// 其他方法实现,如初始化和销毁方法// ...}
从Header 中获取数据库标识
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;@Component
@WebFilter(filterName = "dsFilter", urlPatterns = {"/userInfo/*"})
public class DynamicDataSourceFilter implements Filter {private static final String DATA_SOURCE_HEADER = "X-Data-Source"; // 假设 Header 名称为 "X-Data-Source"@Autowiredprivate DynamicRoutingDataSource dynamicRoutingDataSource;@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) servletRequest;String dataSourceKey = determineDataSourceKey(request);try {// 切换数据源DynamicDataSourceContextHolder.push(dataSourceKey);filterChain.doFilter(servletRequest, servletResponse);} finally {// 恢复默认数据源DynamicDataSourceContextHolder.poll();}}private String determineDataSourceKey(HttpServletRequest request) {String dataSourceKey = request.getHeader(DATA_SOURCE_HEADER);// 根据实际需求进行数据源 key 的处理,例如校验、转换等return dataSourceKey;}
}
2.2. 拦截器里切换数据源
可以使用DynamicDataSourceContextHolder 在拦截器中使用
DataSourceInterceptor的拦截器
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;public class DataSourceInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {String dataBaseCode = request.getHeader("dataBaseCode");DynamicDataSourceContextHolder.push(dataBaseCode);return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {DynamicDataSourceContextHolder.poll();}
}
在Spring的配置中注册这个拦截器:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
public class WebMvcConfig implements WebMvcConfigurer {@Autowiredprivate DataSourceInterceptor dataSourceInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(dataSourceInterceptor).addPathPatterns("/**");}
}
2.2. 方法内部硬编码切换
if (dbHandelVO.getParam() == null) {return E6WrapperUtil.ok();}String dataBaseCode = dbHandelVO.getDataBaseCode();if (!StringUtils.isEmpty(dataBaseCode)) {DynamicDataSourceContextHolder.push(dataBaseCode);}try {dbHandelMapper.executeInsert(dbHandelVO.getParam());} catch (Exception e) {throw new E6Exception(e.getMessage(), e);} finally {//移除当前数据源if (!StringUtils.isEmpty(dataBaseCode)) {DynamicDataSourceContextHolder.poll();}}
3. 参考资料
- dynamic-datasource GitHub 仓库 ↗:dynamic-datasource 的官方 GitHub 仓库,包含源代码、文档和示例等资源。