如何配置Spring R2DBC实现读写分离,分别使用只读和读写数据库URL
最优实现方案:基于AbstractRoutingConnectionFactory的动态数据源路由
该方案完全适配Spring Webflux响应式编程模型,无业务侵入性,同时能保证读写请求的正确路由。
1. 配置双数据源参数
在application.yml中分别配置主库和只读副本的连接参数:
spring: r2dbc: primary: url: r2dbc:postgresql://主节点地址:5432/数据库名 username: 账号 password: 密码 pool: max-size: 20 replica: url: r2dbc:postgresql://只读副本地址:5432/数据库名 username: 账号 password: 密码 pool: max-size: 30
2. 定义路由规则与动态路由工厂
首先定义数据源类型枚举:
public enum DataSourceType { PRIMARY, REPLICA }
自定义路由连接工厂,继承Spring R2DBC提供的AbstractRoutingConnectionFactory,自动从上下文判断当前需要的数据源:
public class RoutingConnectionFactory extends AbstractRoutingConnectionFactory { @Override protected Mono<Object> determineCurrentLookupKey() { // 优先从事务上下文判断:读写事务强制走主库,只读事务走副本 return ReactiveTransactionSynchronizationManager.forCurrentTransaction() .map(tsm -> tsm.isCurrentTransactionReadOnly() ? DataSourceType.REPLICA : DataSourceType.PRIMARY) // 无事务时从响应式上下文读取自定义标识,默认兜底走主库 .defaultIfEmpty(Mono.deferContextual(ctx -> ctx.getOrDefault("DATA_SOURCE_TYPE", DataSourceType.PRIMARY))); } }
3. 注册双数据源与路由工厂
编写配置类,将路由工厂设置为项目默认使用的连接工厂:
@Configuration public class R2dbcDataSourceConfig { @Bean @ConfigurationProperties("spring.r2dbc.primary") public ConnectionFactory primaryConnectionFactory() { return ConnectionFactoryBuilder.create().build(); } @Bean @ConfigurationProperties("spring.r2dbc.replica") public ConnectionFactory replicaConnectionFactory() { return ConnectionFactoryBuilder.create().build(); } @Bean @Primary public ConnectionFactory routingConnectionFactory( @Qualifier("primaryConnectionFactory") ConnectionFactory primary, @Qualifier("replicaConnectionFactory") ConnectionFactory replica ) { Map<Object, Object> targetDataSources = Map.of( DataSourceType.PRIMARY, primary, DataSourceType.REPLICA, replica ); RoutingConnectionFactory routingFactory = new RoutingConnectionFactory(); routingFactory.setTargetDataSources(targetDataSources); routingFactory.setDefaultTargetDataSource(primary); return routingFactory; } @Bean public R2dbcEntityTemplate r2dbcEntityTemplate(ConnectionFactory routingConnectionFactory) { return new R2dbcEntityTemplate(routingConnectionFactory); } }
4. 按需切换数据源的两种使用方式
- 注解自动切换(推荐)
只读查询方法加@Transactional(readOnly = true)即可自动走副本,写操作加普通@Transactional自动走主库:// 只读查询自动路由到副本 @Transactional(readOnly = true) public Flux<Order> listUserOrders(Long userId) { return orderRepository.findByUserId(userId); } // 写操作自动路由到主库 @Transactional public Mono<Order> createOrder(Order order) { return orderRepository.save(order); } - 手动指定切换
无事务的查询场景可手动往响应式上下文塞入标识完成切换:public Flux<Goods> listHotGoods() { return goodsRepository.findByHotFlag(true) .contextWrite(ctx -> ctx.put("DATA_SOURCE_TYPE", DataSourceType.REPLICA)); }
方案优势
- 安全兜底:默认走主库,不会出现写请求误打到只读副本报错的问题
- 业务低侵入:绝大多数场景通过事务注解即可完成切换,不需要修改原有业务逻辑
- 适配响应式模型:完全贴合Webflux的异步上下文机制,不会出现线程上下文传递的问题
- 易扩展:后续新增多个只读副本时,仅需要修改路由工厂的逻辑,加入轮询/随机等负载均衡策略即可
内容的提问来源于stack exchange,提问作者fyrkov
相关产品推荐
相关产品推荐

