专栏名称: Java基基
一个苦练基本功的 Java 公众号,所以取名 Java 基基
目录
相关文章推荐
新闻君  ·  海口“三港”因雾停航,预计复航时间→ ·  17 小时前  
新闻君  ·  海口“三港”因雾停航,预计复航时间→ ·  17 小时前  
湛江日报  ·  大雾停航预警! ·  昨天  
51好读  ›  专栏  ›  Java基基

在 SpringBoot 项目中如何动态切换数据源、数据库?(可直接CV)

Java基基  · 公众号  ·  · 2025-02-06 11:55

正文

👉 这是一个或许对你有用 的社群

🐱 一对一交流/面试小册/简历优化/求职解惑,欢迎加入 芋道快速开发平台 知识星球。 下面是星球提供的部分资料:

👉 这是一个或许对你有用的开源项目

国产 Star 破 10w+ 的开源项目,前端包括管理后台 + 微信小程序,后端支持单体和微服务架构。

功能涵盖 RBAC 权限、SaaS 多租户、数据权限、 商城 、支付、工作流、大屏报表、微信公众号、 ERP CRM AI 大模型 等等功能:

  • Boot 多模块架构:https://gitee.com/zhijiantianya/ruoyi-vue-pro
  • Cloud 微服务架构:https://gitee.com/zhijiantianya/yudao-cloud
  • 视频教程:https://doc.iocoder.cn
【国内首批】支持 JDK 17/21 + SpringBoot 3.3、JDK 8/11 + Spring Boot 2.7 双版本

来源:blog.csdn.net/cyuyanya__/
article/details/139605809


前言

本文参考若依源码,介绍了如何在 SpringBoot 项目中使用AOP和自定义注解实现MySQL主从数据库的动态切换,当从库故障时,能自动切换到主库,确保服务的高可用性。

实现效果:

如果服务器搭建的是一主多从多个mysql数据源,主服务器用来读。从服务器用来写。此时你在代码层面用注解指定了一个增删改方法到从数据源,但是碰巧此时从数据源失效了,那么就会自动的切换到其它服务器。

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 视频教程:https://doc.iocoder.cn/video/

为什么要切换数据源,有哪些应用场景?

动态切换数据源通常是为了满足以下需求:

  • 读写分离: 在数据库架构中,为了提高性能和可用性,常常使用主从复制的方式。主数据库处理写操作,而从数据库处理读操作。动态切换数据源可以在不同的操作中使用不同的数据库,以达到优化性能的目的。
  • 多租户架构: 在SaaS(Software as a Service)应用中,不同的租户可能需要操作不同的数据库。动态数据源允许系统根据租户的身份来切换到对应的数据源。
  • 分库分表: 在处理大规模数据时,可能会采用分库分表的策略来分散数据存储的压力。动态切换数据源可以在执行跨库或跨表操作时,根据需要切换到正确的数据源。
  • 环境隔离: 在开发、测试和生产环境中,可能需要连接到不同的数据库。动态数据源可以在不同环境之间无缝切换,以确保数据的隔离和安全性。
  • 灵活的数据库管理: 在复杂的业务场景下,可能需要根据不同的业务逻辑来选择不同的数据源。动态数据源提供了这种灵活性,允许开发者根据运行时的条件来选择最合适的数据源。
  • 故障转移和高可用性: 当主数据库不可用时,动态切换数据源可以自动或手动切换到备用数据库,以保证服务的连续性和数据的可用性。

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/yudao-cloud
  • 视频教程:https://doc.iocoder.cn/video/

如何切换数据源?

  • SpringBoot版本:3.0.4
  • jdk版本:JDK17

1.pom文件

<dependency>
  <groupId>org.projectlombokgroupId>
  <artifactId>lombokartifactId>
dependency>

<dependency>
  <groupId>org.springframework.bootgroupId>
  <artifactId>spring-boot-starter-aopartifactId>
dependency>

<dependency>
  <groupId>com.alibabagroupId>
  <artifactId>druid-spring-boot-starterartifactId>
  <version>1.2.20version>
dependency>

<dependency>
  <groupId>com.mysqlgroupId>
  <artifactId>mysql-connector-jartifactId>
dependency>

<dependency>
  <groupId>com.baomidougroupId>
  <artifactId>mybatis-plus-boot-starterartifactId>
  <version>3.5.3.1version>
dependency>

2.配置文件:application.yml、application-druid.yml

application.yml配置文件:

#application.yml
 
server:
  port: 8000
spring:
  profiles:
    active: druid

application-druid.yml配置文件:

# 数据源配置
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    druid:
      # 主库数据源
      master:
        url: jdbc:mysql://localhost:3306/study?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
        username: root
        password: 123456
      # 从库数据源
      slave:
        # 从数据源开关/默认关闭
        enabled: true
        url: jdbc:mysql://localhost:3306/t_lyj?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
        username: root
        password: 123456
      # 初始连接数
      initialSize: 5
      # 最小连接池数量
      minIdle: 10
      # 最大连接池数量
      maxActive: 20
      # 配置获取连接等待超时的时间
      maxWait: 60000
      # 配置连接超时时间
      connectTimeout: 30000
      # 配置网络超时时间
      socketTimeout: 60000
      # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
      timeBetweenEvictionRunsMillis: 60000
      # 配置一个连接在池中最小生存的时间,单位是毫秒
      minEvictableIdleTimeMillis: 300000
      # 配置一个连接在池中最大生存的时间,单位是毫秒
      maxEvictableIdleTimeMillis: 900000

3、数据源名称枚举DataSourceType

/**
 * 数据源
 * 
 * @author ruoyi
 */

public enum DataSourceType
{
    /**
     * 主库
     */

    MASTER,
 
    /**
     * 从库
     */

    SLAVE
}

4、Bean工具类SpringUtils

@Component
public final class SpringUtils implements BeanFactoryPostProcessorApplicationContextAware 
{
    /** Spring应用上下文环境 */
    private static ConfigurableListableBeanFactory beanFactory;
 
    private static ApplicationContext applicationContext;
 
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException 
    
{
        SpringUtils.beanFactory = beanFactory;
    }
 
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException 
    
{
        SpringUtils.applicationContext = applicationContext;
    }
 
    /**
     * 获取对象
     *
     * @param name
     * @return Object 一个以所给名字注册的bean的实例
     * @throws BeansException
     *
     */

    @SuppressWarnings("unchecked")
    public static  getBean(String name) throws BeansException
    
{
        return (T) beanFactory.getBean(name);
    }
 
    /**
     * 获取类型为requiredType的对象
     *
     * @param clz
     * @return
     * @throws BeansException
     *
     */

    public static  getBean(Class clz) throws BeansException
    
{
        T result = (T) beanFactory.getBean(clz);
        return result;
    }
 
    /**
     * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true
     *
     * @param name
     * @return boolean
     */

    public static boolean containsBean(String name)
    
{
        return beanFactory.containsBean(name);
    }
 
    /**
     * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)
     *
     * @param name
     * @return boolean
     * @throws NoSuchBeanDefinitionException
     *
     */

    public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException
    
{
        return beanFactory.isSingleton(name);
    }
 
    /**
     * @param name
     * @return Class 注册对象的类型
     * @throws NoSuchBeanDefinitionException
     *
     */

    public static Class> getType(String name) throws NoSuchBeanDefinitionException
    {
        return beanFactory.getType(name);
    }
 
    /**
     * 如果给定的bean名字在bean定义中有别名,则返回这些别名
     *
     * @param name
     * @return
     * @throws NoSuchBeanDefinitionException
     *
     */

    public static String[] getAliases(String name) throws NoSuchBeanDefinitionException
    {
        return beanFactory.getAliases(name);
    }
 
    /**
     * 获取aop代理对象
     * 
     * @param invoker
     * @return
     */

    @SuppressWarnings("unchecked")
    public static  getAopProxy(T invoker)
    
{
        return (T) AopContext.currentProxy();
    }
 
    /**
     * 获取当前的环境配置,无配置返回null
     *
     * @return 当前的环境配置
     */

    public static String[] getActiveProfiles()
    {
        return applicationContext.getEnvironment().getActiveProfiles();
    }
 
    /**
     * 获取当前的环境配置,当有多个环境配置时,只获取第一个
     *
     * @return 当前的环境配置
     */

    public static String getActiveProfile()
    
{
        final String[] activeProfiles = getActiveProfiles();
        return StringUtils.isNotEmpty(Arrays.toString(activeProfiles)) ? activeProfiles[0] : null;
    }
 
    /**
     * 获取配置文件中的值
     *
     * @param key 配置文件的key
     * @return 当前的配置文件的值
     *
     */

    public static String getRequiredProperty(String key)
    
{
        return applicationContext.getEnvironment().getRequiredProperty(key);
    }
}

5、多数据源切换注解DataSource

/**
 * 自定义多数据源切换注解
 *
 * 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准
 *
 * @author lyj
 */

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DataSource
{
    /**
     * 切换数据源名称
     */

    public DataSourceType value() default DataSourceType.MASTER;
}

6、数据源解析配置类DruidConfig

@Configuration
public class DruidConfig {

   @Bean
   @ConfigurationProperties("spring.datasource.druid.master")
   public DataSource masterDataSource(DruidProperties druidProperties){
       DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
       return druidProperties.dataSource(dataSource);
   }

   @Bean
   @ConfigurationProperties("spring.datasource.druid.slave")
   @ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
   public DataSource slaveDataSource(DruidProperties druidProperties) {
       DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
       return druidProperties.dataSource(dataSource);
   }

   @Bean(name = "dynamicDataSource")
   @Primary
   public DynamicDataSource dataSource(DataSource masterDataSource) {
       Map targetDataSources = new HashMap<>();
       targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
       setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource");
       return new DynamicDataSource(masterDataSource, targetDataSources);
   }


   /**
    * 设置数据源
    *
    * @param targetDataSources 备选数据源集合
    * @param sourceName 数据源名称
    * @param beanName bean名称
    */

   public void setDataSource(Map targetDataSources, String sourceName, String beanName) {
       try {
           DataSource dataSource = SpringUtils.getBean(beanName);
           targetDataSources.put(sourceName, dataSource);
       } catch (Exception e) {
       }
   }
}

7、数据源注入核心类 DynamicDataSource

/**
 * 动态数据源
 * 
 * @author lyj
 */

public class DynamicDataSource extends AbstractRoutingDataSource {
    public DynamicDataSource(DataSource defaultTargetDataSource, Map targetDataSources)
    
{
        //设置默认数据源
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        //设置目标数据源的映射
        super.setTargetDataSources(targetDataSources);
        //初始化
        super.afterPropertiesSet();
    }
 
    @Override
    protected Object determineCurrentLookupKey()
    
{
        return DynamicDataSourceContextHolder.getDataSourceType();
    }
}

8、数据源切换处理类DynamicDataSourceContextHolder

/**
 * 数据源切换处理
 * 
 * @author lyj
 */

public class DynamicDataSourceContextHolder
{
    public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);
 
    /**
     * 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
     * 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
     */

    private static final ThreadLocal CONTEXT_HOLDER = new ThreadLocal<>();
 
    /**
     * 设置数据源的变量
     */

    public static void setDataSourceType(String dsType)
    
{
        log.info("切换到{}数据源", dsType);
        CONTEXT_HOLDER.set(dsType);
    }
 
    /**
     * 获得数据源的变量,默认使用主数据源
     */

    public static String getDataSourceType()
    
{
        return CONTEXT_HOLDER.get() == null ? DataSourceType.MASTER.name() : CONTEXT_HOLDER.get();
    }
 
    /**
     * 清空数据源变量
     */

    public static void clearDataSourceType()
    
{
        CONTEXT_HOLDER.remove();
    }
}

9、Aop切面类

@Aspect
@Order(1)
@Component
public class DataSourceAspect {
 
 
    @Pointcut("@annotation(com.LYJ.study.DynamicDataSource.annocation.DataSource)"
            + "|| @within(com.LYJ.study.DynamicDataSource.annocation.DataSource)")
    public void dsPointCut(){}
 
    @Around("dsPointCut()")
 
    public Object around(ProceedingJoinPoint joinPoint) throws






请到「今天看啥」查看全文