动态切换数据源

本文介绍了如何在Spring Boot中实现动态数据源切换。通过自定义注解@DataSource和AOP切面,根据注解指定的数据源名称存储到ThreadLocal中,AbstractRoutingDataSource根据ThreadLocal的值动态选择数据源。详细步骤包括预备知识、引入依赖、配置文件、自定义注解和切面等,并提供了项目代码链接。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

思路

  1. 自定义一个注解 @DataSource,将来可以将该注解加在 service 层方法或者类上面,表示方法或者类中的所有方法都使用某一个数据源。
  2. 对于第一步,如果某个方法上面有 @DataSource 注解,那么就将该方法需要使用的数据源名称存入到 ThreadLocal。
  3. 自定义切面,在切面中解析 @DataSource 注解,当一个方法或者类上面有 @DataSource 注解的时候,将 @DataSource 注解所标记的数据源存入到 ThreadLocal 中。
  4. 最后,当 Mapper 执行的时候,需要 DataSource,他会自动去 AbstractRoutingDataSource 类中查找需要的数据源,我们只需要在 AbstractRoutingDataSource 中返回 ThreadLocal 中的值即可。

项目代码链接:https://github.com/1040580896/dynamic_datasourece

1. 预备知识

想要自定义动态数据源切换,得先了解一个类 AbstractRoutingDataSource

AbstractRoutingDataSource 是在 Spring2.0.1 中引入的(注意是 Spring2.0.1 不是 Spring Boot2.0.1,所以这其实也算是 Spring 一个非常古老的特性了), 该类充当了 DataSource 的路由中介,它能够在运行时, 根据某种 key 值来动态切换到真正的 DataSource 上。

大致的用法就是你提前准备好各种数据源,存入到一个 Map 中,Map 的 key 就是这个数据源的名字,Map 的 value 就是这个具体的数据源,然后再把这个 Map 配置到 AbstractRoutingDataSource 中,最后,每次执行数据库查询的时候,拿一个 key 出来,AbstractRoutingDataSource 会找到具体的数据源去执行这次数据库操作。

2、引入依赖

首先我们创建一个 Spring Boot 项目,引入 Web、MyBatis 以及 MySQL 依赖,项目创建成功之后,再手动加入 Druid 和 AOP 依赖,如下:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.2.9</version>
</dependency>

3、配置文件

# 数据源配置
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    ds:
      # 主库数据源
      master:
        url: jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8
        username: root
        password: th123456
        # 从库数据源
      slave:
        url: jdbc:mysql://localhost:3306/test2?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8
        username: root
        password: th123456
        # 初始连接数
    initialSize: 5
    # 最小连接池数量
    minIdle: 10
    # 最大连接池数量
    maxActive: 20
    # 配置获取连接等待超时的时间
    maxWait: 60000
    # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
    timeBetweenEvictionRunsMillis: 60000
    # 配置一个连接在池中最小生存的时间,单位是毫秒
    minEvictableIdleTimeMillis: 300000
    # 配置一个连接在池中最大生存的时间,单位是毫秒
    maxEvictableIdleTimeMillis: 900000
    # 配置检测连接是否有效
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    webStatFilter:
      enabled: true
    statViewServlet:
      enabled: true
      # 设置白名单,不填则允许所有访问
      allow:
      url-pattern: /druid/*
      # 控制台管理用户名和密码
      login-username: tienchin
      login-password: 123456
    filter:
      stat:
        enabled: true
        # 慢SQL记录
        log-slow-sql: true
        slow-sql-millis: 1000
        merge-sql: true
      wall:
        config:
          multi-statement-allow: tru

都是 Druid 的常规配置,也没啥好说的,唯一需要注意的是我们整个配置文件的格式。ds 里边配置我们的所有数据源,每个数据源都有一个名字,master 是默认数据源的名字,不可修改,其他数据源都可以自定义名字。最后面我们还配置了 Druid 的监控功能,如果小伙伴们还不懂 Druid 的监控功能,可以查看Spring Boot 如何监控 SQL 运行情况?

4、自定义注解和切面

注解

这个注解将来加在 Service 层的方法上,使用该注解的时候,需要指定一个数据源名称,不指定的话,默认就使用 master 作为数据源。

/**
 * 将来可以加在某一个service 类上或者方法上,通过value属性来指定类或者方法应该使用哪一个数据源
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({
   
   ElementType.TYPE,ElementType.METHOD})
public @interface DataSource {
   
   

    /**
     * 如果一个方法上加了 @DataSource 但是却没有指定数据源名称,那么默认使用 master 数据源
     * @return
     */
    String value() default DataSourceType.DEFAULT_DS_NAME;
}

切面

过 AOP 来解析当前的自定义注解

这里使用了ThreadLocal来保存需要哪种数据源

MethodSignature signature = (MethodSignature) pjb.getSignature();

AnnotationUtils.findAnnotation工具类

ThreadLocal 的特点,简单说就是在哪个线程中存入的数据,在哪个线程才能取出来,换一个线程就取不出来了,这样可以确保多线程环境下的数据安全。

@Component
@Aspect
public class DataSourceAspect {
   
   

    /**
     * 切点
     *
     * @annotation(com.th.annotation.DataSource 表示方法上有 @DataSource 注解就将方法拦截下来
     * @within(com.th.annotation.DataSource) 表示如果类上面有 @DataSource 注解,就将类中的方法拦截下来
     */
    @Pointcut("@annotation(com.th.annotation.DataSource) || @within(com.th.annotation.DataSource)")
    public void pc() {
   
   

    }


    @Around("pc()")
    public Object around(ProceedingJoinPoint pjb) {
   
   
        //获取方法上面的注解
        DataSource dataSource = getDataSourece(pjb);
        if (dataSource != null) {
   
   
            //数据源的名称
            String value = dataSource.value();
            DynmaicDataSourceContextHolder.setDataSourceType(value);
        }
        try {
   
   
            return pjb.proceed();
        } catch (Throwable e) {
   
   
            e.printStackTrace();
        } finally {
   
   
            DynmaicDataSourceContextHolder.clearDataSourceType();
        }
        return null;
    }

    private DataSource getDataSourece(ProceedingJoinPoint pjb) {
   
   

        
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值