Spring WebFlux多数据源:能否在AbstractRoutingConnectionFactory中使用事务属性?
解决方案:基于包名的R2dbc多数据源路由适配
在Spring WebFlux响应式环境下,你当前的实现存在两个核心问题:一是TransactionSynchronizationManager.getCurrentTransactionName()默认仅在显式指定@Transactional(name)时才有值,二是响应式上下文依赖Reactor Context而非ThreadLocal传递,直接通过TransactionSynchronizationManager无法拿到事务方法的包名信息。以下是可落地的改造方案:
1. 自定义事务管理器,将包名注入Reactor Context
重写R2dbcTransactionManager的doBegin方法,从事务定义中提取方法全限定名并解析包名,存入Reactor Context供后续路由使用:
public class ContextAwareR2dbcTransactionManager extends R2dbcTransactionManager { @Override protected Mono<Void> doBegin(TransactionSynchronizationManager synchronizationManager, Object transaction, TransactionDefinition definition) { String transactionName = definition.getName(); // 若未指定@Transactional(name),从TransactionAttribute提取方法全限定名 if (transactionName == null && definition instanceof TransactionAttribute) { TransactionAttribute attr = (TransactionAttribute) definition; Method targetMethod = attr.getTargetMethod(); if (targetMethod != null) { transactionName = targetMethod.getDeclaringClass().getName() + "." + targetMethod.getName(); } } // 解析包名并存入Reactor Context String packageName = extractPackage(transactionName); return super.doBegin(synchronizationManager, transaction, definition) .contextWrite(ctx -> ctx.put("TX_METHOD_PACKAGE", packageName)); } private String extractPackage(String fullMethodName) { if (fullMethodName == null) return null; int lastDot = fullMethodName.lastIndexOf('.'); return lastDot != -1 ? fullMethodName.substring(0, lastDot) : null; } }
2. 修改路由连接工厂,从Context获取包名
调整CustomRoutingConnectionFactory的determineCurrentLookupKey方法,直接从Reactor Context中读取预存的包名,实现数据源路由:
public class CustomRoutingConnectionFactory extends AbstractRoutingConnectionFactory { @Override protected Mono<Object> determineCurrentLookupKey() { return Mono.deferContextual(ctxView -> { String packageName = ctxView.getOrDefault("TX_METHOD_PACKAGE", null); if (packageName == null) { return Mono.error(new RuntimeException("No transaction method package found in context")); } if (packageName.startsWith(SomeR2dbcProperties.BASE_PACKAGE)) { return Mono.just(SomeR2dbcProperties.KEY); } else if (packageName.startsWith(OtherR2dbcProperties.BASE_PACKAGE)) { return Mono.just(OtherR2dbcProperties.KEY); } return Mono.error(new RuntimeException("Not supported package: " + packageName)); }); } // 原有其他代码... }
3. 配置替换默认组件
在配置类中注册自定义事务管理器和路由连接工厂,确保上下文传递生效:
@Configuration public class R2dbcConfig { @Bean public R2dbcTransactionManager r2dbcTransactionManager(ConnectionFactory connectionFactory) { return new ContextAwareR2dbcTransactionManager(connectionFactory); } @Bean public CustomRoutingConnectionFactory routingConnectionFactory( @Qualifier("someConnFactory") ConnectionFactory someConnFactory, @Qualifier("otherConnFactory") ConnectionFactory otherConnFactory) { CustomRoutingConnectionFactory routingFactory = new CustomRoutingConnectionFactory(); Map<Object, Object> targetFactories = new HashMap<>(); targetFactories.put(SomeR2dbcProperties.KEY, someConnFactory); targetFactories.put(OtherR2dbcProperties.KEY, otherConnFactory); routingFactory.setTargetConnectionFactories(targetFactories); routingFactory.setDefaultTargetConnectionFactory(someConnFactory); // 设置默认数据源 return routingFactory; } // 注册具体的Some/Other ConnectionFactory... }
补充说明
- 使用
startsWith而非contains匹配包名,避免不同包名因包含相同字符串导致误路由; - 若需支持非事务场景的路由,可在Repository层方法中手动将包名存入Reactor Context,在
determineCurrentLookupKey中兼容处理; - 确保
@Transactional注解应用在业务层方法上,保证方法全限定名能被正确提取。
内容的提问来源于stack exchange,提问作者KimDoubleB
相关产品推荐
相关产品推荐

