SpringBoot自动配置坑了我一把,原来是这样绕过去的

文章来源声明: 原文作者:阿橙的百宝箱; 来源站点:掘金; 原文链接:https://juejin.cn/post/7692296625806032936; 本文基于上述来源整理/加工,觅优补充点评,仅供技术学习交流。版权归原作者所有。
觅优短评

多数据源混用时,自动配置的隐式约定最容易埋雷。这篇复盘给出显式条件注解的解法,还附了四条避坑清单,适合正被连接池问题困扰的后端同学对照排查。

上周五凌晨,我们的支付对账服务突然开始疯狂打印`DataSource is closed`的报错。监控大盘显示数据库连接池活跃连接数归零,而流量明明没有任何波动——这场景是不是让你想起某个不眠夜?

现象:自动配置的"智能"成了定时炸弹

问题出现在一个日均处理 200w+ 账务记录的 SpringBoot 2.7 服务。在接收到 Kafka 消息后,服务会通过 JPA 写入 MySQL,核心逻辑简单到只有三行:

@Transactional 
public void handlePayment(PaymentMessage msg) {
    paymentRepository.save(new Payment(msg.getTxId(), msg.getAmount()));
}

但诡异的是,当 Kafka 消费者重启时,偶尔会触发整个连接池不可用。日志里明晃晃的HikariPool-1 - Database connection pool is closed和Cannot get a connection, pool error交替出现,而这时离服务启动已经过去了 15 分钟。

根因:多数据源下的自动配置博弈

经过反复复现和调试,发现问题出在 SpringBoot 的 自动配置竞速条件 上。我们的项目由于历史原因混用了两个数据源:

  1. 主数据源:通过@ConfigurationProperties(prefix = "spring.datasource")显式配置
  2. 监控数据源:在另一个@Configuration类里手动定义的DataSource

SpringBoot 的自动配置机制在这里玩了个危险游戏:

  1. DataSourceAutoConfiguration 看到存在自定义数据源配置后,会跳过主数据源初始化
  2. 但 HikariAutoConfiguration 仍然会尝试基于spring.datasource.hikari.*参数构建连接池
  3. 当监控数据源先完成初始化时,主数据源的 Hikari 池会被误判为"备用池"而悄悄关闭

用代码来说,错误场景是这样的:

// 错误配置示例:两个数据源配置互相干扰
@Configuration
public class MonitoringConfig {
    @Bean 
    @ConfigurationProperties("monitoring.datasource")
    public DataSource monitoringDataSource() {
        return DataSourceBuilder.create().build(); // 这个先初始化了!
    }
}

// application.properties
spring.datasource.url=jdbc:mysql://primary-db
spring.datasource.hikari.maximum-pool-size=20
monitoring.datasource.url=jdbc:mysql://monitoring-db

解法:用明确的条件注解划清界限

正确的做法是强制让自动配置按我们设定的路线走:

@Configuration
// 关键注解:明确排除自动数据源配置
@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
public class PrimaryDataSourceConfig {
    
    @Primary
    @Bean
    @ConfigurationProperties("spring.datasource.hikari")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().type(HikariDataSource.class).build();
    }
}

@Configuration
public class MonitoringConfig {
    
    @Bean
    @ConfigurationProperties("monitoring.datasource")
    public DataSource monitoringDataSource() {
        return DataSourceBuilder.create().build();
    }
}

  • 性能对比*:改造后,服务启动时间从原来偶尔出现的 15 分钟连接池故障,降低到完全稳定的 30 秒内可用。

避坑清单:多数据源场景下的死亡陷阱

  1. 隐式依赖陷阱:当你混合使用spring-boot-starter-data-jpa和手动数据源时,HibernateJpaAutoConfiguration可能偷偷使用错误的数据源
  2. 连接池参数失效:如果没显式指定type = HikariDataSource.class,连接池参数可能被默认配置覆盖
  3. 监控指标错乱:多个 Hikari 池的 JMX 注册名称冲突会导致监控数据互相覆盖
  4. 测试环境假象:用 H2 内存数据库测试时可能掩盖这个问题,因为 H2 的连接管理行为与生产数据库不同

写在最后

SpringBoot 自动配置的本质是一套精巧的"约定优于配置"机制,但当你需要打破这些约定时,必须用显式声明替代隐式魔法。下次看到数据库连接池神秘关闭时,不妨先检查是否存在配置边界模糊的问题——你在多数据源项目里还踩过哪些坑?欢迎分享你的战场故事。