You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何配置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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 23:39:03