在Spring Boot项目中通过自定义注解实现多数据源以及主备数据库切换

在现代的企业应用开发中,使用多数据源是一个常见的需求。尤其在关键应用中,设置主备数据库可以提高系统的可靠性和可用性。在这篇博客中,我将展示如何在Spring Boot项目中通过自定义注解实现多数据源以及主备数据库切换。

在此说明

我这里以dm6、dm7来举例多数据源 ,以两个dm6来举例主备数据库,基本大部分数据库都通用,举一反三即可。

对于dm6不熟悉但是又要用的可以看我这篇博客

Spring Boot项目中使用MyBatis连接达梦数据库6

1. 环境依赖

首先,确保你的Spring Boot项目中已经添加了以下依赖:

  <!-- Lombok依赖,用于简化Java代码 --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!-- MyBatis Spring Boot Starter依赖,用于集成MyBatis和Spring Boot --><!-- 注意:这里使用1.3.0版本,因为DM6不支持1.3以上版本 --><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>1.3.0</version></dependency><!-- Spring Boot Starter AOP依赖,用于实现AOP功能 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><!-- DM6 JDBC驱动,用于连接DM6数据库 --><dependency><groupId>com.github.tianjing</groupId><artifactId>Dm6JdbcDriver</artifactId><version>1.0.0</version></dependency><!-- DM8 JDBC驱动,用于连接DM8数据库 --><dependency><groupId>com.dameng</groupId><artifactId>DmJdbcDriver18</artifactId><version>8.1.3.62</version></dependency><!-- Hutool工具类库,用于简化Java开发 --><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.27</version></dependency>

2. 配置文件

spring.datasource:dmprimary:driver-class-name: dm6.jdbc.driver.DmDriver # 驱动类名称,用于连接 DM6 数据库jdbc-url: jdbc:dm6://localhost:12345/xxxx  # JDBC URL,指定 DM6 数据库的地址和端口username: xxxx  # 数据库用户名password: xxxxxxx  # 数据库密码connection-test-query: select 1  # 用于测试数据库连接的查询语句type: com.zaxxer.hikari.HikariDataSource  # 使用 HikariCP 作为连接池实现maximum-pool-size: 8  # 最大连接池大小minimum-idle: 2  # 最小空闲连接数idle-timeout: 600000  # 空闲连接的超时时间,单位毫秒max-lifetime: 1800000  # 连接的最大生命周期,单位毫秒connection-timeout: 3000  # 获取连接的超时时间,单位毫秒validation-timeout: 3000  # 验证连接的超时时间,单位毫秒initialization-fail-timeout: 1  # 初始化失败时的超时时间,单位毫秒leak-detection-threshold: 0  # 连接泄漏检测的阈值,单位毫秒dmbackup:driver-class-name: dm6.jdbc.driver.DmDriverjdbc-url: jdbc:dm6://8.8.8.8:12345/xxxxusername: xxxxxxxpassword: xxxxxconnection-test-query: select 1type: com.zaxxer.hikari.HikariDataSourcemaximum-pool-size: 8minimum-idle: 2idle-timeout: 600000max-lifetime: 1800000connection-timeout: 30000validation-timeout: 5000initialization-fail-timeout: 1leak-detection-threshold: 0dm7:driver-class-name: dm.jdbc.driver.DmDriverjdbc-url: jdbc:dm://localhost:5236/xxxxpassword: xxxxxxxxxusername: xxxxxxconnection-test-query: select 1type: com.zaxxer.hikari.HikariDataSourcemaximum-pool-size: 10minimum-idle: 2idle-timeout: 600000max-lifetime: 1800000connection-timeout: 30000validation-timeout: 5000initialization-fail-timeout: 1leak-detection-threshold: 0mybatis:mapper-locations: classpath:/mappers/*.xml  # 修改为你的 MyBatis XML 映射文件路径configuration:#    log-impl: org.apache.ibatis.logging.stdout.StdOutImplmap-underscore-to-camel-case: true

3. 定义数据源相关的常量

/*** 定义数据源相关的常量* @Author: 阿水* @Date: 2024-05-24*/
public interface DataSourceConstant {String DB_NAME_DM6 = "dm";String DB_NAME_DM6_BACKUP = "dmBackup";String DB_NAME_DM7 = "dm7";
}

4. 创建自定义注解

 
import java.lang.annotation.*;
/*** 数据源切换注解* @Author: 阿水* @Date: 2024-05-24*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DataSource {String value() default DataSourceConstant.DB_NAME_DM6;}

5. 动态数据源类

/*** 动态数据源类* @Author: 阿水* @Date: 2024-05-24*/
public class DynamicDataSource extends AbstractRoutingDataSource {@Overrideprotected Object determineCurrentLookupKey() {return DataSourceUtil.getDB();}
}

动态数据源切换的核心实现

在多数据源配置中,我们需要一个类来动态决定当前使用的数据源,这就是 DynamicDataSource 类。它继承自 Spring 提供的 AbstractRoutingDataSource,通过覆盖 determineCurrentLookupKey 方法,从 ThreadLocal 中获取当前数据源的标识符,并返回该标识符以决定要使用的数据源。

6. 数据源工具类

/*** 数据源工具类* @Author: 阿水* @Date: 2024-05-24*/
public class DataSourceUtil {/***  数据源属于一个公共的资源*  采用ThreadLocal可以保证在多线程情况下线程隔离*/private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();/*** 设置数据源名* @param dbType*/public static void setDB(String dbType) {contextHolder.set(dbType);}/*** 获取数据源名* @return*/public static String getDB() {return (contextHolder.get());}/*** 清除数据源名*/public static void clearDB() {contextHolder.remove();}
}

7. 数据源配置类


/*** 数据源配置类,用于配置多个数据源,并设置动态数据源。* @Author: 阿水* @Date: 2024-05-24*/
@Configuration
public class DataSourceConfig {@Bean(name = "primaryDataSource")@ConfigurationProperties(prefix = "spring.datasource.dmprimary")public DataSource primaryDataSource() {return DataSourceBuilder.create().build();}@Bean(name = "backupDataSource")@ConfigurationProperties(prefix = "spring.datasource.dmbackup")public DataSource backupDataSource() {return DataSourceBuilder.create().build();}@Bean(name = "dm7")@ConfigurationProperties(prefix = "spring.datasource.dm7")public DataSource dataSourceDm7() {return DataSourceBuilder.create().build();}/*** 配置动态数据源,将多个数据源加入到动态数据源中* 设置 primaryDataSource 为默认数据源*/@Primary@Bean(name = "dynamicDataSource")public DataSource dynamicDataSource() {DynamicDataSource dynamicDataSource = new DynamicDataSource();dynamicDataSource.setDefaultTargetDataSource(primaryDataSource());Map<Object, Object> dsMap = new HashMap<>();dsMap.put(DataSourceConstant.DB_NAME_DM6, primaryDataSource());dsMap.put(DataSourceConstant.DB_NAME_DM6_BACKUP, backupDataSource());dsMap.put(DataSourceConstant.DB_NAME_DM7, dataSourceDm7());dynamicDataSource.setTargetDataSources(dsMap);return dynamicDataSource;}/*** 配置事务管理器,使用动态数据源*/@Beanpublic PlatformTransactionManager transactionManager() {return new DataSourceTransactionManager(dynamicDataSource());}
}

8. 数据源切换器

 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
/*** 数据源切换器* @Author: 阿水* @Date: 2024-05-24*/@Configuration
public class DataSourceSwitcher extends AbstractRoutingDataSource {@Autowiredprivate DataSource primaryDataSource;@Autowiredprivate DataSource backupDataSource;private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();@PostConstructpublic void init() {this.setDefaultTargetDataSource(primaryDataSource);Map<Object, Object> dataSourceMap = new HashMap<>();dataSourceMap.put("primary", primaryDataSource);dataSourceMap.put("backup", backupDataSource);this.setTargetDataSources(dataSourceMap);this.afterPropertiesSet();}@Overrideprotected Object determineCurrentLookupKey() {return CONTEXT_HOLDER.get();}public static void setDataSource(String dataSource) {CONTEXT_HOLDER.set(dataSource);}public static void clearDataSource() {CONTEXT_HOLDER.remove();}public boolean isPrimaryDataSourceAvailable() {return isDataSourceAvailable(primaryDataSource);}public boolean isBackupDataSourceAvailable() {return isDataSourceAvailable(backupDataSource);}private boolean isDataSourceAvailable(DataSource dataSource) {try (Connection connection = dataSource.getConnection()) {return true;} catch (RuntimeException | SQLException e) {return false;}}
}

这个类通过继承 AbstractRoutingDataSource 实现了动态数据源切换的功能。它使用 ThreadLocal 变量实现线程隔离的数据源标识存储,并提供了设置和清除当前数据源的方法。在 Bean 初始化时,它将主数据源设为默认数据源,并将主数据源和备用数据源添加到数据源映射中。该类还提供了检查数据源可用性的方法,通过尝试获取连接来判断数据源是否可用。

这个类是实现动态数据源切换的核心部分,配合 Spring AOP 可以实现基于注解的数据源切换逻辑,从而实现多数据源和主备数据库的切换功能。

9. AOP切面类


import cn.hutool.core.util.ObjUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import java.util.Objects;
/*** AOP切面* @Author: 阿水* @Date: 2024-05-24*/
@Aspect
@Component
@Slf4j
@EnableAspectJAutoProxy
public class DataSourceAspect {@Autowiredprivate DataSourceSwitcher dataSourceSwitcher;@Autowiredprivate TimeCacheConfig cacheConfig;@Pointcut("@annotation(com.lps.config.DataSource) || @within(com.lps.config.DataSource)")public void dataSourcePointCut() {}/*** AOP环绕通知,拦截标注有@DataSource注解的方法或类* @param point 连接点信息* @return 方法执行结果* @throws Throwable 异常信息*/@Around("dataSourcePointCut()")public Object around(ProceedingJoinPoint point) throws Throwable {// 获取需要切换的数据源DataSource dataSource = getDataSource(point);log.info("初始数据源为{}", dataSource != null ? dataSource.value() : "默认数据源");// 设置数据源if (dataSource != null) {DataSourceUtil.setDB(dataSource.value());}// 处理主数据源逻辑if (DataSourceUtil.getDB().equals(DataSourceConstant.DB_NAME_DM6)) {handlePrimaryDataSource();}// 获取当前数据源String currentDataSource = DataSourceUtil.getDB();log.info("最终数据源为{}", currentDataSource);try {// 执行被拦截的方法return point.proceed();} finally {// 清除数据源DataSourceUtil.clearDB();log.info("清除数据源");}}/*** 处理主数据库的数据源切换逻辑*/private void handlePrimaryDataSource() {// 检查缓存中是否有主数据库挂掉的标记if (ObjUtil.isNotEmpty(cacheConfig.timeCacheHc().get("dataSource", false))) {// 切换到备用数据源DataSourceUtil.setDB(DataSourceConstant.DB_NAME_DM6_BACKUP);log.info("切换到备用数据源");} else {// 检查主数据库状态并切换数据源checkAndSwitchDataSource();}}/*** 检查主数据库状态并在必要时切换到备用数据库*/private void checkAndSwitchDataSource() {try {// 检查主数据库是否可用if (dataSourceSwitcher.isPrimaryDataSourceAvailable()) {log.info("主数据源没有问题,一切正常");} else {// 主数据库不可用,更新缓存并切换到备用数据源cacheConfig.timeCacheHc().put("dataSource", "主数据库挂了,boom");log.info("主数据源存在问题,切换备用数据源");DataSourceUtil.setDB(DataSourceConstant.DB_NAME_DM6_BACKUP);}} catch (Exception e) {// 主数据库和备用数据库都不可用,抛出异常throw new RuntimeException("两个数据库都有问题 GG", e);}}/*** 获取需要切换的数据源* @param point 连接点信息* @return 数据源注解信息*/private DataSource getDataSource(ProceedingJoinPoint point) {MethodSignature signature = (MethodSignature) point.getSignature();DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);if (Objects.nonNull(dataSource)) {return dataSource;}return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);}
}

10. 缓存配置类

/*** 缓存配置类* @Author: 阿水* @Date: 2024-05-24*/
@Configuration
public class TimeCacheConfig {@Beanpublic TimedCache timeCacheHc() {return CacheUtil.newTimedCache(5 * 60 * 1000);}
}

定时缓存,对被缓存的对象定义一个过期时间,当对象超过过期时间会被清理。此缓存没有容量限制,对象只有在过期后才会被移除,详情可以翻阅hutool官方文档

超时-TimedCache

11. 运行结果:

我dmprimary的信息随便写的,可以发现可以自动切换到备用数据库。

12. 结论

通过以上步骤,本次在Spring Boot项目中实现了自定义注解来管理多数据源,并且在主数据库不可用时自动切换到备用数据库。为了提升效率,我们还使用了缓存来记住主数据库的状态,避免频繁的数据库状态检查。这种设计不仅提高了系统的可靠性和可维护性,还能保证在关键时刻系统能够稳定运行。

希望这篇博客能对你有所帮助,如果你有任何问题或建议,欢迎留言讨论。(有问题可以私聊看到就会回)

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

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

相关文章

【HTML】制作一个跟随鼠标的流畅线条引导页界面(可直接复制源码)

目录 前言 HTML部分 CSS部分 JS部分 效果图 总结 前言 无需多言&#xff0c;本文将详细介绍一段HTML代码&#xff0c;图中线条可跟随鼠标移动&#xff0c;具体内容如下&#xff1a; 开始 首先新建一个HTML的文本&#xff0c;文本名改为[index.html]&#xff0c;创建好后右…

第十一节 SpringBoot Starter 面试题

一、面试题 很多同学的简历都写着熟悉 SpringBoot&#xff0c; 而 Starter 的实现原理被当作的考题的的情况越来越多。 来源牛客网关于 starter 的一些面试题 情景一、路虎一面 情景二、蔚来 情景三、同花顺 Starter 频频出现&#xff0c;因此在面试准备时&#xff0c;这道题…

mdm 推送证书制作教程

第一步点击获取&#xff0c;点击以后会下载一个zip压缩包 解压以后&#xff1a;会得到四个文件&#xff0c;请务必保存好&#xff0c;待会需要使用 登录apple开发者官网 https://developer.apple.com/account/resources/certificates/list 点击添加证书 找到mdm csr 然后点击…

用Python代码批量提取PDF文件中的表格

PDF文档中常常包含大量数据&#xff0c;尤其是官方报告、学术论文、财务报表等文档&#xff0c;往往包含了结构化的表格数据。表格作为承载关键信息的载体&#xff0c;其内容的准确提取对于数据分析、研究论证乃至业务决策具有重大意义。然而&#xff0c;PDF格式虽保证了文档的…

Vue从入门到实战Day12

一、Pinia快速入门 1. 什么是Pinia Pinia是Vue的最新状态管理工具&#xff0c;是Vuex的替代品 1. 提供更加简单的API&#xff08;去掉了mutation&#xff09; 2. 提供符合组合式风格的API&#xff08;和Vue3新语法统一&#xff09; 3. 去掉了modules的概念&#xff0c;每一…

人工智能应用-实验7-胶囊网络分类minst手写数据集

文章目录 &#x1f9e1;&#x1f9e1;实验内容&#x1f9e1;&#x1f9e1;&#x1f9e1;&#x1f9e1;代码&#x1f9e1;&#x1f9e1;&#x1f9e1;&#x1f9e1;分析结果&#x1f9e1;&#x1f9e1;&#x1f9e1;&#x1f9e1;实验总结&#x1f9e1;&#x1f9e1; &#x1f9…

Python TCP编程简单实例

客户端&#xff1a;创建TCP链接时&#xff0c;主动发起连接的叫做客户端 服务端&#xff1a;接收客户端的连接 连接其他服务器 可以通过tcp连接其他服务器。 示例&#xff1a; import socket# 1.创建一个socket # 参数1&#xff1a;指定协议 AF_INET&#xff08;ipv4&#…

LeetCode1466重新规划路线

题目描述 n 座城市&#xff0c;从 0 到 n-1 编号&#xff0c;其间共有 n-1 条路线。因此&#xff0c;要想在两座不同城市之间旅行只有唯一一条路线可供选择&#xff08;路线网形成一颗树&#xff09;。去年&#xff0c;交通运输部决定重新规划路线&#xff0c;以改变交通拥堵的…

vite项目怎么build打包成不同环境的代码?从而适配不同环境api接口

在开发 Web 应用的过程中&#xff0c;我们需要在不同的环境中运行和测试我们的应用程序&#xff08;如开发环境、测试环境和生产环境&#xff09;。每个环境都有其特定的 API 接口和配置。Vite&#xff0c;一个基于 ESBuild 的前端构建工具&#xff0c;可以帮助我们实现这个需求…

ganglia的安装使用

1.集群内分别安装epel-release依赖&#xff0c;更新yum源 sudo yum -y install epel-release 2&#xff0e;各节点上分别安装gmond sudo yum -y install ganglia-gmond 3.监控节点上安装gmetad和web(这里安装在node1上) sudo yum -y install ganglia-gmetad sudo yum -y insta…

现代密码学——消息认证和哈希函数

1.概述 1.加密-->被动攻击&#xff08;获取消息内容、业务流分析&#xff09; 消息认证和数字签名-->主动攻击&#xff08;假冒、重放、篡改、业务拒绝&#xff09; 2.消息认证作用&#xff1a; 验证消息源的真实性&#xff0c; 消息的完整性&#xff08;未被篡改…

第七步 实现打印函数

文章目录 前言一、如何设计我们的打印函数&#xff1f;二、实践检验&#xff01; 查看系列文章点这里&#xff1a; 操作系统真象还原 前言 现在接力棒意见交到内核手中啦&#xff0c;只不过我们的内核现在可谓是一穷二白啥都没有&#xff0c;为了让我们设计的内核能被看见被使用…

数据防泄露解决方案分享

在当今高度数字化和互联的商业环境中&#xff0c;数据防泄密已成为企业保护财产、维护客户隐私和遵守合规要求的重要一环。数据防泄密不仅关乎企业的经济利益&#xff0c;更涉及用户个人信息安全、商业机密保护以及国家安全等核心问题。能做好数据防泄露&#xff0c;对于提升企…

启动docker报错:Failed to listen on Docker Socket for the API.

说明&#xff1a; 1、安装部署docker完成后&#xff0c;启动docker报错&#xff1a;Failed to listen on Docker Socket for the API&#xff0c;如下图所示&#xff1a; 2、将SocketGroupdocker更改成&#xff1a;SocketGrouproot即可 一、解决方法&#xff1a; 1、执行命令…

舵机(结构,原理,控制方法)

介绍 舵机&#xff0c;全称为伺服马达&#xff08;Servo Motor&#xff09;&#xff0c;是一种能够精确控制角度或位置的电动机。它广泛应用于模型制作、机器人技术、工业自动化等领域。舵机通过接收控制信号&#xff0c;将其转化为机械运动&#xff0c;从而实现精确的控制。 …

代码随想录算法训练营第三天| 203.移除链表元素、 707.设计链表、 206.反转链表

203.移除链表元素 题目链接&#xff1a; 203.移除链表元素 文档讲解&#xff1a;代码随想录 状态&#xff1a;没做出来&#xff0c;做题的时候定义了一个cur指针跳过了目标val遍历了一遍链表&#xff0c;实际上并没有删除该删的节点。 错误代码&#xff1a; public ListNode re…

文档解析新纪元:TextIn产品体验与解决难题的深度剖析

前言 在数字化浪潮席卷各行各业的今天&#xff0c;作为一名数据分析师&#xff0c;每天我都需要处理和分析大量的文档。然而&#xff0c;传统的文档解析工具在面对我的专业需求时&#xff0c;往往显得力不从心。 我的工作常常涉及到各种格式的文档&#xff0c;包括PDF、Word、…

在洁净实验室设计装修中怎么选择合适实验室家具?

在现代科学研究和技术开发中&#xff0c;洁净实验室装修设计成为了确保实验准确性和安全性的重要因素。洁净实验室需要提供一个无尘、无菌、受控的环境&#xff0c;而在洁净实验室装修设计这个过程中&#xff0c;如何选择合适的实验室家具就显得尤为重要&#xff0c;因为它直接…

Web前端一套全部清晰 ⑨ day5 CSS.4 标准流、浮动、Flex布局

我走我的路&#xff0c;有人拦也走&#xff0c;没人陪也走 —— 24.5.24 一、标准流 标准流也叫文档流&#xff0c;指的是标签在页面中默认的排布规则&#xff0c;例如:块元素独占一行&#xff0c;行内元素可以一行显示多个。 二、浮动 作用: 让块级元素水平排列。 属性名:floa…

练习题(2024/5/22)

1N 皇后 II n 皇后问题 研究的是如何将 n 个皇后放置在 n n 的棋盘上&#xff0c;并且使皇后彼此之间不能相互攻击。 给你一个整数 n &#xff0c;返回 n 皇后问题 不同的解决方案的数量。 示例 1&#xff1a; 输入&#xff1a;n 4 输出&#xff1a;2 解释&#xff1a;如上…