• SpringBoot动态切换数据源


    1、配置文件中配置多个数据库连接

    1. # mysql配置
    2. spring.datasource.local.jdbc-url=jdbc:mysql://192.168.1.115:3308/cq_njdd?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
    3. spring.datasource.local.username=root
    4. spring.datasource.local.password=root3308
    5. spring.datasource.local.driver-class-name=com.mysql.cj.jdbc.Driver
    6. spring.datasource.remote.jdbc-url=jdbc:mysql://192.168.1.115:8307/cq_njdd?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
    7. spring.datasource.remote.username=root
    8. spring.datasource.remote.password=root2022
    9. spring.datasource.remote.driver-class-name=com.mysql.cj.jdbc.Driver

    这里需要注意如果SpringBoot版本是2.0之后的版本,需要把【url】和【driverClassName】写成【jdbc-url】和【driver-class-name】,否则会报错【jdbcUrl is required with driverClassName.】

     数据库连接池配置

    1. spring.datasource.type=com.zaxxer.hikari.HikariDataSource
    2. spring.datasource.hikari.minimum-idle=10
    3. # 连接池中允许的最大连接数
    4. spring.datasource.hikari.maximum-pool-size=200
    5. # 自动提交从池中返回的连接
    6. spring.datasource.hikari.auto-commit=true
    7. # 连接允许在池中闲置的最长时间,超时则被释放(retired)
    8. spring.datasource.hikari.idle-timeout=30000
    9. spring.datasource.hikari.pool-name=ServiceTaskHikariCP
    10. # 一个连接的生命时长(毫秒),超时而且没被使用则被释放(retired),缺省:30分钟,建议设置比数据库超时时长少30秒,参考MySQLwait_timeout参数(show variables like '%timeout%';)
    11. spring.datasource.hikari.max-lifetime=1200000
    12. # 等待连接池分配连接的最大时长(毫秒),超过这个时长还没可用的连接则发生SQLException
    13. spring.datasource.hikari.connection-timeout=30000
    14. # 验证连接是否有效。此参数必须设置为非空字符串,下面三项设置成true才能生效
    15. spring.datasource.dbcp2.validation-query=select 1
    16. # 指明是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个
    17. spring.datasource.dbcp2.test-on-borrow=true
    18. # 指明是否在归还到池中前进行检验
    19. spring.datasource.dbcp2.test-on-return=false
    20. # 验证连接的有效性
    21. spring.datasource.dbcp2.test-while-idle=true
    22. # 空闲连接回收的时间间隔,与test-while-idle一起使用,设置5分钟
    23. spring.datasource.dbcp2.time-between-eviction-runs-millis=300000
    24. # 连接池空闲连接的有效时间 ,设置30分钟
    25. spring.datasource.dbcp2.soft-min-evictable-idle-time-millis=1800000

     不加配置的话会报No operations allowed after connection closed.异常。


    2、数据源枚举类

    自定义数据源枚举类,这里配置了两个数据源,则创建两个枚举。

    1. public enum DataSourceType {
    2. REMOTE,
    3. LOCAL
    4. }

    3、注入数据源

    生成bean。单一数据源与Mybatis整合时将DataSource数据源作为参数构建【SqlSessionFactory】,而多个数据源的话只需要将作为参数的数据源改为动态的数据源即可。

    下面将配置的数据源全部注入到bean中,其中可设置默认数据源。留作之后调用。

    1. import org.springframework.boot.context.properties.ConfigurationProperties;
    2. import org.springframework.boot.jdbc.DataSourceBuilder;
    3. import org.springframework.context.annotation.Bean;
    4. import org.springframework.context.annotation.Configuration;
    5. import org.springframework.context.annotation.Primary;
    6. import javax.sql.DataSource;
    7. import java.util.HashMap;
    8. import java.util.Map;
    9. @Configuration
    10. public class DataSourceConfig {
    11. @Bean
    12. @ConfigurationProperties("spring.datasource.remote")
    13. public DataSource remoteDataSource() {
    14. return DataSourceBuilder.create().build();
    15. }
    16. @Bean
    17. @ConfigurationProperties("spring.datasource.local")
    18. public DataSource localDataSource() {
    19. return DataSourceBuilder.create().build();
    20. }
    21. @Bean(name = "dynamicDataSource")
    22. @Primary
    23. public DynamicDataSource dataSource(DataSource remoteDataSource, DataSource localDataSource) {
    24. Map targetDataSources = new HashMap<>();
    25. targetDataSources.put(DataSourceType.REMOTE.name(), remoteDataSource);
    26. targetDataSources.put(DataSourceType.LOCAL.name(), localDataSource);
    27. DynamicDataSource dynamicDataSource = new DynamicDataSource();
    28. // 注入目标数据源,可以是多个
    29. dynamicDataSource.setTargetDataSources(targetDataSources);
    30. // 注入默认数据源,只能是一个
    31. dynamicDataSource.setDefaultTargetDataSource(localDataSource);
    32. return dynamicDataSource ;
    33. }
    34. }

    4、切换数据源

    SpringBoot动态切换数据源主要依靠AbstractRoutingDataSource类,这个抽象类中有一个属性为【targetDataSources】

     该属性为Map结构,所有需要切换的数据源都存放在其中,根据指定的KEY进行切换。当然还有一个默认的数据源。

    而切换数据源则需要重写该抽象类中的【determineCurrentLookupKey】抽象方法,该方法的返回值决定了需要切换的数据源key,然后根据这个key去之前的【targetDataSources】Map中取数据源。

     所以我们需要重写【determineCurrentLookupKey】方法,如下:

    1. import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    2. public class DynamicDataSource extends AbstractRoutingDataSource {
    3. @Override
    4. protected Object determineCurrentLookupKey() {
    5. return DynamicDataSourceContextHolder.getDataSourceType();
    6. }
    7. }

    5、切换数据源管理类

    数据源属于公共资源,考虑到多线程的情况下,我们将数据源存储在【ThreadLocal】中,保证线程隔离。

    1. public class DynamicDataSourceContextHolder {
    2. private static final ThreadLocal CONTEXT_HOLDER = new ThreadLocal<>();
    3. public static void setDataSourceType(String dataSourceType){
    4. CONTEXT_HOLDER.set(dataSourceType);
    5. }
    6. public static String getDataSourceType(){
    7. return CONTEXT_HOLDER.get();
    8. }
    9. public static void clearDataSourceType(){
    10. CONTEXT_HOLDER.remove();
    11. }
    12. }

    6、自定义多数据源切换注解

    为了操作方便且低耦合,定义一个切换数据源的注解,如下:

    1. import java.lang.annotation.*;
    2. @Target({ElementType.METHOD, ElementType.TYPE})
    3. @Retention(RetentionPolicy.RUNTIME)
    4. @Documented
    5. public @interface DataSource {
    6. DataSourceType value() default DataSourceType.LOCAL;
    7. }

    7、AOP切面拦截

    切面的作用就是在注解的方法执行前,取DataSource注解【value】值设置到【ThreadLocal】中,然后在方法执行后清除掉【ThreadLocal】中的key,保证如果不切换数据源时使用默认的数据源。

    1. import org.aspectj.lang.ProceedingJoinPoint;
    2. import org.aspectj.lang.annotation.Around;
    3. import org.aspectj.lang.annotation.Aspect;
    4. import org.aspectj.lang.annotation.Pointcut;
    5. import org.aspectj.lang.reflect.MethodSignature;
    6. import org.springframework.core.annotation.Order;
    7. import org.springframework.stereotype.Component;
    8. import java.lang.reflect.Method;
    9. @Aspect
    10. @Order(1)
    11. @Component
    12. public class DataSourceAspect {
    13. @Pointcut("@annotation(com.bw.note.config.datasource.DataSource)")
    14. public void dsPointCut() {
    15. }
    16. @Around("dsPointCut()")
    17. public Object around(ProceedingJoinPoint point) throws Throwable {
    18. MethodSignature signature = (MethodSignature) point.getSignature();
    19. Method method = signature.getMethod();
    20. DataSource dataSource = method.getAnnotation(DataSource.class);
    21. if (dataSource != null) {
    22. DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
    23. }
    24. try {
    25. return point.proceed();
    26. } finally {
    27. DynamicDataSourceContextHolder.clearDataSourceType();
    28. }
    29. }
    30. }

    8、切换数据源注解实际使用

    直接在实现方法上使用自定义的注解即可。


     9、SpringBoot启动类

    启动类的【SpringBootApplication】注解加上【exclude = DataSourceAutoConfiguration.class】属性。

    1. @SpringBootApplication(scanBasePackages = "com.bw",exclude = DataSourceAutoConfiguration.class)
    2. @MapperScan("com.bw.note.mapper")
    3. public class NoteServiceApplication {
    4. public static void main(String[] args) {
    5. SpringApplication.run(NoteServiceApplication.class, args);
    6. }
    7. }

    参考资料

    https://blog.csdn.net/qq_36997144/article/details/123439244

  • 相关阅读:
    雷达人体存在感应器成品,广泛应用于感应灯控制,实时精准感知方案
    免费 Copilot 用户可以访问 OpenAI 的 GPT-4 Turbo;面向 3D 虚拟环境的多面手 AI 代理
    React18原理: React核心对象之Update、UpdateQueue、Hook、Task对象
    C#大作业——学生信息管理系统
    计算器的简化版
    「数据结构详解·六」哈希表
    高标准农田可视化
    【基础算法总结】链表
    Python手写语音识别文字
    使用nvm管理多版本node.js
  • 原文地址:https://blog.csdn.net/qq_37634156/article/details/126488674