Простое руководство по пулу соединений в Java
Пул соединений является хорошо известным шаблоном доступа к данным, основная цель которого состоит в том, чтобы уменьшить накладные расходы, связанные с выполнением соединений с базой данных и операциями чтения / записи с базой данных.
Вкратце,a connection pool is, at the most basic level, a database connection cache implementation, который можно настроить в соответствии с конкретными требованиями.
В этом руководстве мы сделаем краткий обзор нескольких популярных фреймворков для пула соединений и узнаем, как реализовать с нуля собственный пул соединений.
2. Почему пул соединений?
Вопрос риторический, конечно.
Если мы проанализируем последовательность шагов, включенных в типичный жизненный цикл подключения к базе данных, мы поймем, почему:
- Открытие соединения с базой данных с помощью драйвера базы данных
- ОткрытиеTCP socket для чтения / записи данных
- Чтение / запись данных через сокет
- Закрытие соединения
- Закрытие розетки
Становится очевидным, чтоdatabase connections are fairly expensive operations, и как таковой, следует свести к минимуму во всех возможных случаях использования (в крайних случаях просто избегать).
Здесь в игру вступают реализации пула соединений.
Просто внедрив контейнер соединений с базой данных, который позволяет нам повторно использовать несколько существующих соединений, мы можем эффективно сэкономить затраты на выполнение огромного количества дорогостоящих поездок по базам данных, тем самым повышая общую производительность наших приложений на основе баз данных.
3. Фреймворки пула соединений JDBC
С прагматической точки зрения, реализация пула соединений с нуля просто бессмысленна, учитывая количество доступных для предприятия инфраструктур пула соединений.
Из дидактического, что и является целью данной статьи, это не так.
Тем не менее, прежде чем мы узнаем, как реализовать базовый пул соединений, давайте сначала продемонстрируем несколько популярных фреймворков для пулов соединений.
3.1. Apache Commons DBCP
Давайте начнем этот краткий обзор сApache Commons DBCP Component, полнофункциональной платформы JDBC для пула соединений:
public class DBCPDataSource < private static BasicDataSource ds = new BasicDataSource(); static < ds.setUrl("jdbc:h2:mem:test"); ds.setUsername("user"); ds.setPassword("password"); ds.setMinIdle(5); ds.setMaxIdle(10); ds.setMaxOpenPreparedStatements(100); >public static Connection getConnection() throws SQLException < return ds.getConnection(); >private DBCPDataSource() < >>
В этом случае мы использовали класс-оболочку со статическим блоком, чтобы легко настроить свойства DBCP.
Вот как получить объединенное соединение с классомDBCPDataSource:
Connection con = DBCPDataSource.getConnection();
3.2. HikariCP
Двигаясь дальше, давайте посмотрим наHikariCP, молниеносную структуру пула соединений JDBC, созданнуюBrett Wooldridge (для получения полной информации о том, как настроить и получить максимальную отдачу от HikariCP, проверьтеthis article ):
public class HikariCPDataSource < private static HikariConfig config = new HikariConfig(); private static HikariDataSource ds; static < config.setJdbcUrl("jdbc:h2:mem:test"); config.setUsername("user"); config.setPassword("password"); config.addDataSourceProperty("cachePrepStmts", "true"); config.addDataSourceProperty("prepStmtCacheSize", "250"); config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048"); ds = new HikariDataSource(config); >public static Connection getConnection() throws SQLException < return ds.getConnection(); >private HikariCPDataSource()<> >
Аналогичным образом, вот как получить объединенное соединение с классомHikariCPDataSource:
Connection con = HikariCPDataSource.getConnection();
3.3. C3PO
Последний в этом обзоре -C3PO, мощный фреймворк JDBC4 для соединений и пулов операторов, разработанный Стивом Уолдманом:
public class C3poDataSource < private static ComboPooledDataSource cpds = new ComboPooledDataSource(); static < try < cpds.setDriverClass("org.h2.Driver"); cpds.setJdbcUrl("jdbc:h2:mem:test"); cpds.setUser("user"); cpds.setPassword("password"); >catch (PropertyVetoException e) < // handle the exception >> public static Connection getConnection() throws SQLException < return cpds.getConnection(); >private C3poDataSource()<> >
Как и ожидалось, получение объединенного соединения с классомC3poDataSource аналогично предыдущим примерам:
Connection con = C3poDataSource.getConnection();
4. Простая реализация
Чтобы лучше понять основную логику пула соединений, давайте создадим простую реализацию.
Давайте начнем со слабосвязанного дизайна, основанного всего на одном интерфейсе:
public interface ConnectionPool
ИнтерфейсConnectionPool определяет общедоступный API базового пула соединений.
Теперь давайте создадим реализацию, которая предоставляет некоторые базовые функции, включая получение и освобождение объединенного соединения:
public class BasicConnectionPool implements ConnectionPool < private String url; private String user; private String password; private ListconnectionPool; private List usedConnections = new ArrayList<>(); private static int INITIAL_POOL_SIZE = 10; public static BasicConnectionPool create( String url, String user, String password) throws SQLException < Listpool = new ArrayList<>(INITIAL_POOL_SIZE); for (int i = 0; i < INITIAL_POOL_SIZE; i++) < pool.add(createConnection(url, user, password)); >return new BasicConnectionPool(url, user, password, pool); > // standard constructors @Override public Connection getConnection() < Connection connection = connectionPool .remove(connectionPool.size() - 1); usedConnections.add(connection); return connection; >@Override public boolean releaseConnection(Connection connection) < connectionPool.add(connection); return usedConnections.remove(connection); >private static Connection createConnection( String url, String user, String password) throws SQLException < return DriverManager.getConnection(url, user, password); >public int getSize() < return connectionPool.size() + usedConnections.size(); >// standard getters >
Хотя это довольно наивно, классBasicConnectionPool обеспечивает минимальную функциональность, которую мы ожидаем от типичной реализации пула соединений.
Вкратце, класс инициализирует пул соединений на основеArrayList, в котором хранится 10 соединений, которые можно легко использовать повторно.
It’s possible to create JDBC connections with the DriverManager class and with Datasource implementations.
Поскольку гораздо лучше сохранить независимость создания базы данных соединений от базы данных, мы использовали первое в рамках статического метода фабрикиcreate().
В этом случае мы поместили метод вBasicConnectionPool, потому что это единственная реализация интерфейса.
В более сложном дизайне с несколькими реализациямиConnectionPool было бы предпочтительнее разместить его в интерфейсе, чтобы получить более гибкий дизайн и больший уровень согласованности.
Здесь наиболее важно подчеркнуть, что после создания пулаconnections are fetched from the pool, so there’s no need to create new ones.
Кроме того,when a connection is released, it’s actually returned back to the pool, so other clients can reuse it.
Никакого дальнейшего взаимодействия с базовой базой данных, например явного вызова методаConnection’s close(), не происходит.
5. Использование классаBasicConnectionPool
Как и ожидалось, использовать наш классBasicConnectionPool просто.
Давайте создадим простой модульный тест и получим объединенное соединение in-memoryH2:
@Test public whenCalledgetConnection_thenCorrect()
6. Дальнейшие улучшения и рефакторинг
Конечно, есть много возможностей для настройки / расширения текущей функциональности нашей реализации пула соединений.
Например, мы могли бы провести рефакторинг методаgetConnection() и добавить поддержку максимального размера пула. Если все доступные соединения заняты, а текущий размер пула меньше настроенного максимума, метод создаст новое соединение:
@Override public Connection getConnection() throws SQLException < if (connectionPool.isEmpty()) < if (usedConnections.size() < MAX_POOL_SIZE) < connectionPool.add(createConnection(url, user, password)); >else < throw new RuntimeException( "Maximum pool size reached, no available connections!"); >> Connection connection = connectionPool .remove(connectionPool.size() - 1); usedConnections.add(connection); return connection; >
Обратите внимание, что метод теперь выдаетSQLException, то есть нам также нужно обновить подпись интерфейса.
Или мы можем добавить метод для корректного завершения работы нашего экземпляра пула соединений:
public void shutdown() throws SQLException < usedConnections.forEach(this::releaseConnection); for (Connection c : connectionPool) < c.close(); >connectionPool.clear(); >
В готовых к реализации реализациях пул соединений должен предоставлять ряд дополнительных функций, таких как возможность отслеживания соединений, которые используются в настоящее время, поддержка подготовленного пула операторов и т. Д.
Чтобы упростить задачу, мы опустим, как реализовать эти дополнительные функции, и для ясности оставим реализацию небезопасной для потоков.
7. Заключение
В этой статье мы подробно рассмотрели, что такое пул соединений, и узнали, как реализовать собственную реализацию пула соединений.
Конечно, нам не нужно начинать с нуля каждый раз, когда мы хотим добавить в наши приложения полнофункциональный уровень пула соединений.
Вот почему мы сначала сделали простой обзор, показывающий некоторые из самых популярных фреймворков пула соединений, чтобы мы могли иметь четкое представление о том, как с ними работать, и выбрать ту, которая лучше всего соответствует нашим требованиям.
Как обычно, доступны все примеры кода, показанные в этой статьеover on GitHub.
Database Connection Pool
Добрый день, хабралюди!
2 недели назад я начал работать juior java разработчиком, и, соответственно, получать много нового для себя опыта. Сегодня я решил совместить приятное с полезным и начать этот опыт оформлять в письменные мысли — в виде статей о тех технологиях, принципах и приёмах, с которыми я столкнулся на своём джуниорском пути. Нижеследующая статья — первая среди подобных, и выкладывая её здесь, я хочу, во-первых, понять, нужны ли хабрасообществу подобные вещи — рассказы не умудрённых опытом и сотнями проектов старожилов, а небольшие попытки поделится опытом от джуниора джуниору, — а во-вторых, как обычно, услышать замечания, исправления и критику.
Спасибо за внимание.
Подавляющее большинство современных веб-приложений использует базы данных для хранения информации. Приложение может обмениваться информацией с БД, используя соединение (database connection). Если создавать при каждом обращении к БД, получается проигрыш во времени: выполнение транзакции может занять несколько милисекунд, в то время как на создание соединения может уйти до нескольких секунд. С другой стороны, можно создать одно-единственное соединение (например, используя шаблон «Singleton») и обращаться к базе данных только через него. Но это решение чревато проблемами, в случае высокой нагрузки: если одновременно сто пользователей попытается получить доступ к базе данных используя одно соединение, образуется очередь, что также пагубно сказывается на производительности приложения.
Database Connection Pool (dbcp) — это способ решения изложенной выше проблемы. Он подразумевает, что в нашем распоряжении имеется некоторый набор («пул») соединений к базе данных. Когда новый пользователь запрашивает доступ к БД, ему выдаётся уже открытое соединение из этого пула. Если все открытые соединения уже заняты, создаётся новое. Как только пользователь освобождает одно из уже существующих соединений, оно становится доступно для других пользователей. Если соединение долго не используется, оно закрывается.
Пример реализации простейшего пула соединений можно найти на официальном сайте java.sun.com: Connection Pooling
Поскольку подобный подход наиболее полезен в случае enterprise- и web-приложений, вполне логично, что такой популярный контейнер сервлетов, как Apache Tomcat предоставляет собственное решение для создания dbcp. Решение это основанно на библиотеке apache-commons-dbcp. Чтобы реализовать поддержку пула соединений с своём приложении, нужно пройти через несколько этапов.
Во-первых, нужно объявить новый ресурс в контексте приложения. Ресурс (в нашем случае — БД) описывается следующим кодом:
type=»javax.sql.DataSource» maxActive=»100″
maxIdle=»30″ maxWait=»10000″
username=»username»
password=»password»
driverClassName=»jdbc.driver.name»
url=»jdbc:protocol://hostname:port/dbname»/>
Контекст приложения описывается XML-файлом. Я считаю правильным хранить его в %document_root%/META-INF/context.xml, однако это не единственный вариант. Подробней про контекст можно почитать на сайте Tomcat’a: The Context Container.
Теперь нужно добавить ссылку на этот ресурс в web.xml:
Теперь мы можем использовать этот ресурс в нашем приложении. Для того чтобы получить объект Connection для выполнения sql-кода, исопльзуется следующий код:
InitialContext initContext= new InitialContext();
DataSource ds = (DataSource) initContext.lookup(«java:comp/env/jdbc/dbconnect»);
Connection conn = ds.getConnection();
Для получения источника данных (data source) используется механизм JNDI (подробнее про него можно почитать здесь)
Всё! Теперь вы можете выполнить conn.createStatement() и реализовать логику работы с БД. В конце, как обычно, следует закрыть соединение (conn.close()), однако в отличии от обычного соединения через драйвер JDBC, это соединение на самом деле не закроется: оно будет помечено в пуле как свободное, и его можно будет переиспользовать позже. Перед возвратом соединения в пул все Statement’ы и ResultSet’ы, полученные с помощью этого соединения, автоматически закрываются в соответствии с API (спасибо Colwin за замечание).