动态数据源多种实现方式及对比详细介绍

文章目录

      • 动态数据源实现方式
        • 1. 概述
        • 2. 动态数据源实现方式
          • 2.1 基于 `AbstractRoutingDataSource` 实现动态数据源
          • 2.2 基于 Spring AOP 实现动态数据源
          • 2.3 基于 `TransactionManager` 实现动态数据源
          • 2.4 通过数据库中间件实现动态数据源(ShardingSphere、MyCAT)
        • 3. 各实现方式对比
        • 4. 总结

动态数据源实现方式


1. 概述

动态数据源是现代企业级应用中常见的需求,尤其是在 多数据源管理读写分离多租户系统分库分表 等场景中,动态切换不同的数据源可以提升系统的灵活性和性能。本文将详细介绍几种常见的动态数据源实现方式,包括其应用场景、实现步骤、优缺点对比。


2. 动态数据源实现方式
2.1 基于 AbstractRoutingDataSource 实现动态数据源

适用场景
主要适用于需要根据业务上下文动态切换数据源的场景,常用于 读写分离多租户系统 等应用。

实现原理
Spring 框架提供的 AbstractRoutingDataSource 类支持动态数据源路由。通过继承该类并重写 determineCurrentLookupKey() 方法,基于当前线程上下文中的数据源标识符进行数据源切换。

实现步骤

  1. 创建数据源配置类:定义多个数据源,并将它们注入 DynamicDataSource
@Configuration
public class DataSourceConfig {@Bean(name = "dataSource1")@ConfigurationProperties(prefix = "spring.datasource.db1")public DataSource dataSource1() {return DataSourceBuilder.create().build();}@Bean(name = "dataSource2")@ConfigurationProperties(prefix = "spring.datasource.db2")public DataSource dataSource2() {return DataSourceBuilder.create().build();}@Primary@Bean(name = "dynamicDataSource")public DataSource dynamicDataSource(@Qualifier("dataSource1") DataSource dataSource1,@Qualifier("dataSource2") DataSource dataSource2) {Map<Object, Object> targetDataSources = new HashMap<>();targetDataSources.put("db1", dataSource1);targetDataSources.put("db2", dataSource2);DynamicDataSource dynamicDataSource = new DynamicDataSource();dynamicDataSource.setTargetDataSources(targetDataSources);dynamicDataSource.setDefaultTargetDataSource(dataSource1);return dynamicDataSource;}
}
  1. 创建 DynamicDataSourceContextHolder:使用 ThreadLocal 管理数据源标识。
public class DynamicDataSourceContextHolder {private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();public static void setDataSourceType(String dataSourceType) {CONTEXT_HOLDER.set(dataSourceType);}public static String getDataSourceType() {return CONTEXT_HOLDER.get();}public static void clearDataSourceType() {CONTEXT_HOLDER.remove();}
}
  1. 创建 DynamicDataSource:继承 AbstractRoutingDataSource 并重写 determineCurrentLookupKey() 方法。
public class DynamicDataSource extends AbstractRoutingDataSource {@Overrideprotected Object determineCurrentLookupKey() {return DynamicDataSourceContextHolder.getDataSourceType();}
}
  1. 使用 AOP 切面动态切换数据源
@Aspect
@Component
public class DynamicDataSourceAspect {@Before("@annotation(ds)")public void switchDataSource(JoinPoint point, DataSource ds) {DynamicDataSourceContextHolder.setDataSourceType(ds.value());}@After("@annotation(ds)")public void clearDataSource(JoinPoint point, DataSource ds) {DynamicDataSourceContextHolder.clearDataSourceType();}
}
  1. 使用自定义注解 @DataSource 指定数据源:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {String value() default "db1";
}

优点

  • 实现简单,轻松与 Spring 框架集成。
  • 适用于小型项目的动态数据源需求。

缺点

  • 性能可能在复杂场景中遇到瓶颈。

2.2 基于 Spring AOP 实现动态数据源

适用场景
适合需要细粒度动态切换数据源的场景,比如特定方法或业务逻辑需要在不同的数据源中执行。

实现原理
通过 Spring AOP 技术,在方法执行前后拦截调用,根据自定义注解切换数据源。

实现步骤

  1. 创建自定义注解 @DataSource
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {String value();
}
  1. 定义 AOP 切面类
@Aspect
@Component
public class DynamicDataSourceAspect {@Before("@annotation(ds)")public void switchDataSource(JoinPoint point, DataSource ds) {DynamicDataSourceContextHolder.setDataSourceType(ds.value());}@After("@annotation(ds)")public void clearDataSource(JoinPoint point, DataSource ds) {DynamicDataSourceContextHolder.clearDataSourceType();}
}
  1. 在业务层使用 @DataSource 注解 切换数据源:
@Service
public class UserService {@DataSource("db1")public List<User> getAllUsersFromDb1() {// 从 db1 获取数据}@DataSource("db2")public List<User> getAllUsersFromDb2() {// 从 db2 获取数据}
}

优点

  • 实现灵活,支持细粒度的控制。
  • 易于使用,适合小范围数据源切换。

缺点

  • 随着项目的规模增加,过多的切面可能导致维护复杂性上升。

2.3 基于 TransactionManager 实现动态数据源

适用场景
用于需要在不同数据源之间切换并保持事务一致性的场景,特别适合有 多数据源事务管理 需求的应用。

实现原理
通过为每个数据源配置不同的 TransactionManager,在 @Transactional 注解中动态指定事务管理器,确保数据源切换时的事务一致性。

实现步骤

  1. 配置每个数据源的 TransactionManager
@Bean
public PlatformTransactionManager transactionManager1(@Qualifier("dataSource1") DataSource dataSource1) {return new DataSourceTransactionManager(dataSource1);
}@Bean
public PlatformTransactionManager transactionManager2(@Qualifier("dataSource2") DataSource dataSource2) {return new DataSourceTransactionManager(dataSource2);
}
  1. 使用 @Transactional 注解指定事务管理器
@Service
public class UserService {@Transactional("transactionManager1")public void performDb1Operation() {// 操作 db1}@Transactional("transactionManager2")public void performDb2Operation() {// 操作 db2}
}

优点

  • 支持多数据源的事务管理,保证数据一致性。
  • 适合需要事务支持的场景。

缺点

  • 配置复杂,特别是涉及到分布式事务时。

2.4 通过数据库中间件实现动态数据源(ShardingSphere、MyCAT)

适用场景
适用于 大规模分库分表读写分离 等复杂场景,尤其是对性能有较高要求的项目。

实现原理
使用数据库中间件(如 ShardingSphere、MyCAT),通过中间件实现数据源路由、分片、读写分离等功能。开发者不需要手动处理数据源切换,所有逻辑由中间件根据配置文件自动完成。

实现步骤

  1. 引入 ShardingSphere 依赖
<dependency><groupId>org.apache.shardingsphere</groupId><artifactId>sharding-jdbc-core</artifactId><version>x.x.x</version>
</dependency>
  1. 配置数据源和路由规则

application.yml 中配置数据源和分片规则。

sharding:data-sources:ds_0:url: jdbc:mysql://localhost:3306/db0username: rootpassword: rootds_1:url: jdbc:mysql://localhost:3306/db1username: rootpassword: root

优点

  • 中间件提供高性能支持,适合复杂的大规模场景。
  • 减少了开发者手动管理多数据源的工作量。

缺点

  • 学习成本较高,配置复杂。
  • 对中间件的稳定性有较高的依赖。


3. 各实现方式对比
实现方式适用场景优点缺点
AbstractRoutingDataSource读写分离、多租户简单易用,与 Spring 框架无缝集成在复杂场景中可能存在性能瓶颈
基于 Spring AOP细粒度数据源切换灵活,支持特定方法和类的动态切换过多切面可能增加调试和维护难度
基于 TransactionManager多数据源事务管理保证多数据源场景中的事务一致性配置复杂,特别是分布式事务时
数据库中间件(ShardingSphere等)分库分表、读写分离性能优异,适合复杂场景,开发者不需手动处理学习成本高,依赖中间件性能和稳定性
手动切换数据源特殊业务场景完全控制数据源切换,灵活实现复杂,维护成本高

4. 总结

动态数据源技术为企业级应用提供了灵活的数据库管理能力。不同的实现方式各有优劣,开发者需要根据项目规模、业务需求和性能要求进行选择。

  • 小型项目:可以选择基于 AbstractRoutingDataSource 或 Spring AOP 的实现,简单易用,能够快速满足动态切换需求。
  • 需要事务管理:对于涉及多数据源事务管理的项目,基于 TransactionManager 的方式更为适合,可以保证数据一致性。
  • 大规模项目:如果是分库分表或读写分离场景,数据库中间件(如 ShardingSphere)能够提供高性能支持。
  • 特殊需求:对于少数特殊业务场景,手动切换数据源可能是最灵活的方案,但维护成本较高。

通过合理的动态数据源选择与实现,能够显著提升系统的扩展性和性能,满足不同复杂业务场景的需求。

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

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

相关文章

Flink Task 日志文件隔离

Flink Task 日志文件隔离 任务在启动时会先通过 MdcUtils 启动一个 slf4j 的 MDC 环境&#xff0c;然后将 jobId 添加到 slf4j 的 MDC 容器中&#xff0c;随后任务输出的日志都将附带 joid。 MDC 介绍如下&#xff1a; MDC ( Mapped Diagnostic Contexts )&#xff0c;它是一个…

深度学习:(六)激活函数的选择与介绍

激活函数 之前使用的 a σ ( z ) a\sigma(z) aσ(z) &#xff0c;其中 σ ( ) \sigma(~) σ( ) 便是激活函数。 在神经网络中&#xff0c;不同层的激活函数可以不同。 在学习中&#xff0c;一般以 g ( z ) g(z) g(z) 来表示激活函数。 为什么需要(线性)激活函数&#xff…

K8s容器运行时,移除Dockershim后存在哪些疑惑?

K8s容器运行时&#xff0c;移除Dockershim后存在哪些疑惑&#xff1f; 大家好&#xff0c;我是秋意零。 K8s版本截止目前&#xff08;24/09&#xff09;已经发布到了1.31.x版本。早在K8s版本从1.24.x起&#xff08;22/05&#xff09;&#xff0c;默认的容器运行时就不再是Doc…

算法之搜索--最长公共子序列LCS

最长公共子序列&#xff08;longest common sequence&#xff09;:可以不连续 最长公共子串&#xff08;longest common substring&#xff09;&#xff1a;连续 demo for (int i 1;i<lena;i){for (int j 1;j<lenb;j){if(a[i-1]b[j-1]){dp[i][j]dp[i-1][j-1]1;}el…

Qt (17)【Qt 文件操作 读写保存】

阅读导航 引言一、Qt文件概述二、输入输出设备类三、文件读写类四、文件和目录信息类五、自定义“记事本” 引言 在上一篇文章中&#xff0c;我们学习了Qt的事件处理机制&#xff0c;知道了如何响应用户的操作。但应用程序常常还需要处理文件&#xff0c;比如读写数据。所以&a…

python爬虫初体验(一)

文章目录 1. 什么是爬虫&#xff1f;2. 为什么选择 Python&#xff1f;3. 爬虫小案例3.1 安装python3.2 安装依赖3.3 requests请求设置3.4 完整代码 4. 总结 1. 什么是爬虫&#xff1f; 爬虫&#xff08;Web Scraping&#xff09;是一种从网站自动提取数据的技术。简单来说&am…

指针修仙之实现qsort

文章目录 回调函数什么是回调函数回调函数的作用 库函数qsort使用qsort函数排序整形使用qsort函数排序结构体 qsort函数模拟实现说明源码and说明 回调函数 什么是回调函数 回调函数就是⼀个通过函数指针调⽤的函数。 如果你把函数的指针&#xff08;地址&#xff09;作为参数…

Sigmoid引发的梯度消失爆炸及ReLU引起的神经元参数失效问题思考

Sigmoid和ReLU激活函数思考&#xff09; 引文Sigmoid函数梯度消失问题梯度爆炸问题解决方案 ReLU函数简化模型示例场景设定前向传播对反向传播的影响总结 内容精简版 引文 梯度消失和梯度爆炸是神经网络训练中常见的两个问题&#xff0c;特别是在使用Sigmoid激活函数时。这些问…

后端-navicat查找语句(单表与多表)

表格字段设置如图 语句&#xff1a; 1.输出 1.输出name和age列 SELECT name,age from student 1.2.全部输出 select * from student 2.where子语句 1.运算符&#xff1a; 等于 >大于 >大于等于 <小于 <小于等于 ! <>不等于 select * from stude…

torch模型量化方法总结

0.概述 模型训练完成后的参数为float或double类型,而装机(比如车载)后推理预测时,通常都会预先定点(量化)为int类型参数,相应的推理的精度会有少量下降,但不构成明显性能下降,带来的结果是板端部署的可能性,推理的latency明显降低,本文对torch常用的量化方法进行总…

JavaEE: 创造无限连接——网络编程中的套接字

文章目录 Socket套接字TCP和UDP的区别有连接/无连接可靠传输/不可靠传输面向字节流/面向数据报全双工/半双工 UDP/TCP api的使用UDPDatagramSocketDatagramPacketInetSocketAddress练习 TCPServerSocketSocket练习 Socket套接字 Socket是计算机网络中的一种通信机制&#xff0…

【VLM小白指北 (1) 】An Introduction to Vision-Language Modeling

开一个新坑Vision-Language Modeling (VLM) &#xff0c;原文76页&#xff0c;慢慢更&#xff0c;for beginners&#xff0c;但也不能之前啥都不会啊。 原文链接&#xff1a;An Introduction to Vision-Language Modeling Introduction 存在的问题&#xff1a;将语言与视觉相…

ChatGPT 在国内使用的方法

AI如今很强大&#xff0c;聊聊天、写论文、搞翻译、写代码、写文案、审合同等等&#xff0c;ChatGPT 真是无所不能~ 作为一款出色的大语言模型&#xff0c;ChatGPT 实现了人类般的对话交流&#xff0c;最主要是能根据上下文进行互动。 接下来&#xff0c;我将介绍 ChatGPT 在国…

xhs 小红书 x-s web 分析

声明: 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 有相关问题请第一时间头像私信联系我…

《深度学习》PyTorch框架 优化器、激活函数讲解

目录 一、深度学习核心框架的选择 1、TensorFlow 1&#xff09;概念 2&#xff09;优缺点 2、PyTorch 1&#xff09;概念 2&#xff09;优缺点 3、Keras 1&#xff09;概念 2&#xff09;优缺点 4、Caffe 1&#xff09;概念 2&#xff09;优缺点 二、pytorch安装 1、安装 2、…

Linux操作系统:GCC(GNU Compiler Collection)编译器

在 Linux 系统中&#xff0c;gcc&#xff08;GNU Compiler Collection&#xff09;是一个非常强大的编译器&#xff0c;主要用于编译 C 语言程序。 除了基本的编译和链接命令外&#xff0c;gcc还提供了许多选项和功能。 以下是一些常用的 gcc命令及其功能&#xff1a; 1. 基本…

Python | Leetcode Python题解之第420题强密码检验器

题目&#xff1a; 题解&#xff1a; class Solution:def strongPasswordChecker(self, password: str) -> int:n len(password)has_lower has_upper has_digit Falsefor ch in password:if ch.islower():has_lower Trueelif ch.isupper():has_upper Trueelif ch.isdi…

基于SpringBoot+Vue的智慧物业管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 精品专栏&#xff1a;Java精选实战项目源码、Python精…

transformer模型进行英译汉,汉译英

上面是在测试集上的表现 下面是在训练集上的表现 上面是在训练集上的评估效果 这是在测试集上的评估效果,模型是transformer模型,模型应该没问题,以上的是一个源序列没加结束符和加了结束符的情况。 transformer源序列做遮挡填充的自注意力,这就让编码器的输出中每个token的语…

寄存器与内存

第三课&#xff1a;寄存器与内存、中央处理器&#xff08;CPU&#xff09;、指令和程序及高级 CPU 设计-CSDN博客 锁存器 引入 ABO0&#xff08;开始状态&#xff09;001&#xff08;将A置1&#xff09;110&#xff08;将A置0&#xff09;11 无论怎么做&#xff0c;都没法从1变…