使用AbstractRoutingDataSource实现动态切换数据源未生效问题

文章探讨了在使用AbstractRoutingDataSource进行数据源动态切换时遇到的一个问题,即JPA查询能正常切换,但通过EntityManager创建的查询无法进入determineCurrentLookupKey()方法。作者发现,问题可能在于数据源切换后,EntityManager仍使用初始的数据源。解决方案是在创建查询前调用entityManager.getEntityManagerFactory().createEntityManager(),这似乎能确保正确地切换数据源。
部署运行你感兴趣的模型镜像

最近使用AbstractRoutingDataSource来实现动态切换数据源的功能,发现jpa实现的查询都可以正常实现数据源的动态切换,唯独使用EntityManager创建查询没有进入determineCurrentLookupKey()方法。
调试一番之后发现在创建查询前加上这一行代码即可解决问题。

entityManager.getEntityManagerFactory().createEntityManager();

猜测应该是数据源切换了之后,EntityManager还是使用的第一次初始化使用的数据源,导致切换查询还是查询的之前的数据源。

您可能感兴趣的与本文相关的镜像

Llama Factory

Llama Factory

模型微调
LLama-Factory

LLaMA Factory 是一个简单易用且高效的大型语言模型(Large Language Model)训练与微调平台。通过 LLaMA Factory,可以在无需编写任何代码的前提下,在本地完成上百种预训练模型的微调

<think>我们讨论的是在Spring Boot中使用AbstractRoutingDataSource实现数据源动态切换时的事务处理问题。关键点在于: 1. AbstractRoutingDataSource实现数据源动态路由,但事务管理器并不感知数据源切换 2. 默认情况下,Spring事务在事务开始时绑定数据源动态切换可能失效 3. 需要确保事务注解和动态数据源切换的协同工作 核心解决方案: 1. 使用ThreadLocal存储当前数据源键值 2. 自定义事务管理器(或使用ChainedTransactionManager)管理多数据源事务 3. 通过AOP确保在事务开启前完成数据源切换 具体实现步骤: ### 1. 配置动态数据源 ```java public class DynamicDataSource extends AbstractRoutingDataSource { private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>(); @Override protected Object determineCurrentLookupKey() { return CONTEXT_HOLDER.get(); } public static void setDataSource(String name) { CONTEXT_HOLDER.set(name); } public static void clear() { CONTEXT_HOLDER.remove(); } } ``` ### 2. 配置多数据源事务管理器 使用`ChainedTransactionManager`(已弃用)或实现自定义事务同步: ```java @Configuration @EnableTransactionManagement public class TransactionConfig { @Bean public PlatformTransactionManager transactionManager( @Qualifier("ds1") DataSource ds1, @Qualifier("ds2") DataSource ds2) { // 创建独立事务管理器 DataSourceTransactionManager tm1 = new DataSourceTransactionManager(ds1); DataSourceTransactionManager tm2 = new DataSourceTransactionManager(ds2); // 使用聚合事务管理器(注意:ChainedTransactionManager在Spring Boot 2.3后已弃用) return new ChainedTransactionManager(tm1, tm2); } } ``` > **注意**:Spring Boot 2.3+ 中`ChainedTransactionManager`已弃用,建议采用`JtaTransactionManager`或自定义方案 ### 3. 使用AOP确保切换顺序 ```java @Aspect @Order(Ordered.HIGHEST_PRECEDENCE) // 必须在@Transactional之前执行 @Component public class DataSourceAspect { @Before("@annotation(ds)") public void switchDataSource(JoinPoint point, DataSource ds) { DynamicDataSource.setDataSource(ds.value()); } @After("@annotation(ds)") public void restoreDataSource(JoinPoint point, DataSource ds) { DynamicDataSource.clear(); } } ``` ### 4. 自定义事务管理器(替代ChainedTransactionManager) ```java public class MultiDataSourceTransactionManager extends AbstractPlatformTransactionManager { private final Map<String, PlatformTransactionManager> txManagers; // 构造器注入各数据源对应的事务管理器 @Override protected Object doGetTransaction() { // 获取当前数据源对应的事务管理器 String dsName = DynamicDataSource.getCurrentDataSource(); return txManagers.get(dsName).getTransaction(new DefaultTransactionDefinition()); } @Override protected void doCommit(DefaultTransactionStatus status) { getCurrentTxManager().commit(status); } private PlatformTransactionManager getCurrentTxManager() { return txManagers.get(DynamicDataSource.getCurrentDataSource()); } } ``` ### 事务处理关键点 1. **执行顺序**:数据源切换AOP必须优先于`@Transactional`执行[^5] ```java @Order(Ordered.LOWEST_PRECEDENCE - 1) // 确保先于事务拦截器 ``` 2. **事务传播**:跨数据源方法调用时需使用`PROPAGATION_REQUIRES_NEW` 3. **事务同步**:对于需要保证原子性的操作,建议使用XA事务 ### 示例业务场景 ```java @Service public class OrderService { @DataSource("orderDB") // 指定主订单库 @Transactional public void createOrder(Order order) { // 操作主订单库 orderDao.insert(order); // 调用库存服务(切换数据源) reduceInventory(order.getItems()); } @DataSource("inventoryDB") @Transactional(propagation = Propagation.REQUIRES_NEW) public void reduceInventory(List<Item> items) { // 操作库存库 inventoryDao.batchUpdate(items); } } ``` ### 注意事项 1. **线程污染**:务必在`finally`块中清除ThreadLocal数据源标识 2. **连接泄漏**:确保每个数据源使用独立连接池 3. **事务嵌套**:避免跨数据源循环调用导致事务死锁 4. **性能影响**:频繁切换数据源会增加连接获取开销 ### 相关问题 1. 如何避免多数据源切换导致的连接泄漏问题? 2. 在分库分表场景下如何优化动态数据源配置? 3. Spring Boot 3.x中如何处理多数据源事务? 4. 非关系型数据库(如MongoDB)能否与关系型数据库共享事务? </think> ## Spring Boot 多数据源动态切换的事务处理方案 使用 `AbstractRoutingDataSource` 实现数据源动态切换时,事务处理的核心挑战在于**确保事务与数据源切换的协同工作**。以下是完整解决方案: --- ### 一、事务处理核心问题 1. **事务绑定时机** Spring 事务在方法开始时绑定数据源,后续切换无效 2. **线程安全** 多线程环境下需确保数据源标识隔离 3. **执行顺序** 数据源切换必须先于事务开启 --- ### 二、解决方案(基于 AOP + ThreadLocal) #### 1. 配置动态数据源 ```java public class DynamicDataSource extends AbstractRoutingDataSource { private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>(); @Override protected Object determineCurrentLookupKey() { return CONTEXT_HOLDER.get(); // 从ThreadLocal获取数据源标识 } public static void setDataSource(String name) { CONTEXT_HOLDER.set(name); } public static void clear() { CONTEXT_HOLDER.remove(); } } ``` #### 2. 配置多数据源事务管理器 ```java @Configuration @EnableTransactionManagement public class TransactionConfig { @Bean public PlatformTransactionManager transactionManager( @Qualifier("ds1") DataSource ds1, @Qualifier("ds2") DataSource ds2) { // 创建独立事务管理器 DataSourceTransactionManager tm1 = new DataSourceTransactionManager(ds1); DataSourceTransactionManager tm2 = new DataSourceTransactionManager(ds2); // 使用聚合事务管理器 return new ChainedTransactionManager(tm1, tm2); } } ``` > **注意**:Spring Boot 2.3+ 中`ChainedTransactionManager`已弃用,建议采用以下替代方案: #### 3. 自定义事务管理器(替代方案) ```java public class MultiDataSourceTransactionManager extends AbstractPlatformTransactionManager { private final Map<String, PlatformTransactionManager> txManagers; // 构造器注入各数据源对应的事务管理器 public MultiDataSourceTransactionManager(Map<String, PlatformTransactionManager> txManagers) { this.txManagers = txManagers; } @Override protected Object doGetTransaction() { // 获取当前数据源对应的事务 String dsName = DynamicDataSource.getCurrentDataSource(); return txManagers.get(dsName).getTransaction(new DefaultTransactionDefinition()); } @Override protected void doCommit(DefaultTransactionStatus status) { getCurrentTxManager().commit(status); } private PlatformTransactionManager getCurrentTxManager() { return txManagers.get(DynamicDataSource.getCurrentDataSource()); } } ``` #### 4. 关键AOP切面(确保执行顺序) ```java @Aspect @Order(Ordered.HIGHEST_PRECEDENCE) // 必须在@Transactional之前执行 @Component public class DataSourceAspect { @Before("@annotation(ds)") public void switchDataSource(JoinPoint point, DataSource ds) { DynamicDataSource.setDataSource(ds.value()); // 切换数据源 } @After("@annotation(ds)") public void restoreDataSource(JoinPoint point, DataSource ds) { DynamicDataSource.clear(); // 清除ThreadLocal } } ``` #### 5. 自定义数据源注解 ```java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface DataSource { String value() default "master"; // 默认数据源 } ``` --- ### 三、业务层使用示例 ```java @Service public class OrderService { // 在主库执行写操作 @DataSource("master") @Transactional public void createOrder(Order order) { orderDao.insert(order); // 写入主库 // 调用其他数据源方法 updateInventory(order.getItems()); } // 在从库执行读操作 @DataSource("slave") @Transactional(readOnly = true) public Order getOrder(Long id) { return orderDao.selectById(id); } // 库存操作使用独立事务 @DataSource("inventory_db") @Transactional(propagation = Propagation.REQUIRES_NEW) public void updateInventory(List<Item> items) { inventoryDao.batchUpdate(items); } } ``` --- ### 四、关键配置要点 1. **执行顺序控制** AOP 切面必须配置更高优先级(`@Order` 值更小) ```java @Order(Ordered.LOWEST_PRECEDENCE - 1) // 确保先于事务拦截器 ``` 2. **事务传播机制** - 同数据源方法调用使用 `PROPAGATION_REQUIRED` - 跨数据源调用使用 `PROPAGATION_REQUIRES_NEW` 3. **连接泄漏防护** ```java @AfterThrowing(pointcut = "@annotation(ds)", throwing = "ex") public void handleException(DataSource ds, Exception ex) { DynamicDataSource.clear(); // 异常时清理ThreadLocal } ``` 4. **YAML 多数据源配置 ```yaml spring: datasource: master: url: jdbc:mysql://localhost:3306/master driver-class-name: com.mysql.cj.jdbc.Driver slave: url: jdbc:mysql://localhost:3306/slave driver-class-name: com.mysql.cj.jdbc.Driver inventory_db: url: jdbc:postgresql://localhost:5432/inventory driver-class-name: org.postgresql.Driver ``` --- ### 五、事务处理方案对比 | **方案** | 一致性 | 性能 | 复杂度 | 适用场景 | |------------------------|--------|------|--------|------------------------| | **ChainedTransactionManager** | 弱 | 高 | 低 | 无严格原子性要求 | | **JTA(XA)** | 强 | 低 | 高 | 金融交易等关键业务 | | **自定义事务同步** | 中 | 中 | 中 | 多数业务场景[^5] | > **最佳实践**:对数据一致性要求不高的场景(如操作日志记录)使用 ChainedTransactionManager;资金交易等关键业务使用 JTA --- ### 六、常见问题解决 1. **事务未生效** 检查 AOP 执行顺序,确保 `@DataSource` 先于 `@Transactional` 执行 2. **跨数据源事务不一致** 使用最终一致性方案: ```java @DataSource("master") @Transactional public void placeOrder(Order order) { orderDao.insert(order); try { inventoryService.reduceStock(order); // 跨数据源调用 } catch (Exception e) { // 本地补偿操作 orderDao.cancel(order.getId()); throw e; } } ``` 3. **ThreadLocal 污染** 在过滤器/拦截器中添加清理逻辑: ```java @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) { try { chain.doFilter(req, res); } finally { DynamicDataSource.clear(); // 请求结束时清理 } } ``` --- ### 相关问题 1. 如何监控多数据源下的 SQL 执行性能? 2. 在分库分表场景中如何扩展动态数据源方案? 3. Spring Boot 3.x 中多数据源事务有哪些新特性? 4. 非关系型数据库(如 MongoDB)如何参与多数据源事务? 5. 如何设计多数据源场景下的故障回滚机制?
评论 4
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值