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: 8088spring:datasource:druid:type: com.alibaba.druid.pool.DruidDataSourcemaster:url: jdbc:mysql://192.168.26.4:3306/test01?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghaiusername: rootpassword: rootdriver-class-name: com.mysql.jdbc.Driverslave:url: jdbc:mysql://192.168.26.8:3306/test01?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghaiusername: rootpassword: rootdriver-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@Primarypublic 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;/*** 决定当前线程使用哪个数据源*/@Overrideprotected 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;@Overridepublic 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的实现层直接在方法上添加注解指定数据源,如:

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

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JCIM | 在gromacs中进行恒定ph模拟

恒定pH分子动力学(MD)是一种强大的技术&#xff0c;可以动态地改变残留物的质子化状态&#xff0c;从而能够以一种以前不可能实现的方式研究pH相关性。最近&#xff0c;这样一项技术引入到了Gromacs中。为了简化和自动化设置此过程&#xff0c;来自瑞典的研究团队提出了一个名为…

【机器学习】调配师:咖啡的完美预测

有一天&#xff0c;小明带着一脸期待找到了你这位数据分析大师。他掏出手机&#xff0c;屏幕上展示着一份详尽的Excel表格。“看&#xff0c;这是我咖啡店过去一年的数据。”他滑动着屏幕&#xff0c;“每个月的销售量、广告投入&#xff0c;还有当月的气温&#xff0c;我都记录…

Yolov8_使用自定义数据集训练模型1

前面几篇文章介绍了如何搭建Yolov8环境、使用默认的模型训练和推理图片及视频的效果、并使用GPU版本的torch加速推理、导出.engine格式的模型进一步利用GPU加速&#xff0c;本篇介绍如何自定义数据集&#xff0c;这样就可以训练出识别特定物体的模型。 《Yolov8_使用自定义数据…

细说JavaScript BOM之window常用子对象

一、location location翻译过来就是位置的意思&#xff0c;打开浏览器窗口大家可以看到导航栏上有一个URL地址。 // 例如 https://www.zhishunet.com// 分心可知&#xff0c;它使用的网络协议是https 服务器名称是 www,zhishunet.comlocation常用对象属性 属性描述search设置…

用Pytorch实现线性回归模型

目录 回顾Pytorch实现步骤1. 准备数据2. 设计模型class LinearModel代码 3. 构造损失函数和优化器4. 训练过程5. 输出和测试完整代码 练习 回顾 前面已经学习过线性模型相关的内容&#xff0c;实现线性模型的过程并没有使用到Pytorch。 这节课主要是利用Pytorch实现线性模型。…

UE5 C++ 学习笔记 UBT UHT 和 一些头文件

总结一些似懂非懂的知识点&#xff0c;从头慢慢梳理。 任何一个项目都有创建这些三个.cs。 这个是蓝图转C 这个是本身就是C项目,应该就是多了一个GameModeBase类 Build.cs包含了每个模块的信息&#xff0c;表明了这个项目用到了哪一些模块。该文件里的using UnrealBuilTool 是…

学习笔记之——3D Gaussian SLAM,SplaTAM配置(Linux)与源码解读

SplaTAM全称是《SplaTAM: Splat, Track & Map 3D Gaussians for Dense RGB-D SLAM》&#xff0c;是第一个&#xff08;也是目前唯一一个&#xff09;开源的用3D Gaussian Splatting&#xff08;3DGS&#xff09;来做SLAM的工作。 在下面博客中&#xff0c;已经对3DGS进行了…

ELK 分离式日志

目录 一.ELK组件 ElasticSearch&#xff1a; Kiabana&#xff1a; Logstash&#xff1a; 可以添加的其它组件&#xff1a; ELK 的工作原理&#xff1a; 二.部署ELK 节点都设置Java环境: 每台都可以部署 Elasticsearch 软件&#xff1a; 修改elasticsearch主配置文件&…

LeetCode、2462. 雇佣 K 位工人的总代价【中等,最小堆+双指针】

文章目录 前言LeetCode、2462. 雇佣 K 位工人的总代价【中等&#xff0c;最小堆双指针】题目及类型思路及代码实现 资料获取 前言 博主介绍&#xff1a;✌目前全网粉丝2W&#xff0c;csdn博客专家、Java领域优质创作者&#xff0c;博客之星、阿里云平台优质作者、专注于Java后…

Linux CentOS stream9 nmcli

nmcli命令是redhat7或者centos7之后的命令&#xff0c;该命令可以完成网卡上所有的配置工作&#xff0c;并且可以写入配置文件&#xff0c;永久生效。 一、前期准备 在讨论、学习与训练nmcli命令前&#xff0c;必须明确几点&#xff1a; 1.开启NetworkManager 使用nmcli命令…

2024.1.19每日一题

LeetCode 2809.使数组和小于等于x的最少时间 2809. 使数组和小于等于 x 的最少时间 - 力扣&#xff08;LeetCode&#xff09; 题目描述 给你两个长度相等下标从 0 开始的整数数组 nums1 和 nums2 。每一秒&#xff0c;对于所有下标 0 < i < nums1.length &#xff0c…

C#开源跨平台的多功能Steam工具箱GitHub加速神器

前言 作为一名程序员你是否会经常会遇到GitHub无法访问(如下无法访问图片)&#xff0c;或者是访问和下载源码时十分缓慢就像乌龟爬行一般。今天分享一款C#开源的、跨平台的多功能Steam工具箱和GitHub加速神器&#xff1a;Watt Toolkit。 项目功能 网络加速、账号切换、库存游…