如何在Ktor集成测试中复用或关闭MySQL连接池?
Ktor集成测试中Hikari连接池导致MySQL连接数超限的解决方法
问题描述
我按照Ktor的Testing指南为后端应用做集成测试,采用Docker部署的真实MySQL数据库(搭配Hikari连接池和Exposed ORM)。测试能正常运行,但存在连接泄漏问题:每个testApplication测试用例执行完毕后,连接池不会被销毁,会一直保留到所有测试结束。结果每个测试都会新建一个HikariPool,例如设置maximumPoolSize = 4时,跑20个测试就会创建80个连接,触发MySQL的Too Many Connection错误。我需要在测试间复用连接池,或能手动销毁连接,且不想在GitHub Actions中为每个测试重启MySQL。
现有代码结构
集成测试代码
class ExampleIntegrationTest { @Test fun `Integration Test for the Example endpoints`() = testApplication { environment { config = ApplicationConfig(application_integration_test.conf) } val response = client.post("/path/example/test") { header("Authorization", dummyToken) header(contentType, APPLICATION_JSON) body?.let { setBody(body) } } assertEquals(201, response.status) assertEquals("[]", response.bodyAsText()) ... } }
测试配置文件application_integration_test.conf
database { driverClassName = com.mysql.cj.jdbc.Driver jdbcUrl = "jdbc:mysql://localhost:3307/test?useSSL=false" username = testuser password = password maximumPoolSize = 4 isAutoCommit = false transactionIsolation = TRANSACTION_REPEATABLE_READ connectionTimeout = 60000 maxLifetime = 20000000 idleTimeout = 10000000 leakDetectionThreshold = 8000 }
Docker Compose配置
version: '3' services: mysql-for-unit-tests: image: test/mysql-test platform: linux/x86_64 build: context: ../ dockerfile: docker/mysqltest/Dockerfile ports: - "3307:3307" environment: MYSQL_DATABASE: test MYSQL_USER: testuser MYSQL_PASSWORD: password MYSQL_ROOT_PASSWORD: root volumes: - ./mysqltest/db:/var/lib/mysql
MySQL Dockerfile
FROM mysql:5.7-debian # copy conf file COPY ./docker/mysqltest/mysql.conf.d/test-api.cnf /etc/mysql/mysql.conf.d/test-api.cnf # Set debian default locale to ja_JP.UTF-8 RUN apt-get update && \ apt-get install -y locales && \ rm -rf /var/lib/apt/lists/* && \ echo "ja_JP.UTF-8 UTF-8" > /etc/locale.gen && \ locale-gen ja_JP.UTF-8 ENV LC_ALL ja_JP.UTF-8
解决方法
1. 测试类级别复用单个连接池
将连接池的初始化和销毁移到测试类的@BeforeAll和@AfterAll方法中,让整个测试类共用一个连接池,避免重复创建:
class ExampleIntegrationTest { private lateinit var dataSource: HikariDataSource @BeforeAll fun setupDataSource() { val config = HikariConfig().apply { jdbcUrl = "jdbc:mysql://localhost:3307/test?useSSL=false" username = "testuser" password = "password" maximumPoolSize = 4 // 同步application_integration_test.conf中的其他配置 isAutoCommit = false transactionIsolation = "TRANSACTION_REPEATABLE_READ" connectionTimeout = 60000 } dataSource = HikariDataSource(config) } @AfterAll fun shutdownDataSource() { dataSource.close() // 测试类结束后销毁连接池 } @Test fun `Integration Test for the Example endpoints`() = testApplication { environment { config = ApplicationConfig(application_integration_test.conf) // 覆盖应用模块中的数据源,使用预创建的全局连接池 module { // 假设应用通过DI容器获取数据源,这里替换为全局dataSource single<DataSource> { dataSource } } } // 原有测试逻辑不变 val response = client.post("/path/example/test") { header("Authorization", dummyToken) header(contentType, APPLICATION_JSON) body?.let { setBody(body) } } assertEquals(201, response.status) assertEquals("[]", response.bodyAsText()) ... } }
2. 单个测试用例结束后手动销毁连接池
如果必须为每个测试用例单独创建连接池,可在测试结束后手动关闭数据源:
@Test fun `Integration Test for the Example endpoints`() = testApplication { environment { config = ApplicationConfig(application_integration_test.conf) } // 原有测试逻辑 val response = client.post("/path/example/test") { header("Authorization", dummyToken) header(contentType, APPLICATION_JSON) body?.let { setBody(body) } } assertEquals(201, response.status) assertEquals("[]", response.bodyAsText()) // 从应用中获取数据源并关闭 val dataSource = application.attributes.get(DataSourceKey) // 需提前定义DataSourceKey存储数据源 (dataSource as HikariDataSource).close() }
注意:需确保Ktor应用将DataSource实例存入
ApplicationAttributes或DI容器,方便测试时获取。
3. 优化Hikari配置减少连接占用
调整Hikari参数,让闲置连接更快释放,避免连接堆积:
database { // 保持其他原有配置不变 maxLifetime = 600000 // 缩短连接最大生命周期为10分钟 idleTimeout = 300000 // 闲置连接5分钟后回收 maximumPoolSize = 2 // 根据测试需求降低连接池上限 }
4. 可选:用Testcontainers管理数据库生命周期
若允许测试时重启数据库,Testcontainers可自动为每个测试类/用例创建和销毁MySQL容器,彻底避免连接泄漏。但此方案不适合需要复用同一数据库实例的场景,可根据需求选择。
内容的提问来源于stack exchange,提问作者shokdjsa-dkfdk.ss
相关产品推荐
相关产品推荐

