
Недавно загрузил пет-проект в сеть и понял, что Hibernate ну совсем не умеет обращаться с соединениями. Мой лимит на десять штук быстро исчерпывался, и приложение приходилось перезапускать, чтобы снова заработало.
Message: The internal connection pool has reached its maximum size and no connection is currently available!
Description: The server encountered an unexpected condition that prevented it from fulfilling the request.
Exception:
org.hibernate.HibernateException: The internal connection pool has reached its maximum size and no connection is currently available!
at org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl$PooledConnections.pollConnection(DriverManagerConnectionProviderImpl.java:423)
at org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl.getConnection(DriverManagerConnectionProviderImpl.java:204)
at org.hibernate.internal.NonContextualJdbcConnectionAccess.obtainConnection(NonContextualJdbcConnectionAccess.java:38)
at org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl.acquireConnectionIfNeeded(LogicalConnectionManagedImpl.java:106)
at org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl.getPhysicalConnection(LogicalConnectionManagedImpl.java:136)
Получал такое исключение, и сначала проверил все сессии и соединения. Обнаружил, что всё закрываю, и потом понял, что не использую пул соединений. Я не тупой, просто пул мне до этого в проекте не был нужен (я с ним долгое время работал локально и был единственным юзером). А теперь нужно бы его добавить.
Что такое connection pool и зачем он нужен
Про пулы соединений я уже писал пару лет назад, когда делал проект на Спринге и подключал туда Hikari. Но не случится ничего плохого, если я немного повторюсь.
Connection pool / пул соединений – по сути, кэширование соединений с базой данных, чтобы использовать их заново. При каждом обращении к базе приложение должно установить соединение с ней, а эта операция довольно дорогостоящая и медленная. Использование пула помогает уменьшить нагрузку, увеличить скорость и сэкономить ресурсы (и отделаться от назойливых ошибок вроде той в начале статьи, или даже переполнения памяти в другом моём проекте).
Теперь, после закрытия соединения (connection.close()) оно переходит в пул, где лежит заданное время. И если какому-то методу снова понадобится обратиться к базе, ему не нужно будет устанавливать его заново – он просто получит уже установленное соединение. Если же какое-то время никто не хочет использовать соединения в пуле повторно, то они наконец-то закрываются с концами.
Ниже покажу, как задавать максимальное число соединений в пуле и настраивать частоту очистки и время жизни неиспользуемых соединений.
Но сначала немного про выбранный мной инструмент (c3p0).
c3p0 vs Hikari
В гитхабе создатель описывает своё творение как высокопроизводительную библиотеку пула соединений JDBC с поддержкой кэширования и повторного использования PreparedStatements. Это одна из самых популярных опций, и одно из главных преимуществ – удобная интеграция с Hibernate и гибкая настройка.
У Hikari, в сравнении с c3p0, выше надёжность, чаще обновления и лучше поддержка Spring. Оно и понятно, сам проект крупнее. Но я решил попробовать c3p0 в этом проекте.
Добавление в проект и конфигурация c3p0
Для подключения библиотеки добавляем зависимость в pom.xml:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-c3p0</artifactId>
<version>5.3.6.Final</version>
</dependency>
Этого должно хватить для версий hibernate новее пятой. Иначе можно добавить ещё:
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.5</version>
</dependency>
Конфигурация в xml-файле
Теперь можно настроить пул. Так как у меня в проекте уже есть xml файл с настройками подключения к базе, туда я и буду добавлять новые строчки.
<hibernate-configuration>
<session-factory>
<property name="connection.driver_class">net.sourceforge.jtds.jdbc.Driver</property>
<property name="connection.url">*********</property>
<property name="connection.username">*******</property>
<property name="connection.password">*******</property>
<property name="dialect">org.hibernate.dialect.SQLServerDialect</property>
<property name="hibernate.c3p0.max_size">1000</property>
<property name="hibernate.c3p0.timeout">7200</property>
<property name="hibernate.c3p0.max_statements">150</property>
<property name="hibernate.c3p0.idle_test_period">600</property>
<mapping class="app.models.User" />
<...>
</session-factory>
</hibernate-configuration>
После логина, пароля и адреса базы и перед всеми маппингами я добавил четыре строчки.
hibernate.c3p0.max_size – максимальное количество соединений
hibernate.c3p0.timeout – время (в секундах), после которого неиспользуемое соединение будет закрыто
hibernate.c3p0.max_statements – сколько prepared statementов кэшировать
hibernate.c3p0.idle_test_period – интервал (в секундах) для проверки неактивных соединений в пуле
idle_test_period должен быть меньше timeout для корректной работы. Каждые idle_test_period секунд все соединения в пуле будут проверяться. Если они не используются (idle) и пролежали больше timeout, то будут закрыты.
Среди других настроек, которые можно прописать в xml:
hibernate.c3p0.min_size – минимальное количество соединений (которые всегда будут в пуле)
hibernate.c3p0.acquire_increment – сколько новых соединений добавлять при нехватке (по умолчанию добавляется три)
hibernate.c3p0.validate – проверять ли соединение перед использованием
hibernate.c3p0.maxIdleTimeExcessConnections – указывает время в секундах, через которое удаляются все idle соединения, пока не будет достигнуто количество min_size. Показатель должен быть меньше timeout, чтобы иметь эффект.
hibernate.c3p0.unreturnedConnectionTimeout – указывает время в секундах, через которое зависшие соединения будут бесцеремонно уничтожатся. Крайняя мера для приложений с утечкой данных, чтобы не переполнить пул.
hibernate.c3p0.checkoutTimeout – указывает время в секундах, которое можно ожидать освободившегося подключения, если в пуле не осталось свободных.
С такими настройками в xml-файле ничего в джава-классах менять не нужно.
public class HibernateUtilSQLMain {
private static SessionFactory sessionFactory = buildSessionFactory();
private static SessionFactory buildSessionFactory() {
try {
return new Configuration().configure("sqlserverMain.cfg.xml").buildSessionFactory();
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
public static SessionFactory getSessionFactory() {
return sessionFactory;
}
}
Конфигурация в java-классе HibernateUtil
Если же вы устанавливаете соединение с базой в классе HibernateUtil, а не используете файл с настройками, то работы предстоит немного больше. Нужно создать объект типа ComboPooledDataSource, и туда уже впихивать все настройки соединения и пула.
public class HibernateUtil {
private static SessionFactory sessionFactory;
public static SessionFactory buildSessionFactory() {
try {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("net.sourceforge.jtds.jdbc.Driver");
dataSource.setJdbcUrl("***************");
dataSource.setUser("*********");
dataSource.setPassword("*********");
dataSource.setMinPoolSize(5);
dataSource.setMaxPoolSize(1000);
dataSource.setAcquireIncrement(5);
dataSource.setMaxStatements(150);
dataSource.setIdleConnectionTestPeriod(600);
dataSource.setMaxIdleTime(7200);
Properties hibernateProps = new Properties();
hibernateProps.put("hibernate.dialect", "org.hibernate.dialect.SQLServerDialect");
hibernateProps.put("hibernate.hbm2ddl.auto", "validate");
hibernateProps.put("hibernate.show_sql", "true");
Configuration configuration = new Configuration();
configuration.setProperties(hibernateProps);
//добавляем Entity-классы
configuration.addAnnotatedClass(*******************);
ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
.applySettings(configuration.getProperties())
.applySetting("hibernate.connection.datasource", dataSource)
.build();
sessionFactory = configuration.buildSessionFactory(serviceRegistry);
return sessionFactory;
} catch (PropertyVetoException e) {
throw new RuntimeException("Ошибка при инициализации c3p0", e);
}
}
public static SessionFactory getSessionFactory() {
return sessionFactory != null ? sessionFactory : buildSessionFactory();
}
}
В таблице ниже сопоставил методы из ComboPooledDataSource и настройки из файла xml.
c3p0.acquireIncrement hibernate.c3p0.acquire_increment
c3p0.idleConnectionTestPeriod hibernate.c3p0.idle_test_period
c3p0.initialPoolSize нет аналога
c3p0.maxIdleTime hibernate.c3p0.timeout
c3p0.maxPoolSize hibernate.c3p0.max_size
c3p0.maxStatements hibernate.c3p0.max_statements
c3p0.minPoolSize hibernate.c3p0.min_size
c3p0.testConnectionOnCheckout hibernate.c3p0.validate
Подробнее об этом проекте в документации
Проверка работы пула
После настройки проекта было бы неплохо проверить, работает ли пул. У меня tomcat выводит данные по используемому пулу. При запуске программы видно все настройки, и тут можно проверить, какие из них вы написали верно, а какие проигнорировались.
26-Jun-2025 22:14:44.223 INFO [http-nio-8080-exec-1] com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource. Initializing c3p0 pool... com.mchange.v2.c3p0.PoolBackedDataSource@d379ba64 [ connectionPoolDataSource -> com.mchange.v2.c3p0.WrapperConnectionPoolDataSource@418f3ab8 [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, maxConnectionAge -> 0, maxIdleTime -> 120, maxIdleTimeExcessConnections -> 60, maxPoolSize -> 1000, maxStatements -> 150, maxStatementsPerConnection -> 0, minPoolSize -> 5, <.....> ]
Далее в базе можно отследить, сколько на самом деле соединений используются.
SELECT COUNT(*) AS ActiveConnections
FROM sys.dm_exec_sessions
WHERE is_user_process = 1;
В следующей разбивки все джава-соединения будут в строке с jTDS:
SELECT program_name, COUNT(*) AS cnt
FROM sys.dm_exec_sessions
WHERE is_user_process = 1
GROUP BY program_name
ORDER BY cnt DESC;
У меня пул отказывался очищать соединения даже после таймаута, поэтому я ещё выполнил следующий запрос:
SELECT
s.session_id,
s.login_name,
s.host_name,
s.program_name,
s.status,
s.cpu_time,
s.memory_usage,
s.reads,
s.writes,
s.last_request_end_time,
DATEDIFF(MINUTE, s.last_request_end_time, GETDATE()) AS minutes_idle
FROM sys.dm_exec_sessions s
LEFT JOIN sys.dm_exec_requests r ON s.session_id = r.session_id
WHERE
s.is_user_process = 1
AND r.session_id IS NULL
AND s.status = 'sleeping'
AND s.program_name LIKE '%jTDS%'
AND DATEDIFF(MINUTE, s.last_request_end_time, GETDATE()) > 5
ORDER BY minutes_idle DESC;
Он показывает «спящие» соединения, которые не использовались дольше пяти минут (для теста я задал таймаут в две минуты). Таким образом, мы увидим, если пул работает неверно и не удаляет соединения, когда должен был бы.
Следующий анализ можно провести, добавив вывод stacktrace в xml-файле:
<property name="hibernate.c3p0.debugUnreturnedConnectionStackTraces">true</property>
Мне этот способ явно указал на метод, в котором я по какой-то случайности забыл завершить транзакцию и закрыть сессию.