Очень много приходилось работать со Spring Data Repositories, и проблема с соединениями с БД была не такой уже и редкой. То забудешь сессию закрыть, то что-то пойдет не так и нужный метод не вызывается и поиск ошибок, когда заканчиваются соединения с БД превращаются в настоящую проблему.
Я обычно использую postgresql для продуктива и h2db для тестирования, примеры буду приводить для postgresql.
Вот такой командой можно получить максимальное количество допустимых соединений СУБД. Причем нужно понимать что если у вас на одном сервере несколько бд, то соединения считаются в общем, что нужно учитывать для настройки пулов.
select setting::int max_conn from pg_settings where name=$$max_connections$$
Запрос, которым можно получить число активных соединений.
SELECT sum(numbackends) FROM pg_stat_database;
После очередных изменений стали появляться ошибки.
org.hibernate.exception.JDBCConnectionException: Could not open connection
Не мог понять почему через некоторое время приложение просто падало. Понятно, что заканчиваются соединения в пуле, осталось разобраться из-за чего они заканчиваются.
При настройке источника данных (javax.sql.DataSource) подключение нужно настроить с использованием пула. Пул HikariCP (com.zaxxer.HikariCP) поддерживает механизм поиска connection leak. Для того чтобы его включить, нужно при настройке пула задать таймаут через который пул будет понимать, что соединение зависло и не закрылось.
Значение таймаута задается в миллисекундах. Получилась вот такая конфигурация:
HikariConfig config = new HikariConfig();
config.setMaximumPoolSize(100);
config.setIdleTimeout(30000);
config.setLeakDetectionThreshold(30000); // <-- таймаут для включения поиска connection leak
config.setConnectionTimeout(TimeUnit.SECONDS.toMillis(50));
config.setDataSourceClassName(dataSourceProperties.getDriverClassName());
config.addDataSourceProperty("url", dataSourceProperties.getUrl());
if (dataSourceProperties.getUsername() != null) {
config.addDataSourceProperty("user", dataSourceProperties.getUsername());
} else {
config.addDataSourceProperty("user", ""); // HikariCP doesn't allow null user
}
if (dataSourceProperties.getPassword() != null) {
config.addDataSourceProperty("password", dataSourceProperties.getPassword());
} else {
config.addDataSourceProperty("password", ""); // HikariCP doesn't allow null password
}
return new HikariDataSource(config);
После настройки пула в логе стали появляться исключения
ProxyLeakTask: Connection leak detection triggered for connection
Стектрейс должен привести к методу службы или репозитория где возник leak. Как же я удивился, когда это были обычные методы для чтения.
public interface CardFileRepository extends JpaRepository<CardFile,Long> {
Stream<CardFile> findAllByCard(Card card);
}
Дальше вспоминаем, что Stream в отличае от того же List мало того, что может быть прочитан только один раз, так еще и вычисление значения элемента Stream ленивое, то есть выполняется при обращении. В документации spring написано, что обязательно нужно закрывать Stream вручную через вызов метода close или использовать try-with-resources.
В статье по использованию объектов spring-data тоже пишут закрывайте stream. Как это часто бывает, полезная статья вышла уже после того, как ты ошибок понаделал и героически их починил.