0,0
рейтинг
10 августа 2010 в 00:29

Разработка → Database Connection Pool

JAVA*
Добрый день, хабралюди!
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 за замечание).
Александр Коротких @akril
карма
16,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (38)

  • 0
    Понятно что решение обобщённое и подходит для любых ресурсов, а не только соединений с БД, но неужели нельзя в строке подключения указать что-то вроде «Pooling=yes»?
    • +1
      У драйвера нельзя, он низкоуровневый и с такими категориями не работает.
      • –1
        Печально как-то у вас это…
        • +1
          У нас все хорошо :]
  • +10
    Обожаю статьи типа «Две недели назад я начал заниматься… и вот я решил написать статью...»
    • +1
      Мне тоже понравились «письменные мысли путем написания...». :-)
      • 0
        Спасибо, поправил :)
  • –18
    Мне всегда нравилась JAVA. Пачка фаилов, для того чтобы только подключится к БД
    • +6
      Наверное правильнее сказать что всего 2 файла и получаем пул соединений. :)
    • 0
      Вы не правы. Захардкодить подключение можно одной строчкой, а пачкой файлов можно добиться гибкости.
    • +1
      Connection conn = DriverManager.getConnection(«jdbc:oracle:thin:127.0.0.1:1521», «system», «sys»);

      Реквестирую пачку файлов. Кроме JDBC-драйвера естественно.
      • +1
        ну, вообще-то именно это и называется «захардкодить одной строчкой» :)
  • 0
    Замечу, что у вас возникнет проблема, если maxWait будет больше чем connection_timeout базы данных. Лучше посмотреть эту ссылку commons.apache.org/dbcp/configuration.html и дописать конфиг
    • +1
      Хотя нет, не так. Не от maxWait это зависит. У нас была проблема с тем, что соединения отвалившиеся по таймауту оставались в пуле
      • 0
        У нас на проекте тоже были проблемы… Мы их «решали» давольно легким способом — перед выполнением запроса делали фейковый запрос к БД. Если он срабатывал, то все хорошо, иначе пересоздавали, хотя система считала, что все нормально… ведь у нее же было открытое потключение :)
        • 0
          Настройка validationQuery почти этим и занимается. Думаю удобней один раз в конфиге прописать и забыть о проблеме вобще)
  • –3
    Две недели назад я начал писать на Java и теперь, дорогие хабралюди, я расскажу вам о чудном способе вывода «Hello, world»… [*сарказм*]
    • +4
      Так это про кого.
  • НЛО прилетело и опубликовало эту надпись здесь
  • –5
    Сдаётся мне, что главная причина, по которой используется пул соединений — это возможность одновременной работы нескольких потоков (threads) приложения. В противном случае потоку, который открывает транзакцию, пришлось бы ещё и тормозить остальные потоки. Кстати, странно, что используется работа с БД напрямую. Есть же JPA, есть же Hibernate. Насколько я знаю, для них пул можно настроить специфическим механизмом.
    • 0
      В посте правильно написано, главная причина — исключить затраты на открытие/закрытие соединения.
      • 0
        Ну не совсем правильно выразился. В посте написано, что можно было бы использовать singleton, но лучше его не использовать, т.к. это пагубно скажется на производительности. А как раз поднимать только одно соединение на всё приложение чревато не столько снижением производительности, сколько невозможности одновременной обработки нескольких запросов.
    • 0
      Не поверите, «для них пул можно настроить специфическим механизмом» внутри hibernate пула лежит примерный такой же механизм описанный автором.
      • 0
        Да, работают они все так же. Но внутри Hibernate использует пул по умолчанию. Кроме того, можно (и нужно в продуктиве) настраивать пул специально для Hibernate. В этом случае настройка пула выглядит несколько иначе, чем описано в статье.
  • 0
    Я бы добавил к статье следующее замечание.
    Перед возвратом соединения в пул все Statement'ы и ResultSet'ы, полученные с помощью этого соединения, автоматически закрываются в соответствии с API.
    • +3
      Ну да, так оно и есть. Главное не забывать, что соединение необходимо вовремя закрывать, иначе можем запросто переполнить пул соединений и очередь. Statement и ResultSet тоже лучше закрывать, если они больше не нужны. У меня эта привычка осталась с «древних» времен Turbo C/Pascal когда самому приходилось следить за ресурсами. С моей точки зрения привычка «убирать за собой» при работе приложения на сервере приложений, это хорошая привычка. Каждый объект, будь то EJB, Servlet и пр. имеет свой жизненный цикл и тут уже нельзя полагаться на то, что конкретный экземпляр умрет сразу после обращения и GC унесет весь мусор связанный с ним.
      • 0
        Лучше сделать аннотацию и высвобождать ресурсы в interceptor-е. А само соединение получать через dependency injection. И не нужно будет помнить о дуратских close в finally
  • +1
    А еще удобнее этим через Spring пользоваться — он сам «вставит» Connection Pool в ваши классы
  • +1
    Похоже вся соль этой статьи выплывает в комментариях)
  • +2
    еще опции:
    autoCommit=«false»
    — тут я думаю понятно

    removeAbandoned=«true» removeAbandonedTimeout=«30» logAbandoned=«true»
    — автоматически возвращает в пул соединения, которые не были закрыты а ссылка на коннекшн потеряна
    (например прогер «забыл» сделать try-catch-finaly и в финализации закрыть стейтмент и соединение, а произошло исключение…

    • +1
      это, собственно, одна из главных опций, а автор про неё почему-то и не упомянул…
  • 0
    В конце, как обычно, следует закрыть соединение (conn.close()), однако в отличии от обычного соединения через драйвер JDBC, это соединение на самом деле не закроется: оно будет помечено в пуле как свободное, и его можно будет переиспользовать позже.

    Каким образом об этом узнает пул? Как осуществляется обратная связь?
    • 0
      По ссылке на мануал написано, что идет не закрытие соединения, а его возврат в пул:

      pool.returnConnection(this);
  • 0
    Этот же пул соединений используется в tomcat, в таком случае записывается в контекст приложения.
    • 0
      В таком случае Resource записывается в контекст приложения
  • 0
    Основываясь на практическом опыте, могу сказать, что стоит весьма трепетно отнестись к производительности самого пула.

    dbcp отличается низкой производительностью. В случае большого количества параллельных обращений к пулу он становится существенным затыком во всей системе. Проблема заключается в неэффективной реализации контейнера объектов (собственно говоря commons-pool), которая использует java synchronized.

    Есть несколько альтернатив dbcp: c3p0, proxol, nanopool, miniconanectionpool, Oracle jdbc connection pool и др. После сравнительных тестов производительности мы выбрали Tomcat JDBC Connection Pool. Он использует java concurrent collections, прост в использовании и расширении, а конфигурация совместима c dbcp. Пропускная способность нашего приложения после этой замены варосла приблизительно на 30%.
    • 0
      Странно. Всегда думал, что затыком является не пул, а сам движок базы данных.
  • 0
    Поправил ошибки.
    <resource-ref>
    <description>DB Connection</description>
    <res-ref-name>jdbc/appname</res-ref-name>
    <res-type>javax.sql.DataSource</res-type>
    <res-auth>Container</res-auth>
    </resource-ref>
    


    InitialContext initContext= new InitialContext();
    DataSource ds = (DataSource) initContext.lookup("java:comp/env/jdbc/appname");
    Connection conn = ds.getConnection();
    


    У меня была ошибка: сannot create JDBC driver of class '' for connect URL 'null'
    Исправил context.xml так:
    <?xml version="1.0" encoding="UTF-8"?>
    <Context>   
       <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"/>
    </Context>
    

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.