Вопрос или проблема
Я разрабатываю чистое приложение на Kotlin. В моем приложении мне нужно получить доступ к базе данных.
Мой текущий стек для этого:
- mybatis версия = “3.5.16”
- flyway версия = “10.19.0”
- hikari версия = “6.0.0”
Для разработки я использую Intellij Idea. Это важно. После некоторого времени разработки у меня возникает ошибка в базе данных – слишком много соединений. Я думаю, это связано с отладкой и завершением процесса. Я думаю, что это мешает аккуратно закрыть соединение.
Я не нашел никакой причины. Когда я делаю это вручную много раз, я не замечал, что соединение утекло (show processlist; пусто)
Вот код, который я выполняю в начале приложения:
class DB(props: AppProperties) {
private val hikariSource: HikariDataSource
private var batisSessionFactory: SqlSessionFactory
// Конструктор
init {
// HikariCP
hikariSource: HikariDataSource = HikariDataSource()
hikariSource.jdbcUrl = props.databaseUrl
hikariSource.username = props.databaseUser
hikariSource.password = props.databasePass
hikariSource.maximumPoolSize = 5
hikariSource.isAutoCommit = true
// Flyway
val config = Flyway
.configure()
.dataSource(dataSource)
.baselineOnMigrate(true)
val flyway = Flyway(config)
flyway.migrate()
// MyBatis
batisSessionFactory = createSqlSessionFactory(hikariSource).apply {
configuration.addMapper(UserDao::class.java)
}
}
fun <T> withUserDao(autoCommit: Boolean = true, block: UserDao.() -> T) : T {
return batisSessionFactory.openSession(autoCommit).use {
val userDao = it.getMapper(UserDao::class.java)
block(userDao)
}
}
private fun createSqlSessionFactory(dataSource: DataSource): SqlSessionFactory {
val configuration = Configuration()
// Передайте свой пользовательский DataSource в окружение
val environment = org.apache.ibatis.mapping.Environment(
props.environment,
org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory(),
dataSource
)
configuration.environment = environment
return SqlSessionFactoryBuilder().build(configuration)
}
}
Вот и всё. Этот простой код вызывает утечки соединений. И я не уверен, кто отвечает за эту утечку. Hikari? Flyway? MyBatis?
Для разработчиков на Java о функции withUserDao()
- открывает сессию batis как try-with-resources в стиле Java
- внутри блока try получает маппер для UserDao и передает его в лямбду внешнего блока
- после работы с UserDao – закрывает сессию batis
Ответ или решение
Разбор проблемы утечек соединений при использовании Flyway, MyBatis и HikariCP в Kotlin
В процессе разработки ваших приложений на Kotlin, взаимодействующих с базой данных, вы столкнулись с проблемой утечек соединений, проявляющейся в ошибках "слишком много соединений". Давайте рассмотрим возможные причины этой проблемы и предложим пути её решения.
1. Понимание используемых технологий
Ваш стек состоит из трех ключевых компонентов:
- MyBatis (версия 3.5.16): ORM-фреймворк, который управляет соединениями и маппингом данных.
- Flyway (версия 10.19.0): инструмент для миграции баз данных, использующий соединение с БД для выполнения миграций.
- HikariCP (версия 6.0.0): высокопроизводительный пул соединений, отвечающий за эффективное управление соединениями с БД.
Каждый из этих компонентов играет важную роль, и они все могут быть причиной утечки соединений, если не настроены или не используются должным образом.
2. Возможные причины утечек соединений
-
Управление соединениями:
- Вы используете
openSession(autoCommit)
в методеwithUserDao
. Эта функция возвращает сессию MyBatis, которая должна закрываться после завершения работы. Однако если поток выполнения прерывается до закрытия, могут возникнуть неосвобожденные соединения.
- Вы используете
-
Проблемы с Flyway:
- Flyway выполняет миграции в рамках одного соединения. Если миграции происходят при частых изменениях вашего кода, это может также привести к лишним соединениям.
-
Проблемы в процессе отладки:
- Как вы уже заметили, если вы часто останавливаете и перезапускаете приложение в процессе отладки, это может привести к неисправно закрытым соединениям. HikariCP не всегда успевает корректно закрыть соединения при аварийном завершении.
3. Рекомендации по исправлению проблемы
-
Убедитесь, что все соединения закрываются. Использование
use
с сессией MyBatis – это хорошая практика. Однако следует иметь в виду, что если внутри блокаuse
происходит исключение, соединение может не освободиться. Рассмотрите возможность обработки исключений:fun <T> withUserDao(autoCommit: Boolean = true, block: UserDao.() -> T): T { return batisSessionFactory.openSession(autoCommit).use { session -> val userDao = session.getMapper(UserDao::class.java) try { block(userDao) } catch (e: Exception) { // Логируйте исключение или обрабатывайте его throw e } } }
-
Проверка конфигурации HikariCP: Проверьте настройки вашего пула соединений. Убедитесь, что параметры, такие как максимальное количество соединений, правильны и соответствуют требованиям вашего приложения. Например, вы можете уменьшить значение
maximumPoolSize
, чтобы избежать создания слишком большого количества соединений. -
Заботьтесь о Flyway: Убедитесь, что миграции не выполняются без необходимости. Можно выполнять миграции только при необходимости, а не при каждом запуске приложения.
-
Мониторинг соединений: Используйте возможности HikariCP для мониторинга состояния соединений. Например, настраивайте логи соединений, чтобы лучше понять, какие соединения остаются открытыми.
4. Заключение
Утечки соединений могут иметь много причин, и важно проводить тщательный анализ кода и конфигураций вашего приложения. Рекомендуется следить за состоянием соединений и ввести механизмы, позволяющие выявлять и устранять такие проблемы.
Если все эти рекомендации не устранют проблему, возможно, стоит подумать о дополнительной диагностике с использованием инструментов профилирования JVM, чтобы увидеть, где именно ваши соединения не закрываются.
Надеюсь, эти рекомендации помогут вам разобраться с вашей проблемой с утечками соединений в приложении. Не забывайте, что тщательное управление ресурсами – ключ к стабильной и надежной работе приложений.