Springboot+mybatis-plus+dynamic-datasource+Hikari 手动切换数据源

Springboot+mybatis-plus+dynamic-datasource+Hikari 手动切换数据源

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 用于动态切换数据源的上下文工具类。在使用多数据源的情况下,它可以帮助在运行时选择要使用的数据源。

  1. 存储当前线程的数据源标识:DynamicDataSourceContextHolder使用线程本地变量(ThreadLocal)来存储当前线程所使用的数据源标识。这允许在同一应用程序中的不同线程使用不同的数据源。
 ThreadLocal<Deque<String>> LOOKUP_KEY_HOLDER = new NamedThreadLocal<Deque<String>>("dynamic-datasource") 

  1. 通过调用**DynamicDataSourceContextHolder.setDataSourceKey(String dataSourceKey)**方法,可以将当前线程的数据源标识设置为指定的值。数据源标识通常是一个字符串,用于识别要使用的具体数据源。

  2. 通过调用DynamicDataSourceContextHolder.getDataSourceKey()方法,可以获取当前线程正在使用的数据源标识。这对于在代码中动态选择数据源非常有用。

  3. 在使用完特定数据源后,通过调用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 {
      @Override
      public 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"

    @Autowired
    private DynamicRoutingDataSource dynamicRoutingDataSource;

    @Override
    public 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 {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String dataBaseCode = request.getHeader("dataBaseCode");
        DynamicDataSourceContextHolder.push(dataBaseCode);
        return true;
    }

    @Override
    public 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 {

    @Autowired
    private DataSourceInterceptor dataSourceInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(dataSourceInterceptor).addPathPatterns("/**");
    }
}

2.3. 方法内部硬编码硬编码切换

 if (dbHandelVO.getParam() == null) {
            return WrapperUtil.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();
            }
        }

2.4. 注解进行切换

每个service 和mapper加入注解@DS

service调用本类的方法不会有切面进行切换数据源

    @Bean
    public DsProcessor dsProcessor(){
        TenantDsProcessor tenantDsProcessor = new TenantDsProcessor();
        tenantDsProcessor.setNextProcessor(new DsSpelExpressionProcessor());
        return tenantDsProcessor;
    }


@RequiredArgsConstructor
@Slf4j
public class TenantDsProcessor extends DsProcessor {

    /**
     * 用于获取租户数据源配置的 Service
     */
    @Resource
    @Lazy
    private TenantDatasourceCache tenantDatasourceCache;

    /**
     * 动态数据源
     */
    @Resource
    @Lazy  
    private DynamicRoutingDataSource dynamicRoutingDataSource;

    /**
     * 用于创建租户数据源的 Creator
     */
    @Resource
    @Lazy
    private DefaultDataSourceCreator dataSourceCreator;

    @Override
    public boolean matches(String key){
        return Objects.equals(key, DS.KEY);
    }

    @Override
    public String doDetermineDatasource(MethodInvocation methodInvocation, String s){
        // 获得数据源配置
        String tenantId = TenantContextHolder.getRequiredTenantId();
        if (tenantId.equals(GlobalConsts.MAIN_TENANT_ID)) {
            return "master";
        }
        DataSourceProperty dataSourceProperty = tenantDatasourceCache.getDataSourceProperty(tenantId);
        // 创建 or 创建数据源,并返回数据源名字
        return createDatasourceIfAbsent(dataSourceProperty);
    }

}

博主自己的改造方式

3. 参考资料

  1. dynamic-datasource GitHub 仓库 ↗:dynamic-datasource 的官方 GitHub 仓库,包含源代码、文档和示例等资源。

借鉴地址https://blog.csdn.net/wangshuai6707/article/details/132669192

博客地址

代码下载

下面的springcloud_dynamic_datasource

<think>好的,我现在需要帮助用户了解如何在Spring Boot项目中使用MyBatis-PlusHikari配置多数据源。根据用户提供的引用内容,我需要整理出配置步骤和注意事项。首先,用户提到了dynamic-datasource-spring-boot-starter这个依赖,所以第一步应该是添加相关的Maven依赖。然后,配置数据源信息,这里需要注意Hikari的连接池设置,因为在引用4中提到必须指定type为HikariDataSource,否则可能会使用Druid,导致问题。接下来是配置MyBatis-Plus,可能需要设置mapper接口的位置和分页插件,引用3提到这种配置方式便于管理不同数据源。然后,用户可能需要手动切换数据源,引用2和4提到了使用@DS注解或者在代码中手动切换。另外,读写分离的配置也是一个点,引用4中的MasterSlaveAutoRoutingPlugin需要被创建为Bean。日志配置也很重要,方便调试。最后,测试验证部分需要确保不同数据源的操作正确,可能通过单元测试或者实际调用。需要提醒用户注意依赖冲突,尤其是Hikari和Druid的版本问题,以及在事务管理中数据源切换的限制。这些步骤应该能帮助用户完成多数据源的配置。现在需要将这些整理成结构清晰的教程,并给出示例代码,同时生成相关问题。</think>以下是在Spring Boot项目中整合MyBatis-PlusHikariCP实现多数据源的配置教程,结合官方文档和最佳实践总结: ### 一、添加依赖 ```xml <dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>3.6.1</version> </dependency> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> </dependency> ``` ### 二、配置数据源 ```yaml spring: datasource: dynamic: primary: master # 设置默认数据源 strict: false hikari: connection-timeout: 30000 maximum-pool-size: 15 datasource: master: url: jdbc:mysql://localhost:3306/master_db username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver type: com.zaxxer.hikari.HikariDataSource # 必须显式指定[^4] slave1: url: jdbc:mysql://localhost:3306/slave1_db username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver type: com.zaxxer.hikari.HikariDataSource ``` ### 三、配置MyBatis-Plus ```java @Configuration @MapperScan(basePackages = "com.example.mapper.*") public class MybatisPlusConfig { @Bean public PaginationInterceptor paginationInterceptor() { return new PaginationInterceptor(); } } ``` ### 四、数据源切换 1. **注解方式**(自动切换): ```java @Service public class UserServiceImpl { @DS("master") // 指定数据源 public void addUser(User user) { // 主库操作 } @DS("slave1") public User getUser(Long id) { // 从库查询 } } ``` 2. **手动切换**: ```java DynamicDataSourceContextHolder.push("slave1"); // 切换数据源 try { // 执行数据库操作 } finally { DynamicDataSourceContextHolder.poll(); } ``` ### 五、读写分离配置(可选) ```java @Bean public MasterSlaveAutoRoutingPlugin masterSlaveAutoRoutingPlugin() { return new MasterSlaveAutoRoutingPlugin(); } ``` ### 六、日志配置 ```yaml logging: level: com.baomidou.dynamic: debug ``` ### 七、测试验证 ```java @SpringBootTest class DataSourceTests { @Autowired private UserMapper userMapper; @Test @DS("slave1") void testSlaveQuery() { User user = userMapper.selectById(1L); Assert.notNull(user, "查询失败"); } } ``` **注意事项**: 1. 避免同时使用其他数据源组件(如Druid)造成冲突[^4] 2. 事务中切换数据源需使用`@DSTransactional`注解 3. Hikari连接池参数应根据实际压力调整 4. 不同数据源的数据库表结构应保持一致
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

liuhm~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值