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
相关产品推荐
相关产品推荐

