GrabDuck

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. Чтобы реализовать поддержку пула соединений с своём приложении, нужно пройти через несколько этапов.

Во-первых, нужно объявить новый ресурс в контексте приложения. Ресурс (в нашем случае — БД) описывается следующим кодом:

<Resource name="jdbc/appname" auth="Container"
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:

<resource-ref>
DB Connection
<res-ref-name>jdbc/appname</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>

Теперь мы можем использовать этот ресурс в нашем приложении. Для того чтобы получить объект 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 за замечание).