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

SpringBoot 3.4.0中CustomRoutingDataSource读写数据源路由失效问题

解决SpringBoot中只读事务路由数据源失效问题

问题根源

你遇到的核心问题是:数据源连接的获取时机早于事务只读状态的设置。Spring的TransactionSynchronizationManager.setCurrentTransactionReadOnly(true)是在事务切面拦截器执行时才设置的,但如果getConnection在这之前被调用(比如自动配置Bean初始化、提前的数据访问操作),determineCurrentLookupKey方法就会读取到错误的事务状态,导致路由到读写数据源。

解决方案

1. 用TransactionAwareDataSourceProxy包装路由数据源

Spring提供的TransactionAwareDataSourceProxy会拦截连接获取请求,确保在事务上下文完全初始化(包括只读状态设置)后,再调用底层数据源的getConnection方法。这是最直接有效的解决方式。

配置示例

@Configuration
public class DataSourceConfig {

    // 主数据源(读写)
    @Bean
    public DataSource masterDataSource() {
        HikariDataSource ds = new HikariDataSource();
        ds.setJdbcUrl("jdbc:mysql://master-host:3306/your_db");
        ds.setUsername("user");
        ds.setPassword("pass");
        // 其他连接池配置
        return ds;
    }

    // 从数据源(只读)
    @Bean
    public DataSource slaveDataSource() {
        HikariDataSource ds = new HikariDataSource();
        ds.setJdbcUrl("jdbc:mysql://slave-host:3306/your_db");
        ds.setUsername("user");
        ds.setPassword("pass");
        // 其他连接池配置
        return ds;
    }

    // 自定义路由数据源
    @Bean
    public CustomRoutingDataSource routingDataSource(DataSource master, DataSource slave) {
        CustomRoutingDataSource routingDS = new CustomRoutingDataSource();
        Map<Object, Object> targetDS = new HashMap<>();
        targetDS.put("master", master);
        targetDS.put("slave", slave);
        routingDS.setTargetDataSources(targetDS);
        routingDS.setDefaultTargetDataSource(master); // 默认走主库
        return routingDS;
    }

    // 用TransactionAwareDataSourceProxy包装路由数据源
    @Bean
    public DataSource transactionAwareDS(CustomRoutingDataSource routingDS) {
        return new TransactionAwareDataSourceProxy(routingDS);
    }

    // 所有数据访问组件(JdbcTemplate、EntityManagerFactory等)都使用这个代理数据源
    @Bean
    public JdbcTemplate jdbcTemplate(DataSource transactionAwareDS) {
        return new JdbcTemplate(transactionAwareDS);
    }
}

2. 验证路由逻辑的正确性

确保你的CustomRoutingDataSource的路由判断逻辑完全依赖Spring事务同步管理器的状态,没有其他干扰:

public class CustomRoutingDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        // 直接通过Spring提供的工具类判断当前事务是否只读
        return TransactionSynchronizationManager.isCurrentTransactionReadOnly() ? "slave" : "master";
    }
}

3. 确保事务配置生效

  • 目标方法上必须正确标注@Transactional(readOnly = true),比如:
    @RestController
    public class FooBarController {
        @Autowired
        private JdbcTemplate jdbcTemplate;
    
        @GetMapping("/foo-bar")
        @Transactional(readOnly = true)
        public String fooBar() {
            // 这里的数据库操作会路由到从库
            String result = jdbcTemplate.queryForObject("SELECT name FROM test LIMIT 1", String.class);
            return result;
        }
    }
    
  • 检查SpringBoot的事务自动配置是否未被禁用(不要在@SpringBootApplication中排除DataSourceTransactionManagerAutoConfiguration)。

4. 排查提前获取连接的操作

如果存在@PostConstruct、Bean初始化方法中的数据库访问操作,这些操作会在事务上下文建立前执行,导致路由到主库。可以:

  • 将这类操作移到事务方法内部;
  • 用@Lazy延迟初始化相关Bean,确保它们在事务上下文建立后再执行初始化。

内容的提问来源于stack exchange,提问作者ibre5041

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 23:43:27