springBoot如何动态切换数据源

项目背景:最近公司中需要搭建mysql的主从,想着在spring中集成多数据源。mybatisplus提供的有插件用@DS注解就能够实现,但是这种在mysql服务宕机的情况下不能够进行自动切换,于是就想着用aop+自定义注解的方式来实现

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

注意:为了节省篇幅,向controller、service层就不展示出来了,只展示相关核心代码。

1、pom文件


<dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-web</artifactId>
       </dependency>
 
       <!-- aop 切面 -->
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-aop</artifactId>
       </dependency>
 
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-jdbc</artifactId>
       </dependency>
 
 
       <dependency>
           <groupId>org.projectlombok</groupId>
           <artifactId>lombok</artifactId>
           <optional>true</optional>
       </dependency>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-test</artifactId>
           <scope>test</scope>
       </dependency>
       <!-- druid -->
       <!-- https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter -->
       <dependency>
           <groupId>com.alibaba</groupId>
           <artifactId>druid-spring-boot-starter</artifactId>
           <version>1.2.8</version>
       </dependency>
       <!--主从配置依赖-->
       <dependency>
           <groupId>com.baomidou</groupId>
           <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
           <version>2.4.2</version>
       </dependency>
       <dependency>
           <groupId>mysql</groupId>
           <artifactId>mysql-connector-java</artifactId>
           <version>8.0.21</version>
       </dependency>
       <dependency>
           <groupId>com.baomidou</groupId>
           <artifactId>mybatis-plus-boot-starter</artifactId>
           <version>3.2.0</version>
       </dependency>
       <!--mybatis-plus生成器-->
       <dependency>
           <groupId>com.baomidou</groupId>
           <artifactId>mybatis-plus-generator</artifactId>
           <version>3.3.2</version>
       </dependency>
       <dependency>
           <groupId>commons-lang</groupId>
           <artifactId>commons-lang</artifactId>
           <version>2.6</version>
       </dependency>
 
       <!-- 模板引擎 -->
       <dependency>
           <groupId>org.apache.velocity</groupId>
           <artifactId>velocity-engine-core</artifactId>
           <version>2.0</version>
       </dependency>
 
       <dependency>
           <groupId>log4j</groupId>
           <artifactId>log4j</artifactId>
           <version>1.2.14</version>
       </dependency>

2、配置文件:application.yml

server:
  port: 8088
 
spring:
  datasource:
    druid:
      type: com.alibaba.druid.pool.DruidDataSource
      master:
        url: jdbc:mysql://192.168.26.4:3306/test01?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
        username: root
        password: root
        driver-class-name: com.mysql.jdbc.Driver
      slave:
        url: jdbc:mysql://192.168.26.8:3306/test01?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
        username: root
        password: root
        driver-class-name: com.mysql.jdbc.Driver

3、数据源名称枚举类CommonConstant:

public class CommonConstant {
    /**
     * 默认数据源标识
     */
    public static final String MASTER = "master";
    /**
     * 从数据源标识
     */
    public static final String SLAVE = "slave";
}

4 数据源解析类DruidConfig:

@Data
@Configuration
public class DruidConfig {
  
    @Bean(name = CommonConstant.MASTER)
    @ConfigurationProperties("spring.datasource.druid.master")
    public DataSource masterDataSource()
    {
        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
        return dataSource;
    }
  
    @Bean(name = CommonConstant.SLAVE)
    @ConfigurationProperties("spring.datasource.druid.slave")
    public DataSource slaveDataSource()
    {
        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
        return dataSource;
    }
  
    @Bean
    @Primary
    public DynamicDataSource dynamicDataSource()
    {
        Map<Object, Object> dataSourceMap = new HashMap<>(2);
        dataSourceMap.put(CommonConstant.MASTER,masterDataSource());
        dataSourceMap.put(CommonConstant.SLAVE,slaveDataSource());
        //设置动态数据源
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        dynamicDataSource.setDefaultTargetDataSource(masterDataSource());
        dynamicDataSource.setTargetDataSources(dataSourceMap);
        //将数据源信息备份在defineTargetDataSources中
        dynamicDataSource.setDefineTargetDataSources(dataSourceMap);
        return dynamicDataSource;
    }
}

5、DynamicDataSource类

编写DynamicDataSource类继承AbstractRoutingDataSource类并重写抽象方法determineCurrentLookupKey以此来决定当前线程使用哪个数据源

/**
 * 动态数据源
 * 调用AddDefineDataSource组件的addDefineDynamicDataSource()方法,获取原来targetdatasources的map,并将新的数据源信息添加到map中,并替换targetdatasources中的map
 * 切换数据源时可以使用@DataSource(value = "数据源名称"),或者DynamicDataSourceContextHolder.setContextKey("数据源名称")
 * @author zhangyu
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DynamicDataSource extends AbstractRoutingDataSource {
 
    //备份所有数据源信息,
    private Map<Object, Object> defineTargetDataSources;
  
    /**
     * 决定当前线程使用哪个数据源
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceHolder.getDynamicDataSourceKey();
    }
 
}

6、DynamicDataSourceHolder

DynamicDataSourceHolder类主要是设置当前线程的数据源名称,移除数据源名称,以及获取当前数据源的名称,便于动态切换

/**
 * 数据源切换处理
 *
 * @author zhangyu
 */
@Slf4j
public class DynamicDataSourceHolder {
    /**
     * 保存动态数据源名称
     */
    private static final ThreadLocal<String> DYNAMIC_DATASOURCE_KEY = new ThreadLocal<>();
  
    /**
     * 设置/切换数据源,决定当前线程使用哪个数据源
     */
    public static void setDynamicDataSourceKey(String key){
        log.info("数据源切换为:{}",key);
        DYNAMIC_DATASOURCE_KEY.set(key);
    }
  
    /**
     * 获取动态数据源名称,默认使用mater数据源
     */
    public static String getDynamicDataSourceKey(){
        String key = DYNAMIC_DATASOURCE_KEY.get();
        return key == null ? CommonConstant.MASTER : key;
    }
  
    /**
     * 移除当前数据源
     */
    public static void removeDynamicDataSourceKey(){
        log.info("移除数据源:{}",DYNAMIC_DATASOURCE_KEY.get());
        DYNAMIC_DATASOURCE_KEY.remove();
    }
}

7、自定义注解

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

8 aop切面

import com.alibaba.druid.pool.DruidDataSource;
import com.liubujun.config.*;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
 
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.*;
 
@Aspect
@Component
@Slf4j
public class DataSourceAspect {
  
    // 设置DataSource注解的切点表达式
//    @Pointcut("@annotation(com.liubujun.config.aespect.DataSource)")
    @Pointcut("execution(public * com.liubujun.service..*.*(..))")
    public void dynamicDataSourcePointCut(){
  
    }
  
    //环绕通知
    @Around("dynamicDataSourcePointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        DataSource defineAnnotation = getDefineAnnotation(joinPoint);
        String key = "";
        //判断方法上是否有注解,没有注解则默认是走的是主服务器
        if (defineAnnotation == null ) {
            key = CommonConstant.MASTER;
        }else {
            key = defineAnnotation.value();
        }
        //判断数据库是否断开连接
        key = getConnection(key);
        DynamicDataSourceHolder.setDynamicDataSourceKey(key);
        Object proceed = null;
        try {
            proceed =  joinPoint.proceed();
        } finally {
            DynamicDataSourceHolder.removeDynamicDataSourceKey();
        }
        return proceed;
    }
 
    /**
     * 先判断方法的注解,后判断类的注解,以方法的注解为准
     * @param joinPoint
     * @return
     */
    private DataSource getDefineAnnotation(ProceedingJoinPoint joinPoint){
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        DataSource dataSourceAnnotation = methodSignature.getMethod().getAnnotation(DataSource.class);
        if (Objects.nonNull(methodSignature)) {
            return dataSourceAnnotation;
        } else {
            Class<?> dsClass = joinPoint.getTarget().getClass();
            return dsClass.getAnnotation(DataSource.class);
        }
    }
 
    /**
     * 判断数据库是否连接成功
     * @return
     */
    private String getConnection(String target) throws SQLException {
        //将数据源名称添加到list集合,方便后续操作
        List<String> dataSources = new ArrayList<>();
        dataSources.add(CommonConstant.SLAVE);
        dataSources.add(CommonConstant.MASTER);
        //获取装配好的bean对象
        DruidConfig druidConfig = (DruidConfig)SpringUtil.getBean("druidConfig");
        DruidDataSource druidDataSource = new DruidDataSource();
        if (target.equals(CommonConstant.SLAVE)) {
             druidDataSource = (DruidDataSource) druidConfig.slaveDataSource();
        }
        if (target.equals(CommonConstant.MASTER)) {
            druidDataSource = (DruidDataSource) druidConfig.masterDataSource();
        }
        try {
            Connection connection = DriverManager.getConnection(druidDataSource.getUrl(), druidDataSource.getUsername(), druidDataSource.getPassword());
        } catch (SQLException e) {
            dataSources.remove(target);
            // shuffle 打乱顺序
            Collections.shuffle(dataSources);
            String changeTarget = dataSources.get(0);
            getConnection(changeTarget);
            log.info("========================数据源:{}连接异常,切换数据源为:{}===========================",target,changeTarget);
            return changeTarget;
        }
        return target;
    }
  
}

9 获取bean对象工具类

@Component
public class SpringUtil implements ApplicationContextAware {
  
    private static ApplicationContext applicationContext;
  
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if(SpringUtil.applicationContext == null) {
            SpringUtil.applicationContext = applicationContext;
        }
    }
  
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }
     //根据类名获取指定对象
    public static Object getBean(String name){
        return getApplicationContext().getBean(name);
    }
     //根据类型获取指定对象
    public static <T> T getBean(Class<T> clazz){
        return getApplicationContext().getBean(clazz);
    }
     //根据类名和类型获取指定对象
    public static <T> T getBean(String name,Class<T> clazz){
        return getApplicationContext().getBean(name, clazz);
    }
}

以上就是在代码层面动态切换数据源的相关代码,那么如何使用呢?

可以直接在业务service的实现层直接在方法上添加注解指定数据源,如:

我在这个方法上指定的是从数据库,如果此时从数据库发生宕机,那么就会自动切换到主数据库进行操作 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

el-tree获取当前选中节点及其所有父节点的id(包含半选中父节点的id)

如下图,我们现在全勾中的有表格管理及其下的子级,而半勾中的有工作台和任务管理及其子级 现在点击保存按钮后,需要将勾中的节点id及该节点对应的父节点,祖先节点的id(包含半选中父节点的id)也都一并传给后端,那这个例子里就应该共传入9个id,我们可以直接将getCheckedK…

架构篇07-复杂度来源:低成本、安全、规模

文章目录 低成本安全规模小结关于复杂度来源,前面的专栏已经讲了高性能、高可用和可扩展性,今天我们来聊聊复杂度另外三个来源低成本、安全和规模。 低成本 当我们的架构方案只涉及几台或者十几台服务器时,一般情况下成本并不是我们重点关注的目标,但如果架构方案涉及几百…

【Docker】在centos中安装nginx

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是平顶山大师&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的博客专栏《【Docker】安装nginx》。&#x1f3af;&#…

Macos flatter(用于快速LLL)本地编译安装(解决安装过程各种疑难杂症)

flatter是一个开源项目&#xff0c;能大大提高LLL的速度&#xff0c;项目提供的安装文档适用于Ubuntu&#xff0c;但是在macos上安装&#xff0c;总会遇到各种各样的问题&#xff0c;这里记录下所踩坑&#xff0c;帮助大家快速在macos上安装flatter。 文章目录 1.安装依赖库&am…

【linux进程间通信(一)】匿名管道和命名管道

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:Linux从入门到精通⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学更多操作系统知识   &#x1f51d;&#x1f51d; 进程间通信 1. 前言2. 进程间…

计算机毕业设计 基于SpringBoot的红色革命文物征集管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

CSS 实现卡片以及鼠标移入特效

CSS 实现卡片以及鼠标移入特效 文章目录 CSS 实现卡片以及鼠标移入特效0、效果预览默认鼠标移入后 1、创建卡片组件2、添加样式3、完整代码 0、效果预览 默认 鼠标移入后 在本篇博客中&#xff0c;我们将探讨如何使用 CSS 来实现卡片组件&#xff0c;并添加鼠标移入特效&#…

第五回 九纹龙剪径赤松林 鲁智深火烧瓦罐寺 Webmin傻瓜式配置服务器

话说鲁智深走过数个山坡&#xff0c;看见一所败落的寺庙&#xff0c;上写“瓦罐之寺”。庙里空无一人&#xff0c;于是鲁智深径直走到后面厨房&#xff0c;发现有几个老和尚。鲁智深向和尚们讨要饭食&#xff0c;和尚们初时推说没有。然而&#xff0c;鲁智深眼尖&#xff0c;发…

【Python程序开发系列】一文搞懂argparse模块的常见用法(案例+源码)

一、引言 argsparse是python的命令行解析的标准模块&#xff0c;内置于python&#xff0c;不需要安装。这个库可以让我们直接在命令行中就可以向程序中传入参数并让程序运行。 在运行深度学习程序时。往往会因为电脑配置不行导致程序运行慢卡&#xff0c;需要将程序在虚机上进行…

【React基础】– JSX语法

文章目录 认识JSX为什么React选择了JSXJSX的使用 React事件绑定this的绑定问题事件参数传递 React条件渲染React列表渲染列表中的key JSX的本质createElement源码Babel官网查看直接编写jsx代码 虚拟DOM的创建过程jsx – 虚拟DOM – 真实DOM声明式编程 阶段案例练习 认识JSX ◼ …

手动添加测试用例配置输入参数和期望值

1.选中函数&#xff0c;点击右键选择插入测试用例。这里所选择的插入测试用例区别于之前的测试用例的地方在于&#xff0c;这里插入测试用例是手动配置的&#xff0c;之前的是自动生成的。手动配置可以自定义选择输入参数和期望值。 2.添加测试用例后&#xff0c;点击测试用例&…

高光谱分类论文解读分享之Grid Network: 基于各向异性视角下特征提取的高光谱影像分类

IEEE GRSL 2023&#xff1a;Grid Network: 基于各向异性视角下特征提取的高光谱影像分类 题目 Grid Network: Feature Extraction in Anisotropic Perspective for Hyperspectral Image Classification 作者 Zhonghao Chen , Student Member, IEEE, Danfeng Hong , Senior …

Conmi的正确答案——使用eclipse进行ESP32C3的debug

eclipse IDE 版本:2023-12 1、安装debug环境 参考大神的教程:【图文】手把手教你使用 Eclipse IDE 开发 ESP32 (这里是为了我下次回来速通才写的部分) 1.1、安装插件(plug-in,新的软件已经写成software了): 相关软件参数: 汉化(安装完成会提示重启应用): Name:…

【前后端的那些事】15min快速实现图片上传,预览功能(ElementPlus+Springboot)

文章目录 Element Plus SpringBoot实现图片上传&#xff0c;预览&#xff0c;删除效果展示 1. 后端代码1.1 controller1.2 service 2. 前端代码2.1 路由创建2.2 api接口2.2 文件创建 3. 前端上传组件封装 前言&#xff1a;最近写项目&#xff0c;发现了一些很有意思的功能&…

node.js 实现文件上传 和图片映射 文件下载(multer)

Muter是一个node.js中间件。主要处理multupart/from-data类型的表单数据&#xff0c;常用于上传文件。在express.js应用中&#xff0c;multer使得上传文件变得更加简单。主要功能是将客户端上传的文件存储在服务器的本地文件系统中。它还添加了一个body对象以及file或files对象…

qnx 上screen + egl + opengles 最简实例

文章目录 前言一、qnx 上的窗口系统——screen二、screen + egl + opengles 最简实例1.使用 addvariant 命令创建工程目录2. 添加源码文件3. common.mk 文件4. 编译与执行总结参考资料前言 本文主要介绍如何在QNX 系统上使用egl和opengles 控制GPU渲染一个三角形并显示到屏幕上…

数据结构与算法-二叉树-从前序与中序遍历序列构造二叉树

从前序与中序遍历序列构造二叉树 给定两个整数数组 preorder 和 inorder &#xff0c;其中 preorder 是二叉树的先序遍历&#xff0c; inorder 是同一棵树的中序遍历&#xff0c;请构造二叉树并返回其根节点。 示例 1: 输入: preorder [3,9,20,15,7], inorder [9,3,15,20,7…

Ubuntu18.04在线镜像仓库配置

在线镜像仓库 1、查操作系统版本 rootubuntu:~# lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 18.04.5 LTS Release: 18.04 Codename: bionic 2、原文件备份 sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak 3、查…

深入理解Linux文件系统

&#x1f3ac;慕斯主页&#xff1a;修仙—别有洞天 ♈️今日夜电波&#xff1a;晴る—ヨルシカ 0:20━━━━━━️&#x1f49f;──────── 4:30 &#x1f504; ◀️ ⏸ ▶️ ☰ &…

宋仕强论道之华强北的领军企业(四十六)

华强北曾经的3发展是因为领军企业的带动而蓬勃发展&#xff0c;当年的华强集团、赛格集团、桑达集团和华发集团等是其中的代表&#xff0c;但现在他们失去了曾经的带头大哥的地位。其他的例子&#xff0c;如腾讯对于深圳市南山区科技园的带动&#xff0c;华为对龙岗坂田雪象岗头…