运行@SpringBootTest类时连接未关闭堆积,求除@DirtiesContext外的解决方案
问题根因
Spring 测试框架默认会缓存使用过的Spring上下文,避免每次测试都重建上下文提升执行速度。每个唯一配置的上下文都会持有独立的HikariDataSource实例,每个实例自带独立的连接池,只要上下文还在缓存中,连接池就不会被关闭,连接也就不会释放,因此测试用例越多、不同配置的上下文越多,连接堆积就越严重。
方案1:调整测试环境Hikari连接池配置(最优,对构建速度无影响)
测试场景下几乎都是单线程执行用例,单个连接池完全不需要默认的10个连接,直接在测试专用的配置文件(比如application-test.properties)中添加以下配置:
# 单个连接池最大连接数设为1即可满足测试需求 spring.datasource.hikari.maximum-pool-size=1 # 空闲连接超时时间设为30秒,自动回收长时间不用的空闲连接 spring.datasource.hikari.idle-timeout=30000 # 连接最大生命周期设为1分钟 spring.datasource.hikari.max-lifetime=60000
这个方案不需要修改任何测试代码,也不会影响上下文缓存,构建速度和之前完全一致,按默认32个上下文缓存上限计算,总连接数最多只有32,远低于300的连接上限。
方案2:限制Spring测试上下文缓存大小(次优,几乎无性能影响)
Spring测试默认的上下文缓存数量上限是32,可以通过JVM启动参数调整这个值,进一步控制同时存在的上下文数量,从而控制总连接数:
-Dspring.test.context.cache.maxSize=20
调整后最多只会缓存20个上下文,超过的最旧上下文会被自动销毁,对应的连接池也会被关闭释放连接,总连接数可以控制在20以内。
方案3:按需标记脏上下文(比全类加@DirtiesContext性能好很多)
不要给所有测试类加@DirtiesContext,只给修改了上下文状态(比如修改了数据库表结构、修改了Bean定义)的测试类添加,并且指定类模式为AFTER_CLASS,只在整个测试类跑完后才销毁上下文:
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) public class YourTestClass { // 测试代码 }
这样大部分配置一致的测试类还是可以复用上下文,只有少数特殊测试类会触发上下文重建,整体构建速度下降非常少。
方案4:自定义测试监听器自动清理连接池(特殊场景使用)
如果以上方案都不满足需求,可以自定义TestExecutionListener,在测试类执行完成后主动关闭当前上下文的连接池:
public class DataSourceCleanupListener implements TestExecutionListener { @Override public void afterTestClass(TestContext testContext) throws Exception { DataSource dataSource = testContext.getApplicationContext().getBean(DataSource.class); if (dataSource instanceof HikariDataSource hikariDataSource) { hikariDataSource.close(); } } }
然后在测试父类上添加注解注册这个监听器即可:
@TestExecutionListeners(listeners = DataSourceCleanupListener.class, mergeMode = MergeMode.MERGE_WITH_DEFAULTS) public abstract class BaseIntegrationTest { // 基础测试配置 }
注意这个方案仅适用于每个测试类独立使用上下文的场景,如果上下文被多个测试类复用,提前关闭连接池会导致后续测试报错。
内容的提问来源于stack exchange,提问作者Cécile Fecherolle

