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

news2024/12/23 10:56:05

注意:文章若有错误的地方,欢迎评论区里面指正 🍭 


前言

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

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


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

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

  1. 读写分离:在数据库架构中,为了提高性能和可用性,常常使用主从复制的方式。主数据库处理写操作,而从数据库处理读操作。动态切换数据源可以在不同的操作中使用不同的数据库,以达到优化性能的目的。

  2. 多租户架构:在SaaS(Software as a Service)应用中,不同的租户可能需要操作不同的数据库。动态数据源允许系统根据租户的身份来切换到对应的数据源。

  3. 分库分表:在处理大规模数据时,可能会采用分库分表的策略来分散数据存储的压力。动态切换数据源可以在执行跨库或跨表操作时,根据需要切换到正确的数据源。

  4. 环境隔离:在开发、测试和生产环境中,可能需要连接到不同的数据库。动态数据源可以在不同环境之间无缝切换,以确保数据的隔离和安全性。

  5. 灵活的数据库管理:在复杂的业务场景下,可能需要根据不同的业务逻辑来选择不同的数据源。动态数据源提供了这种灵活性,允许开发者根据运行时的条件来选择最合适的数据源。

  6. 故障转移和高可用性:当主数据库不可用时,动态切换数据源可以自动或手动切换到备用数据库,以保证服务的连续性和数据的可用性。

如何切换数据源?

SpringBoot版本:3.0.4

jdk版本:JDK17

1.pom文件

<dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
    </dependency>
    <!-- aop切面-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <!--druid连接池-->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid-spring-boot-starter</artifactId>
      <version>1.2.20</version>
    </dependency>
    <!--mysql驱动-->
    <dependency>
      <groupId>com.mysql</groupId>
      <artifactId>mysql-connector-j</artifactId>
    </dependency>
    <!--MybatisPlus-->
    <dependency>
      <groupId>com.baomidou</groupId>
      <artifactId>mybatis-plus-boot-starter</artifactId>
      <version>3.5.3.1</version>
    </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 BeanFactoryPostProcessor, ApplicationContextAware 
{
    /** 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 <T> T getBean(String name) throws BeansException
    {
        return (T) beanFactory.getBean(name);
    }

    /**
     * 获取类型为requiredType的对象
     *
     * @param clz
     * @return
     * @throws BeansException
     *
     */
    public static <T> T getBean(Class<T> 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 <T> T 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<Object, Object> 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<Object, Object> 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<Object, Object> 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<String> 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 Throwable{
        DataSource dataSource = getDataSource(joinPoint);
        if (dataSource != null){
            DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
        }
        try {
            return joinPoint.proceed();
        }
        finally {
            // 销毁数据源 在执行方法之后
            DynamicDataSourceContextHolder.clearDataSourceType();
        }
    }

    /**
     * 获取需要切换的数据源
     */
    public DataSource getDataSource(ProceedingJoinPoint point)
    {
        MethodSignature signature = (MethodSignature) point.getSignature();
        com.LYJ.study.DynamicDataSource.annocation.DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), com.LYJ.study.DynamicDataSource.annocation.DataSource.class);
        if (Objects.nonNull(dataSource)) {
            return dataSource;
        }
        return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
    }
}

10、在业务中使用

@Service
@RequiredArgsConstructor
@DataSource(value=DataSourceType.MASTER)
//@DataSource(value=DataSourceType.SLAVE)
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    private final UserMapper userMapper;
    @Override
    @DataSource(value=DataSourceType.MASTER)
    //@DataSource(value=DataSourceType.SLAVE)
    public List<User> queryAll() {
        return userMapper.selectList(null);
    }
}

我们在service、mapper的类和方法上使用都可以。 

补充:有很多从数据源怎么办? 

我们上面已经配置了一个从数据源了,接下来我们继续配置多个从数据源

首先在application-druid.yml文件添加新的数据源

在枚举添加数据源名称


//如果配置多数据源,继续添加即可
public enum DataSourceType
{
    /**
     * 主库
     */
    MASTER,

    /**
     * 从库
     */
    SLAVE,
    /**
     * 从库2
     */
    SLAVE2
}

 

如何切换数据库?

我们就以Oracle为例

 <!--oracle驱动-->
    <dependency>
      <groupId>com.oracle</groupId>
      <artifactId>ojdbc6</artifactId>
      <version>11.2.0.3</version>
    </dependency>

在application-druid.yml添加

      slave3:
        # 从数据源开关/默认关闭
        enabled: true
        url: jdbc:oracle:thin:@127.0.0.1:1521:oracle
        username: root
        password: password

 然后删除指定的mysql驱动,默认会自动寻找驱动

添加数据源和用法参考上面即可,都是一样的。 

注意:在切换数据库时,因为mysql跟Oracle的sql语法有差别,启动时可能报错。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1858102.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

国标GB28181视频汇聚平台EasyCVR设备展示数量和显示条数不符的原因排查与解决

国标GB28181/GA/T1400协议/安防综合管理系统EasyCVR视频汇聚平台能在复杂的网络环境中&#xff0c;将前端设备统一集中接入与汇聚管理。智慧安防/视频存储/视频监控/视频汇聚EasyCVR平台可以提供实时远程视频监控、视频录像、录像回放与存储、告警、语音对讲、云台控制、平台级…

API低代码平台介绍6-数据库记录删除功能

数据库记录删除功能 在前续文章中我们介绍了如何插入和修改数据库记录&#xff0c;本篇文章会沿用之前的测试数据&#xff0c;介绍如何使用ADI平台定义一个删除目标数据库记录的接口&#xff0c;包括 单主键单表删除、复合主键单表删除、多表删除&#xff08;整合前两者&#x…

aws的eks(k8s)ingress+elb部署实践

eks&#xff08;k8s&#xff09;版本1.29 ingress 版本1.10.0 负载均衡elb 1. 创建Ingress-Nginx服务 部署项目地址【点我跳转】推荐自定义部署 可绑定acm证书什么的自己属性 这里就是aws上面Certificate Manager产品上面创建证书 导入 创建都行 对应集群版本推荐阵列GitH…

YOLOv8 目标检测程序,依赖的库最少,使用onnxruntime推理

YOLOv8 目标检测程序&#xff0c;依赖的库最少&#xff0c;使用onnxruntime推理 flyfish 为了方便理解&#xff0c;加入了注释 """ YOLOv8 目标检测程序 Author: flyfish Date: Description: 该程序使用ONNX运行时进行YOLOv8模型的目标检测。它对输入图像进行…

尴尬时刻:如何在忘记名字时巧妙应对

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

代理IP知识:导致代理IP访问超时的原因有哪些?

很多用户在使用代理IP进行网络访问时&#xff0c;可能会遇到代理IP超时的情况&#xff0c;也就是代理IP的延迟过高。代理IP延迟过高会影响用户的网络体验和数据获取效率。因此&#xff0c;了解代理IP延迟过高的原因很重要。以下是导致代理IP延迟过高的一些常见原因&#xff1a;…

美容美发店营销版微信小程序源码

打造线上生意新篇章 一、引言&#xff1a;微信小程序&#xff0c;开启美容美发行业新纪元 在数字化时代&#xff0c;微信小程序以其便捷、高效的特点&#xff0c;成为了美容美发行业营销的新宠。本文将带您深入了解美容美发营销微信小程序&#xff0c;探讨其独特优势及如何助…

盘点5款最热门的AI绘画软件!总有一款是你的菜

在数字化艺术日益盛行的今天&#xff0c;AI绘画软件成为了创作者们的新宠。这些软件不仅能够帮助艺术家们快速生成独特的艺术作品&#xff0c;还能为普通用户带来全新的绘画体验。今天&#xff0c;我们就来盘点五款最热门的AI绘画软件&#xff0c;看看哪一款是你的菜&#xff0…

深度学习 --- stanford cs231学习笔记五(训练神经网络的几个重要组成部分之三,权重矩阵的初始化)

权重矩阵的初始化 3&#xff0c;权重矩阵的初始化 深度学习所学习的重点就是要根据损失函数训练权重矩阵中的系数。即便如此&#xff0c;权重函数也不能为空&#xff0c;总是需要初始化为某个值。 3&#xff0c;1 全都初始化为同一个常数可以吗&#xff1f; 首先要简单回顾一下…

技术干货 | AI驱动工程仿真和设计创新

在当今快速发展的技术领域&#xff0c;人工智能&#xff08;AI&#xff09;、机器学习和深度学习等技术已经成为推动工程仿真和设计创新的关键力量。Altair技术经理张晨在Altair “AI FOR ENGINEERS”线下研讨会上发表了相关精彩演讲&#xff0c;本文摘自演讲内容&#xff0c;与…

数字化校园平台:引领教育创新的智慧之选

数字化校园平台是信息化技术与传统教育深度结合的产物。在当今这个信息技术日新月异的时代&#xff0c;数字化校园平台正逐渐崭露头角&#xff0c;成为教育领域一股不可小觑的革新力量。它如同一座桥梁&#xff0c;连接起教育资源的各个角落&#xff0c;将繁杂的教学材料、珍贵…

猫狗识别—视频识别

猫狗识别—视频识别 1. 导入所需的库&#xff1a;2. 创建Tkinter主窗口并设置标题&#xff1a;3. 设置窗口的宽度和高度&#xff1a;4. 创建一个Canvas&#xff0c;它将用于显示视频帧&#xff1a;5. 初始化一个视频流变量cap&#xff0c;用于存储OpenCV的视频捕获对象&#xf…

Matlab要这样批量读取txt数据!科研效率UpUp第10期

假如我们有多组txt格式的数据&#xff1a; 其数据格式是这样的&#xff1a; 想要批量读取这些数据&#xff0c;并把他们画在一张图上&#xff0c;该怎么操作呢&#xff1f; ​之前有分享load函数的版本&#xff0c;本期进一步分享适用性更强的readtable函数的实现方法​。 首…

工业的物联网在构建弹性供应链系统中的作用

物联网 (IoT) 可以显着提高供应链系统的效率&#xff0c;因为物联网处理设备之间的连接。简而言之&#xff0c;物联网转化为“连接设备”&#xff0c;物联网的这种能力导致了智能系统或环境。物联网将这些设备与传感器和执行器连接起来&#xff0c;这些传感器和执行器收集数据并…

【计算机网络仿真】b站湖科大教书匠思科Packet Tracer——实验8 IPv4地址 — 分类地址

一、实验目的 1.验证分类IP地址的作用&#xff1b; 2.初步了解路由器的功能。 二、实验要求 1.使用Cisco Packet Tracer仿真平台&#xff1b; 2.观看B站湖科大教书匠仿真实验视频&#xff0c;完成对应实验。 三、实验内容 1.构建网络拓扑&#xff1b; 2.修改网络拓扑&…

原创作品—工业软件界面设计作品

在工业4.0时代&#xff0c;界面设计不仅要追求美观&#xff0c;更要以用户体验为核心。通过简化操作流程、优化交互逻辑&#xff0c;降低用户的学习成本&#xff0c;提高使用效率。这样的设计能够为企业数字化转型提供有力支持&#xff0c;增强用户对产品的黏性。 数字化转型的…

云盘高速视觉检测机如何提升螺丝尺寸检测效率?

螺丝&#xff0c;一种用来连接和固定物体的金属件&#xff0c;通常是长有螺纹的金属棒。螺丝有不同种类和尺寸&#xff0c;常见的用途包括组装家具、机械设备和其他结构。连接和固定物体&#xff0c;通过螺丝的螺纹结构&#xff0c;将两个或多个物体牢固地连接在一起。提供调节…

LabVIEW与C#相互调用dll

C#调用LabVIEW创建的dll 我先讲LabVIEW创建自己的.net类库的方法吧&#xff0c;重点是创建&#xff0c;C#调用的步骤&#xff0c;大家可能都很熟悉了。 1、创建LabVIEW项目&#xff0c;并创建一个简单的add.vi&#xff0c;内容就是abc&#xff0c;各个接线端都正确连接就好。 …

一种改进解卷积算法在旋转机械故障诊断中的应用(MATLAB)

轴承振动是随机振动。在不同的时刻&#xff0c;轴承振动值是不尽相同的&#xff0c;不能用一个确定的时间函数来描述。这是由于滚动体除了有绕轴承公转运动以外&#xff0c;还有绕自身轴线的自旋运动&#xff0c;且在轴承运转时&#xff0c;滚动接触表面形貌是不断变化的&#…

大脑网路分析的进展:基于大规模自监督学习的诊断| 文献速递-先进深度学习疾病诊断

Title 题目 BrainMass: Advancing Brain Network Analysis for Diagnosis with Large-scale Self-Supervised Learning 大脑网路分析的进展&#xff1a;基于大规模自监督学习的诊断 01 文献速递介绍 功能性磁共振成像&#xff08;fMRI&#xff09;利用血氧水平依赖&#x…