Spring Boot中Druid连接池与多数据源切换的方案

文摘   2024-11-14 20:49   广东  

微服务架构中多数据源切换是个常见的需求。Spring Boot 提供了强大的支持来简化这一过程.

多数据源切换原理

多数据源切换的原理主要基于 Spring 的 AbstractRoutingDataSource 类。AbstractRoutingDataSource 类允许根据运行时上下文动态选择数据源。其核心在于实现 determineCurrentLookupKey 方法,该方法决定当前操作使用哪个数据源。AbstractRoutingDataSource 实现多数据源切换的原理:

1. 数据源映射

  • AbstractRoutingDataSource 内部维护了一个映射(Map),用于存储数据源标识(key)和对应的数据源实例(value)。这个映射允许根据数据源标识快速查找和获取对应的数据源。

2. 数据源标识的确定

  • AbstractRoutingDataSource 提供了一个抽象方法 determineCurrentLookupKey(),该方法用于确定当前需要使用的数据源标识。这个方法需要由子类实现,以返回当前线程或请求应该使用的数据源标识。

3. 数据源的选择与连接获取

  • 当应用程序需要获取数据库连接时,AbstractRoutingDataSourcegetConnection() 方法会被调用。这个方法首先调用 determineCurrentLookupKey() 方法来获取当前的数据源标识,然后根据这个标识从内部映射中查找对应的数据源。
  • 一旦找到了对应的数据源,AbstractRoutingDataSource 就会调用该数据源的 getConnection() 方法来获取实际的数据库连接,并将这个连接返回给应用程序。

4. 数据源切换的实现

  • 为了实现数据源的动态切换,通常会在子类中重写 determineCurrentLookupKey() 方法,并根据当前的上下文(如线程变量)来确定返回的数据源标识。
  • 此外,通常会使用 ThreadLocal 来存储每个线程的数据源标识,这样每个线程都可以独立地切换数据源而不会互相干扰。

实现步骤

1. 依赖

引入 MySQL 和 Druid 的依赖

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.2.6</version>
</dependency>
2. 配置数据源

application.yml 文件中配置多个数据源。

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      datasource1:
        url: jdbc:mysql://localhost:3306/master_db
        username: root
        password: password
        initial-size: 5
        min-idle: 5
        max-active: 20
        max-wait: 60000
      datasource2:
        url: jdbc:mysql://localhost:3306/slave_db
        username: root
        password: password
        initial-size: 5
        min-idle: 5
        max-active: 20
        max-wait: 60000
3. 创建数据源配置类

创建一个配置类来定义数据源 Bean。

@Configuration
public class DataSourceConfig {

    @Bean
    @ConditionalOnProperty(prefix = "spring.datasource.druid", name = "datasource1")
    @ConfigurationProperties(prefix = "spring.datasource.druid.datasource1")
    public DataSource dataSource1() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @ConditionalOnProperty(prefix = "spring.datasource.druid", name = "datasource2")
    @ConfigurationProperties(prefix = "spring.datasource.druid.datasource2")
    public DataSource dataSource2() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @Primary
    public DataSource dynamicDataSource() {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        dynamicDataSource.setDefaultTargetDataSource(dataSource1());
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("dataSource1", dataSource1());
        targetDataSources.put("dataSource2", dataSource2());
        dynamicDataSource.setTargetDataSources(targetDataSources);
        return dynamicDataSource;
    }
}
4. 实现 AbstractRoutingDataSource

创建一个继承自 AbstractRoutingDataSource 的类,并实现 determineCurrentLookupKey 方法。

public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSource();
    }
}
5. 创建 DataSourceContextHolder

创建一个工具类来保存当前线程的数据源信息。

public class DataSourceContextHolder {

    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

    public static void setDataSource(String dataSource) {
        CONTEXT_HOLDER.set(dataSource);
    }

    public static String getDataSource() {
        return CONTEXT_HOLDER.get();
    }

    public static void clearDataSource() {
        CONTEXT_HOLDER.remove();
    }
}
6. AOP动态切换数据源

使用 AOP 在方法执行前后切换数据源。

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class DataSourceAspect {
 // 方法或者类上的横切点
    @Pointcut("@annotation(dataSource) || @within(dataSource)")
    public void dataSourcePointcut(DataSource dataSource) {}

    @Before("dataSourcePointcut(dataSource)")
    public void switchDataSource(JoinPoint joinPoint, DataSource dataSource) {
        // 从注解中获取数据源标识
        String dataSourceKey = dataSource.value();
        // 切换到指定的数据源
        DataSourceContextHolder.setDataSourceType(dataSourceKey);
    }

    @AfterReturning(pointcut = "dataSourcePointcut(dataSource)", returning = "result")
    public void restoreDataSource(JoinPoint joinPoint, DataSource dataSource, Object result) {
        // 恢复默认数据源(可选)
        DataSourceContextHolder.clearDataSourceType();
    }
}

/**
* 自定义注解
*/

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {

    String value() default "dataSource1";
}

7. 使用自定义注解

在需要切换数据源的方法上使用自定义注解。

@Service
public class UserService {

    @Autowired
    private UserRepository userRepo;

    @DataSource("dataSource2")
    public void queryAndUpdate(List<Integer> deptIds) {
        List<User> userList = userRepo.findAll();
        System.out.println(userList.size());
        // 其他操作
    }
}

注意

  • 数据源切换的逻辑应该尽可能简单和高效,以避免对应用程序性能产生负面影响。
  • 在切换数据源时,需要注意事务管理的问题,确保在同一个事务中只使用同一个数据源。

太强 ! SpringBoot中出入参增强的5种方法 : 加解密、脱敏、格式转换、时间时区处理

太强 ! SpringBoot中优化if-else语句的七种绝佳方法实战

SpringBoot使用EasyExcel并行导出多个excel文件并压缩zip下载
提升编程效率的利器: Google Guava库中双向映射BitMap
从MySQL行格式原理看:为什么开发规范中不推荐NULL?数据是如何在磁盘上存储的?
SpringBoot中使用Jackson实现自定义序列化和反序列化控制的5种方式总结

提升编程效率的利器: Google Guava库之RateLimiter优雅限流

深入JVM逃逸分析原理:且看其如何提高程序性能和内存利用率

必知必会!MySQL索引下推:原理与实战

深入解析JVM内存分配优化技术:TLAB

SpringBoot中基于JWT的双token(access_token+refresh_token)授权和续期方案
SpringBoot中基于JWT的单token授权和续期方案
SpringBoot中Token登录授权、续期和主动终止的方案(Redis+Token)
微服务中token鉴权设计的4种方式总结
提升编程效率的API利器:精通Google Guava库区间范围映射RangeMap
SpringBoot中Jackson控制序列化和反序列化的注解和扩展点总结【收藏版】

SpringBoot中大量数据导出方案:使用EasyExcel并行导出多个excel文件并压缩zip后下载

提升编程效率的API利器:精通Google Guava库之IO工具类
提升编程效率的API利器:精通Google Guava库二维映射表Table
提升编程效率的API利器:精通Google Guava库区间范围映射RangeMap
提升编程效率的利器: Google Guava库中双向映射BitMap
提升编程效率的利器: Google Guava库之RateLimiter优雅限流
基于Guava布隆过滤器的海量字符串高效去重实践
加密算法理论总结:分类与典型算法
每个后端开发人员都应该问的发人深省的问题
提升编程效率的API利器:40个示例精通Google Guava库常用工具
MySQL高级优化技巧:使用Hints精准控制查询优化器的选择
每个后端开发人员都应该问的发人深省的问题

Elasticsearch揭秘:高效写入与精准检索的流程原理全解析


关注『 码到三十五 』,日有所获
                     点赞 和 在看 就是最大的支持

码到三十五
主要分享正经的开发技术(原理,架构,实践,源码等),以输出驱动输入;当然偶尔会穿插点生活琐碎,顺便吃个瓜,目的嘛,搞点精准流量,看能不能发发广告。
 最新文章