支持嵌套数据源的DataSourceContextHolder实现

news/2025/3/22 13:11:05/文章来源:https://www.cnblogs.com/euler-blog/p/18786446

一、基于Queue的数据源上下文实现

public class DataSourceContextHolder {// 使用ThreadLocal<Deque>支持嵌套数据源private static final ThreadLocal<Deque<DataSourceType>> CONTEXT_HOLDER = ThreadLocal.withInitial(LinkedList::new);/*** 设置数据源类型到队列头部*/public static void push(DataSourceType dataSourceType) {CONTEXT_HOLDER.get().push(dataSourceType);}/*** 获取当前数据源类型(队列头部)*/public static DataSourceType peek() {Deque<DataSourceType> deque = CONTEXT_HOLDER.get();return deque.isEmpty() ? null : deque.peek();}/*** 移除当前数据源类型*/public static DataSourceType pop() {Deque<DataSourceType> deque = CONTEXT_HOLDER.get();return deque.isEmpty() ? null : deque.pop();}/*** 清空数据源类型*/public static void clear() {CONTEXT_HOLDER.remove();}/*** 获取当前数据源切换深度*/public static int size() {return CONTEXT_HOLDER.get().size();}
}

二、数据源切换注解

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {DataSourceType value() default DataSourceType.DB1;
}@Aspect
@Component
public class DataSourceAspect {@Pointcut("@annotation(com.example.annotation.DataSource)")public void dataSourcePointcut() {}@Around("dataSourcePointcut()")public Object around(ProceedingJoinPoint point) throws Throwable {MethodSignature signature = (MethodSignature) point.getSignature();Method method = signature.getMethod();DataSource ds = method.getAnnotation(DataSource.class);if (ds != null) {// 将数据源推入栈中DataSourceContextHolder.push(ds.value());}try {return point.proceed();} finally {if (ds != null) {// 方法执行完后弹出数据源DataSourceContextHolder.pop();}}}
}

三、使用示例

  1. 嵌套数据源服务
@Service
@Slf4j
public class NestedDataSourceService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate OrderMapper orderMapper;@DataSource(DataSourceType.DB1)public User getUserFromDb1(Long id) {return userMapper.selectById(id);}@DataSource(DataSourceType.DB2)public Order getOrderFromDb2(Long id) {return orderMapper.selectById(id);}// 嵌套使用数据源的方法@DataSource(DataSourceType.DB1)public void complexOperation() {// 使用DB1数据源User user = getUserFromDb1(1L);log.info("Current DataSource: {}", DataSourceContextHolder.peek());// 切换到DB2数据源Order order = getOrderFromDb2(1L);log.info("Current DataSource: {}", DataSourceContextHolder.peek());// 自动恢复到DB1数据源user = getUserFromDb1(2L);log.info("Current DataSource: {}", DataSourceContextHolder.peek());}
}
  1. 事务处理
@Service
@Slf4j
public class NestedTransactionService {@Autowiredprivate DataSource db1DataSource;@Autowiredprivate DataSource db2DataSource;@Autowiredprivate PlatformTransactionManager db1TransactionManager;@Autowiredprivate PlatformTransactionManager db2TransactionManager;public void nestedTransactionOperation() {// DB1事务TransactionTemplate tt1 = new TransactionTemplate(db1TransactionManager);tt1.execute(status -> {DataSourceContextHolder.push(DataSourceType.DB1);try {// DB1操作// DB2事务TransactionTemplate tt2 = new TransactionTemplate(db2TransactionManager);tt2.execute(status2 -> {DataSourceContextHolder.push(DataSourceType.DB2);try {// DB2操作return null;} finally {DataSourceContextHolder.pop();}});return null;} finally {DataSourceContextHolder.pop();}});}
}
  1. 异常处理
@Service
@Slf4j
public class SafeDataSourceService {public <T> T executeWithDataSource(DataSourceType dataSourceType, Supplier<T> operation) {DataSourceContextHolder.push(dataSourceType);try {return operation.get();} finally {DataSourceContextHolder.pop();}}public void safeOperation() {try {// 使用DB1executeWithDataSource(DataSourceType.DB1, () -> {// DB1操作// 嵌套使用DB2return executeWithDataSource(DataSourceType.DB2, () -> {// DB2操作return null;});});} catch (Exception e) {log.error("操作失败", e);// 确保清理ThreadLocalDataSourceContextHolder.clear();}}
}
  1. 监控和调试
@Aspect
@Component
@Slf4j
public class DataSourceMonitorAspect {@Around("@annotation(dataSource)")public Object monitorDataSource(ProceedingJoinPoint point, DataSource dataSource) throws Throwable {String methodName = point.getSignature().getName();DataSourceType dsType = dataSource.value();log.debug("Method [{}] switching to datasource [{}], current stack depth: {}", methodName, dsType, DataSourceContextHolder.size());long startTime = System.currentTimeMillis();try {return point.proceed();} finally {long endTime = System.currentTimeMillis();log.debug("Method [{}] finished using datasource [{}], took {}ms", methodName, dsType, (endTime - startTime));}}
}
  1. 单元测试
@SpringBootTest
class NestedDataSourceTests {@Autowiredprivate NestedDataSourceService service;@Testvoid testNestedDataSourceSwitch() {// 验证数据源嵌套切换service.complexOperation();// 确保ThreadLocal被清理assertNull(DataSourceContextHolder.peek());assertEquals(0, DataSourceContextHolder.size());}@Testvoid testDataSourceStackOperation() {DataSourceContextHolder.push(DataSourceType.DB1);assertEquals(DataSourceType.DB1, DataSourceContextHolder.peek());DataSourceContextHolder.push(DataSourceType.DB2);assertEquals(DataSourceType.DB2, DataSourceContextHolder.peek());DataSourceContextHolder.pop();assertEquals(DataSourceType.DB1, DataSourceContextHolder.peek());DataSourceContextHolder.pop();assertNull(DataSourceContextHolder.peek());}
}

使用Queue(具体是Deque)来支持数据源嵌套使用的优点:
支持嵌套:可以处理方法嵌套调用时的数据源切换
自动恢复:使用栈结构自然地支持数据源的恢复
线程安全:ThreadLocal保证了线程隔离
易于调试:可以方便地查看数据源切换历史
异常安全:即使发生异常,也能保证数据源正确恢复
注意事项:
确保在finally块中调用pop()
考虑添加最大嵌套深度限制
在异常情况下清理ThreadLocal
监控数据源切换的性能影响
考虑添加数据源切换的日志记录
这种实现方式适合处理复杂的业务场景,特别是需要在同一个事务中访问多个数据源的情况。

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

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

相关文章

什么是RabbitMQ入门

一.什么是RabbitMQ 中间件(Middleware)是处于操作系统和应用程序之间的软件,也有人认为它应该属于操作系统中的一部分。人们在使用中间件时,往往是一组中间件集成在一起,构成一个平台(包括开发平台和运行平台),但在这组中间件中必须要有一个通信中间件,即中间件=平台+…

使用BL0937 IC进行交流电源监控

一个简单的电路来监测交流电源,接线板可以插在任何地方,显示电压,电流和功率。交流电源监控是当今物联网相关应用中的一个惊人功能,例如智能风扇,开关和板。一些优秀的公司董事会在他们的产品中实施了这项技术,并持续监控供应的输出功率。在本地设备层面监控电源有其自身…

20244224 实验一《Python程序设计》实验一报告

课程:《Python程序设计》 班级: 2442 姓名: 旦曾央京 学号:20244224 实验教师:王志强 实验日期:2025年3月18日 必修/选修: 公选课 1.实验内容 1.熟悉Python开发环境; 2.练习Python运行、调试技能; 3.编写程序,练习变量和类型、字符串、对象、缩进和注释等; 4.编…

【智慧构造题】CF1427E Xum

我们发现这个原数为奇数的条件比较神秘,于是我们考虑每次把这个数字第一位 \(1\) 给干了。 考虑怎么构造。 令 $d=\lfloor \log_2 x \rfloor $ 令 \(x=1abc1\) \(x1=1abc10000\) \(x2=1abc0abc1\) \(x3=x2+x1=1abc01abc1\) \(x4=x ⊕ x3=1abc000000\) \(x5=x1+x1=1abc100000…

DataGrip结果运行在代码区域

DataGrip运行过后发现运行的结果在代码区域解决方式进入设置界面 点击左上角File(文件)->settings(设置) 搜索栏输入results或output and results 找到下面的设置界面 找到Results下的 Show results in editor ,取消勾选

我们接着创建项目中的app

在pycharm中运行以下代码python manage.py startapp teslaapp运行后呢会在目录中在创建一个名字叫teslaapp的包接着我们还需要在项目Tesla包中的setting文件中 添加我们刚创建的app名称

CMU_15445_Project4_BonusTask_Serializable_Verification

Serializable Verification 我们知道 MVCC 并不能解决幻读以及写偏差的问题, 仅通过 MVCC 的事务调度是无法保证数据库引擎的 ACID 原则的, 那么为了保证数据库的 ACID 原则, 即使在调度的过程中无法保证, 可以通过在 Commit 的时候, 通过验证, Abort 可能造成写偏差于幻读的事…

使用低代码平台设计UI

由于自己不是专业的设计师,就自己简单画个UI界面,那么就可以用到下面的低代码平台 http://120.92.142.115:81/vform3/ 里面可以选择体验vue3模板 进入下面图中的设计界面可以自己拖动组件 修改组件展示给用户的标签名称 以及数据传输时发送的字段名称 可以设置是否必填 默认值…

使用 Hosting 构建 WPF 程序 - prism 篇

WPF + .NET Generic Host + Prism + CommunityToolkit.Mvvm在 使用 Hosting 构建 WPF 程序 - Stylet 篇 中,使用 Hosting + Stylet 的方式,构建了一个 WPF 框架, 本文用于记录使用 .NET Generic Host + Prism 构建 WPF 所需的修改,仅供参考。 示例代码:Jasongrass/Demo.Ap…

如何构建一个用于3D扫描的电动转台

在这个项目中,我将向您展示如何构建一个用于3D扫描的电动转台。这个DIY项目由三个主要部分组成:顶板、中央齿轮驱动板和底座。顶板保持静止,作为放置待扫描物体的平台。在设计的核心,中间板的特点是一个内部齿轮机构,隐藏在视线之外,这是由一个步进电机驱动。这个齿轮板旋…

【每日一题】20250322

【每日一题】 1.(18分) \(\hspace{0.7cm}\)I.(5分)根据单摆周期公式 \(\displaystyle T=2\pi\sqrt{\frac{l}{g}}\),可以通过实验测量当地的重力加速度.如图 \(1\) 所示,将细线的上端固定在铁架台上,下端系一小钢球,就做成了单摆.\(\hspace{0.7cm}\)(1)用游标卡尺测…

Git 和 GitHub 笔记

笔记说明 该笔记记录所有我使用 Git 和 GitHub 过程中遇到的、觉得需要记录的事情。复杂的问题按需抽出来单独称为一篇笔记,换成博客链接。 修改远程仓库名字后,本地仓库做什么处理? git remote -v git remote set-url origin https://github.com/用户名/新仓库名.git or gi…