动态切换数据源的最佳实践

序言

本文和大家聊聊在开发中,动态切换多数据源的方案。

一、多数据源需求

随着应用程序的发展和复杂性增加,对于多数据源的需求也变得越来越普遍。在某些场景下,一个应用程序可能需要连接和操作多个不同的数据库或数据源。常见的场景包括多租户系统、分布式架构、数据分片、读写分离以及数据同步和迁移等。在这些场景下,应用程序需要连接到多个数据源来满足不同的业务需求。

二、动态切换多数据源设计

在设计动态切换数据源的方案时,需要考虑以下几个方面:

  1. 数据源的管理和配置:如何管理和配置多个数据源,以便应用程序能够动态地切换数据源。
  2. 数据源的路由和选择:如何根据业务需求选择合适的数据源,并在运行时动态切换数据源。
  3. 数据源的连接池管理:如何有效地管理多个数据源的连接池,以提高系统的性能和资源利用率。

三、动态切换多数据源关键技术

实现动态切换数据源的关键技术包括:

  1. 使用 Spring 框架的 AbstractRoutingDataSource 实现动态数据源路由。
  2. 使用 AOP + 注解方式拦截数据源访问方法,并在运行时动态切换数据源。

四、动态切换多数据源核心原理

在 Spring 中提供了一个 AbstractRoutingDataSource 抽象类,用于实现动态路由到不同数据源的功能。它允许应用程序根据特定的规则在运行时选择使用哪个数据源,而不是在启动时就确定使用哪个数据源。

其原理如下:

workspace.png

  1. 开发人员将多个 DataSource(数据源)对象放入 AbstractRoutingDataSource 的 targetDataSources 成员变量中。其中,targetDataSources 是一个 Map<Object, Object> 集合,key 存放的是 DataSource 的名称,value 存放具体 DataSource 对象。
  2. 开发人员实现 AbstractRoutingDataSource#determineCurrentLookupKey() 方法,该方法返回 DataSource 的 key。
  3. AbstractRoutingDataSource 会根据 AbstractRoutingDataSource#determineCurrentLookupKey() 返回的 key 查找相应的 DataSource 对象,从而实现了动态指定数据源

五、实现方案

5.1 数据源管理和配置

首先,我们需要定义多数据源的配置方式以及管理方式。我们在 spring.datasource 的基础上,添加一个 multi 属性定义多数据源。其中,multi 下是数据源列表,具体格式如下:

spring:datasource:multi:- name: masterdriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://192.168.56.101:3306/learnusername: rootpassword: 123456type: com.alibaba.druid.pool.DruidDataSource- name: slavedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://192.168.56.102:3306/testusername: rootpassword: 123456type: com.alibaba.druid.pool.DruidDataSource

读取自定义配置属性的配置类:

@ConfigurationProperties(prefix = "spring.datasource")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MultiDataSourceProperties {// DataSourceProperties 是 Spring 里面的private List<DataSourceProperties> multi;
}

5.2 数据源动态路由规则

实现动态路由切换数据源的关键是在 AbstractRoutingDataSource#determineCurrentLookupKey() 方法里,因为 AbstractRoutingDataSource 会根据其返回的 key 去查找相应的 DataSource。
workspace (1).png

我们可以将路由规则进行如下处理:

  1. determineCurrentLookupKey() 直接从 ThreadLocal 中获取 DataSource 的 key 返回
  2. 开发者动态更换 ThreadLocal 中的值,即可实现动态路由

定义 ThreadLocal 的操作对象(实现对 ThreadLocal 的操作):

public class DataSourceContextHolder {public static final ThreadLocal<String> DATASOURCE_CONTEXT_HOLDER = new ThreadLocal<>();// 放入 DataSource 的 keypublic static void setDataSourceContext(String dataSource) {DATASOURCE_CONTEXT_HOLDER.set(dataSource);}// 获取 DataSource 的 keypublic static String getDataSource() {return DATASOURCE_CONTEXT_HOLDER.get();}// 清除 DataSource 的 keypublic static void clear() {DATASOURCE_CONTEXT_HOLDER.remove();}
}

根据配置生成数据源,并实现路由规则:

@Configuration
@EnableConfigurationProperties({MultiDataSourceProperties.class})
public class DynamicDataSourceAutoConfigure {private final MultiDataSourceProperties multiDataSourceProperties;private final TreeMap<Object, Object> targetDataSources = new TreeMap<>();/*** 构造器注入** @param multiDataSourceProperties 数据源配置*/@Autowiredpublic DynamicDataSourceAutoConfigure(MultiDataSourceProperties multiDataSourceProperties) {this.multiDataSourceProperties = multiDataSourceProperties;}/*** 该方法根据数据源配置生成对应的 DataSource 对象** @param dataSourceProperties 数据源配置* @return DataSource*/private DataSource createDataSource(DataSourceProperties dataSourceProperties) {return DataSourceBuilder.create().driverClassName(dataSourceProperties.getDriverClassName()).url(dataSourceProperties.getUrl()).username(dataSourceProperties.getUsername()).password(dataSourceProperties.getPassword()).type(dataSourceProperties.getType()).build();}/**** 在实例化时根据配置动态的创建多个数据源*/@PostConstructpublic void init() {List<DataSourceProperties> dataSources = multiDataSourceProperties.getMulti();for (DataSourceProperties dataSourceProperties : dataSources) {// 创建数据源DataSource dataSource = createDataSource(dataSourceProperties);// 将数据源放入 targetDataSourcestargetDataSources.put(dataSourceProperties.getName(), dataSource);}}/*** 注入自定义的 AbstractRoutingDataSource,并实现路由规则** @return DataSource*/@Beanpublic DataSource dynamicDataSource() {AbstractRoutingDataSource dataSource = new AbstractRoutingDataSource() {@Overrideprotected Object determineCurrentLookupKey() {// 路由规则:直接从 ThreadLocal 获取 DataSource 的 keyreturn DataSourceContextHolder.getDataSource();}};// 设置默认数据源为配置文件的第一个数据源dataSource.setDefaultTargetDataSource(targetDataSources.firstEntry().getValue());// 配置数据源列表dataSource.setTargetDataSources(targetDataSources);return dataSource;}
}

5.3 动态切换数据源

之前,数据源的动态路由规则已经定义完成了。但是这个规则是依据 ThreadLocal 中值的动态变化完成的。如何动态设置 ThreadLocal 中的值就成了关键。动态设置 ThreadLocal 中的值其实并不难,为了使我们的开发更加方便,我们采用 AOP + 注解 的方式,从而实现声明式动态更改 ThreadLocal 中的值。

  1. 定义一个注解

    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface DS {String value() default "";
    }
    
  2. 给该注解添加 AOP 处理逻辑

    @Aspect
    @Component
    public class DynamicDataSourceAspect {// 可在类和方法上检测该注解@Before("@annotation(dataSource) || @within(dataSource)")public void before(JoinPoint joinPoint, DS dataSource) {MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();DS annotation = method.getAnnotation(DS.class);String value = annotation != null ? annotation.value() : dataSource.value();// 将注解中的值放入 ThreadLocal 中DataSourceContextHolder.setDataSourceContext(value);}@After("@annotation(dataSource) || @within(dataSource)")public void after(DS dataSource) {// 清除 ThreadLocal 中的值DataSourceContextHolder.clear();}
    }
    
  3. 使用方式

    @Service
    public class UserServiceImpl implements UserService {@Resourceprivate UserMapper userMapper;// 使用在方法上@DS("slave")@Overridepublic User getUser(int userId) {return userMapper.getUserById(userId);}
    }// 使用在类上
    @DS("slave")
    @Service
    public class UserServiceImpl implements UserService {@Resourceprivate UserMapper userMapper;@Overridepublic User getUser(int userId) {return userMapper.getUserById(userId);}
    }
    

至此,我们便完成了多数据源的动态切换。今后我们若有需要只需:

  1. 在配置文件中添加数据源配置
  2. 使用 @DS 注解就可以完成数据源的切换了。

六、FAQ

本文主要是提供动态切换数据源的核心思路,若大家有特殊开发需求可以自行借助搜索引擎或在评论区下大家一起讨论哦。(‾◡◝)

推荐阅读

  1. 缓存神器-JetCache
  2. Mybatis 缓存机制
  3. 为什么 MySQL 单表数据量最好别超过 2000w
  4. IoC 思想简单而深邃
  5. ThreadLocal

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

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

相关文章

Qt QThreadPool线程池

1.简介 QThreadPool类管理一个QThread集合。 QThreadPool管理和重新设计单个QThread对象&#xff0c;以帮助降低使用线程的程序中的线程创建成本。每个Qt应用程序都有一个全局QThreadPool对象&#xff0c;可以通过调用globalInstance来访问该对象。 要使用其中一个QThreadPool…

深度学习入门(4)

神经网络的构建 import numpy as np import matplotlib.pyplot as plt def sigmoid(x):return 1/(1np.exp(-x)) def identity_function(x):#恒等函数return x def init_network():#进行权重和偏置的初始化&#xff0c;并保存到字典中network{}network[W1]np.array([[0.1,0.3,0…

ezplot--Matlab学习

目录 一、代码 二、效果 ​编辑 三、ezplot讲解 四、如何自定义一个函数 一、代码 clc; clear; t0:32; x4(t) cos(2*pi*t/4).*sin(2*pi*t/4); x8(t) cos(2*pi*t/8).*sin(2*pi*t/8); x16(t) cos(2*pi*t/16).*sin(2*pi*t/16); subplot(3,1,1) ezplot(x4,[0,32]); subplot…

POCEXP编写—多线程

POC&EXP编写—多线程 1. 前言2. 多进程&多线程2.1. 多进程2.1.1. 案例 2.2. 多线程2.2.1. 案例&#xff1a; 2.3. POC的案例&#xff08;模板&#xff09; 3. UA头设置3.1. 随机UA头3.1.1. 案例3.1.2. 模板拼接 4. 代理Proxy4.1. 单代理案例4.2. 多代理案例4.2.1. 请求…

【AI心理咨询应用】继Woebot之后,国内诞生的“LLM+CBT”应用:白小喵

导言 AI认知行为疗法&#xff08;Cognitive Behavioral Therapy&#xff0c;CBT&#xff09;早在2017年便有了首例&#xff0c;即美国知名CBT治疗机器人Woebot。 然而&#xff0c;Woebot在CBT的完整落地上仍有缺陷问题&#xff0c;LLM的出现促进了对该问题的解决&#xff0c;…

【机器学习基础1】什么是机器学习、预测模型解决问题的步骤、机器学习的Python生态圈

文章目录 一. 什么是机器学习1. 概念2. 机器学习算法分类 二. 利用预测模型解决问题的步骤三. 机器学习的Python生态圈 一. 什么是机器学习 1. 概念 机器学习&#xff08;Machine Learning&#xff0c;ML&#xff09;是一门多领域的交叉学科&#xff0c;涉及概率论、统计学、…

服务器数据恢复—Storwize V3700存储数据恢复案例

服务器存储数据恢复环境&#xff1a; 某品牌Storwize V3700存储&#xff0c;10块硬盘组建了2组Mdisk加入到一个存储池中&#xff0c;一共创建了1个通用卷来存放数据&#xff0c;主要数据为oracle数据库。 服务器存储故障&#xff1a; 其中一组Mdisk中两块磁盘出现故障离线&…

【每日刷题】Day28

【每日刷题】Day28 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;每日刷题&#x1f34d; &#x1f33c;文章目录&#x1f33c; ​ 1. 121. 买卖股票的最佳时机 - 力扣&#xff08;LeetCode&#xff09; 2. 205. 同构字符串 - 力扣…

关于discuz论坛网址优化的一些记录(网站地图sitemap提交)

最近网站刚上线&#xff0c;针对SEO做了些操作&#xff0c;为了方便网站网页百度被收录&#xff0c;特此记录下 discuz有免费的sitemap插件可以用&#xff0c;打开后台管理&#xff0c;找到插件栏&#xff0c;然后找到更多插件&#xff0c;进入插件市场。 选择这个免费的sitem…

【数据分析面试】34.填充NaN值 (Python:groupby/sort_value/ffill)

题目&#xff1a;填充NaN值 &#xff08;Python) 给定一个包含三列的DataFrame&#xff1a;client_id、ranking、value 编写一个函数&#xff0c;将value列中的NaN值用相同client_id的前一个非NaN值填充&#xff0c;按升序排列。 如果不存在前一个client_id&#xff0c;则返…

Linux详解:进程等待

文章目录 进程等待等待的必要性进程等待的方法waitwaitpid获取子进程status阻塞等待 与 非阻塞等待 进程等待 等待的必要性 子进程退出&#xff0c;父进程不进行回收的话&#xff0c;就可能造成僵尸进程&#xff0c;进而造成内存泄露 如果进程进入了僵尸状态&#xff0c;kill…

GITEE 基于OAuth2的API V5版本

为了构建更好的码云生态环境&#xff0c;我们推出了基于OAuth2的API V5版本。 API V5接口使用方式以及Url都参照GitHub&#xff0c;为了各位开发者更好的兼容已经存在的第三方应用。 API 使用条款 OSCHINA 用户是资源的拥有者&#xff0c;需尊重和保护用户的权益。不能在应用…

基于SpringBoot+Vue高校竞赛管理系统的设计与实现

项目介绍&#xff1a; 高校竞赛管理系统管理系统按照操作主体分为管理员和用户。管理员的功能包括字典管理、论坛管理、竞赛公告管理、获奖管理、老师管理、评审管理、评审分配管理、评审打分管理、赛事管理、赛事提交管理、赛事报名管理、用户管理、专家管理、管理员管理。用…

如何安装最新版Docker Compose?

Docker Compose 是一个用于定义和运行多容器 Docker 应用程序的工具。通过 Compose&#xff0c;您可以使用 YAML 文件来配置应用服务&#xff0c;然后只需一个简单的命令便能创建和启动所有服务。在本篇博客中&#xff0c;我们将详细介绍如何在 Linux 系统上安装 Docker Compos…

可以向同事炫耀的10个Python技巧

Python 是一种用途极为广泛的编程语言&#xff0c;有大量的库和框架为其提供支持。然而&#xff0c;在庞大的 Python 生态系统中&#xff0c;还存在一些鲜为人知的编码技巧和库&#xff0c;它们可以显著增强您的开发体验并优化您的代码。我们将探讨一些鲜为人知的 Python 技巧&…

与Apollo共创生态:七周年大会深有体会!

前言 百度从2013年开始涉足自动驾驶领域。2017年3月1日&#xff0c;百度通过内部邮件宣布&#xff0c;将现有业务和资源整合&#xff0c;成立智能驾驶事业群组&#xff08;简称IDG&#xff09;。同年&#xff0c;在上海车展上&#xff0c;百度发布了“Apollo计划”&#xff0c…

Git:克隆代码,提交流程,分支提交流程

1.克隆代码 git clone http://…(代码地址&#xff09; 克隆代码到本地 2.提交流程&#xff08;不牵涉分支&#xff09; 1&#xff09;git add . 将本地修改的文件暂存到缓存区 2&#xff09;git commit -m "当次提交解释说明备注" 在日志中显示 3&#xff09;git…

0426GoodsBiddingAJAX项目

0426GoodsBiddingAJAX项目包-CSDN博客 数据库字段 ​ 管理员的登录界面 ​ 登录成功跳转在线拍卖界面&#xff0c;使用监听器拦截请求&#xff0c;只能登录管理员后访问该界面 ​ 商品竞拍列表 ​ 商品竞拍列表的竞拍操作&#xff1a; ​ 1 用户未登录跳转用户登录界面&#x…

面向对象编程三大特征:封装、继承、多态

封装、继承、多态 1. 封装 1.1 介绍 封装(encapsulation)就是把抽象出的数据 [属性] 和对数据的操作 [方法] 封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作 [方法] ,才能对数据进行操作。 1.2 封装的理解和好处 1) 隐藏实现细节:方法(连接数据库)<…

STM32CubeMX+MDK通过I2S接口进行音频输入输出(全双工读写一个DMA回调)续-音质问题解决总结

一、前言 之前进行了STM32CubeMXMDK通过I2S接口进行音频输入输出&#xff08;全双工读写一个DMA回调&#xff09;的研究总结&#xff1a; https://juejin.cn/post/7339016190612881408#heading-34 后续音质问题解决了&#xff0c;目前测试下来48khz的双声道使用效果很好&…