如何配置Spring Boot在@JdbcTest测试类结束后关闭连接池?
这问题我之前帮团队踩过坑,确实是Spring Boot测试上下文隔离带来的典型痛点,咱们一步步拆解优化方案:
Spring Boot测试默认会为每个配置不同的@ContextConfiguration创建全新的ApplicationContext,而每个上下文都会初始化独立的DataSource连接池。10+个测试类就意味着10+个连接池,每个池默认还有多个活跃连接,分分钟打满PostgreSQL的max_connections上限,触发"连接过多"的报错。
1. 先做上下文复用(零成本见效)
Spring Boot测试会自动缓存完全一致的上下文,所以咱们可以把公共配置抽成基础测试类,让所有测试类继承它,尽量减少上下文差异:
// 基础测试父类,统一公共配置 @RunWith(SpringRunner.class) @JdbcTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @Sql(scripts = {"/schema-init.sql", "/test-data.sql"}) @ContextConfiguration(classes = {CommonDao.class, CommonService.class}) public abstract class BasePostgresTest { // 可以放公共的依赖注入、通用测试方法 }
然后业务测试类只需要补充自己的特定配置:
public class UserDaoTest extends BasePostgresTest { // 只需要注入当前测试需要的UserDao @Autowired private UserDao userDao; // 测试方法... }
只要上下文配置差异不大,Spring会自动复用缓存的上下文,连接池数量会骤减。
2. 限制单个连接池的连接数
在src/test/resources/application-test.properties里给HikariCP加个上限,避免单个池占用太多连接:
# 每个连接池最多3个连接,根据测试需求调整 spring.datasource.hikari.maximum-pool-size=3 spring.datasource.hikari.minimum-idle=1 spring.datasource.hikari.idle-timeout=60000
就算有10个连接池,总连接数也只有30,远低于PostgreSQL默认的100上限。
3. 统一清理连接池(替代复制粘贴的临时代码)
你之前的临时代码是手动保存DataSource,其实可以用Spring的TestExecutionListener统一处理,不用每个测试类复制粘贴:
先写一个自定义监听器:
public class DataSourceCleanupListener extends AbstractTestExecutionListener { private static final List<HikariDataSource> DATA_SOURCES = new ArrayList<>(); // 测试实例初始化时,收集连接池 @Override public void prepareTestInstance(TestContext testContext) throws Exception { DataSource dataSource = testContext.getApplicationContext().getBean(DataSource.class); if (dataSource instanceof HikariDataSource) { DATA_SOURCES.add((HikariDataSource) dataSource); } } // 整个测试类执行完后,销毁所有连接池 @Override public void afterTestClass(TestContext testContext) throws Exception { for (HikariDataSource ds : DATA_SOURCES) { if (!ds.isClosed()) { ds.close(); } } DATA_SOURCES.clear(); } }
然后把监听器加到基础测试类上:
@RunWith(SpringRunner.class) @JdbcTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @Sql(scripts = {"/schema-init.sql", "/test-data.sql"}) @ContextConfiguration(classes = {CommonDao.class}) // 合并默认监听器,加上自定义的清理监听器 @TestExecutionListeners(mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS, listeners = DataSourceCleanupListener.class) public abstract class BasePostgresTest { }
这样每个测试类执行完会自动销毁对应的连接池,彻底释放连接。
4. 长期最优方案:用Testcontainers管理PostgreSQL容器
如果团队愿意引入Testcontainers,这是最靠谱的解决方案:它会在测试启动时创建临时的PostgreSQL容器,所有测试类共享这个容器,测试结束后自动销毁,完全避免连接泄漏和上下文隔离的问题,而且测试环境和生产环境100%一致。
配置步骤:
- 先加Maven依赖:
<dependency> <groupId>org.testcontainers</groupId> <artifactId>postgresql</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.testcontainers</groupId> <artifactId>junit-jupiter</artifactId> <scope>test</scope> </dependency>
- 写Testcontainers配置类:
@TestConfiguration public class TestcontainersPostgresConfig { @Bean @ServiceConnection // 自动把容器的连接信息注入到Spring的DataSource public PostgreSQLContainer<?> postgresContainer() { return new PostgreSQLContainer<>("postgres:15-alpine") .withDatabaseName("test_db") .withUsername("test_user") .withPassword("test_pass"); } }
- 改造基础测试类:
@SpringBootTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @Import(TestcontainersPostgresConfig.class) @Sql(scripts = {"/schema-init.sql", "/test-data.sql"}) public abstract class BasePostgresTest { }
这样所有测试类都会共享同一个PostgreSQL容器,连接池只初始化一次,彻底解决连接过多的问题。
内容的提问来源于stack exchange,提问作者TMTH

