Пул подключений PoolConnection
В общем случае пул объектов — это контейнер объектов, из которого можно получить один из них на время. Использование пула объектов есть шаблонный способ решения проблемы хранения и раздачи объектов во временное пользование.
Для разработки приложения можно использовать пулы различного типа объектов, например пул потоков Thread Pool. В данной статье рассматривается только пул подключений Connection Pool к БД.
Пул подключений необходим для WEB приложения, когда все запросы к базе данных стекаются в одно место к серверу, а создание нового подключения к БД требует значительных ресурсоов. Использование пула подключения оправдано более экономной стратегией — создание несколько подключений и слежение за их состояниям; по мере необходимости добавлять новые.
Существует несколько реализаций пулов подключений к БД. Можно, конечно, сделать и свою имплементацию. Опасность данной затеи заключается в том, что если сделать не очень аккуратно, то пул может «протекать» или объекты будут «тухнуть». Для серверной части приложения это особо неприятно — необходимо контролировать объём пула и состояние объектов.
На сегодняшний день существуют готовые решения. Самое главное научиться их правильно настраивать.
Пул подключений — apache commons dbcp, datasource
Популярный контейнер сервлетов Apache Tomcat предоставляет собственное решение для создания dbcp, основанное на библиотеке apache-commons-dbcp.
apache dbcp предполагает, что имеется некоторый набор («пул») соединений к базе данных. Когда новый пользователь запрашивает доступ к БД, ему выдаётся уже открытое соединение из этого пула. Если все открытые соединения уже заняты, создаётся новое. Как только пользователь освобождает одно из уже существующих соединений, оно становится доступно для других пользователей. Если соединение долго не используется, оно закрывается.
Чтобы реализовать поддержку пула подключений нужно пройти несколько этапов.
На первом этапе необходимо объявить ресурс (базу данных) в контексте приложения. Ресурс описывается следующим кодом (пример для MySQL) :
Контекст приложения представляет файл XML. Желательно хранить его в %document_root%/META-INF/context.xml, однако это не единственный вариант. Про контекст можно почитать на официальном сайте Tomcat’a: The Context Container.
Далее следует добавить ссылку на этот ресурс в дескрипторе приложения web.xml :
DB Connection jdbc/appname javax.sql.DataSource Container
После этого можно использовать пул подключений (connection pool java) в приложении. Для этого следует получить объект Connection в следующем коде :
InitialContext initContext= new InitialContext(); DataSource ds = (DataSource) initContext.lookup("java:comp/env/jdbc/appname"); Connection connection = ds.getConnection();
Для получения источника данных (DataSource) используется механизм JNDI (подробнее про него можно почитать здесь)
Описание пула подключений завершено, подключение в приложение получено, и теперь можно приступить к реализации логики работы с БД — connection.createStatement(). После выполнения запроса следует закрыть соединение (connection.close()). Однако в отличии от обычного соединения через драйвер JDBC, это соединение на самом деле не закроется: оно будет помечено в пуле как свободное, и его можно будет переиспользовать позже.
Перед возвратом соединения в пул все Statement и ResultSet, полученные с помощью этого соединения, автоматически закрываются в соответствии с API.
Connection Pool C3P0
Пул подключений C3P0 представлен файлом c3p0-0.9.1.2.jar (версия может отличаться). Аббревиатура расшифровывается следующим образом : Connection Pool 3.0 => СP30 => C3P0.
Принцип настройки JNDI ресурсов уже представлен выше. Необходимо определить глобальные ресурсы, а в WEB приложении указать ссылку, либо можно указать непосредственно в context.xml. Все зависит от того, где хранятся библиотеки и какая конфигурация сервера.
Пример настройки C3P0 с БД MySQL :
Теперь установить пул подключений не требует особого ума. Основная задача заключается в настройке его работы, чтобы он стабильно функционировал и выдерживал нагрузку. Дополнительно о значении каждого параметры можно прочитать в документации :
- maxPoolSize и minPoolSize — возможные максимальное и минимальное количество подключений в пуле
- preferredTestQuery — запрос который проверяет подключение к БД. Зависит от конкретной СУБД.
- acquireRetryAttempts — количество попыток подключения к СУБД, если она не доступна. Ставим 0, если хотим, чтобы попытки не заканчивались.
- testConnectionOnCheckout — «прожорлив», если можно, то лучше не использовать
Задача создания пула подключений к Oracle не тривиальна. Документация приведена здесь
Пул подключений на уровне java приложения, ComboPooledDataSource
Пул подключений C3P0 можно подключать не только на уровне контейнера приложений Tomcat, но и на уровне самого приложения. Для этого не требуется создавать никаких дополнительных внешних XML файлов, о которых было сказано выше.
Следующий код демонстрирует процесс создания и инициализации пула подключений :
import java.beans.PropertyVetoException; import com.mchange.v2.c3p0.ComboPooledDataSource; . ComboPooledDataSource cpds = new ComboPooledDataSource(); try < cpds.setDriverClass("com.mysql.jdbc.Driver" ); cpds.setJdbcUrl ("jdbc:mysql://localhost:3306/dbName"); cpds.setUser ("dbLogin" ); cpds.setPassword ("dbPassword"); Properties properties = new Properties(); properties.setProperty ("user" , "dbLogin" ); properties.setProperty ("password" , "dbPassword"); properties.setProperty ("useUnicode" , "true" ); properties.setProperty ("characterEncoding", "UTF8" ); cpds.setProperties(properties); // set options cpds.setMaxStatements (180); cpds.setMaxStatementsPerConnection(180); cpds.setMinPoolSize ( 50); cpds.setAcquireIncrement ( 10); cpds.setMaxPoolSize ( 60); cpds.setMaxIdleTime ( 30); >catch (PropertyVetoException e)
Следующий код демонстрирует как получить Connection из пула, и как его закрыть (вернуть в пул):
// Получить подключение из пула try < Connection connection = cpds.getConnection(); System.out.println ("closeConnection : idleConnections = " + cpds.getNumIdleConnections() + ", busyConnections = " + cpds.getNumBusyConnections()); >catch (SQLException e) < e.printStackTrace(); >// «Вернуть» (закрыть) подключение try < connection.close(); System.out.println ("closeConnection : idleConnections = " + cpds.getNumIdleConnections() + ", busyConnections = " + cpds.getNumBusyConnections()); >catch (SQLException e)
Примеры Java для демонстрации пулов подключений
Область применения: база данных Azure для MySQL — отдельный сервер
База данных Azure для MySQL — один сервер находится на пути прекращения поддержки. Настоятельно рекомендуется выполнить обновление до База данных Azure для MySQL — гибкий сервер. Дополнительные сведения о переходе на База данных Azure для MySQL —гибкий сервер см. в статье Что происходит с База данных Azure для MySQL отдельным сервером?
Приведенный ниже пример кода иллюстрирует использование пулов подключений в Java.
import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.HashSet; import java.util.Set; import java.util.Stack; public class MySQLConnectionPool < private String databaseUrl; private String userName; private String password; private int maxPoolSize = 10; private int connNum = 0; private static final String SQL_VERIFYCONN = "select 1"; StackfreePool = new Stack<>(); Set occupiedPool = new HashSet<>(); /** * Constructor * * @param databaseUrl * The connection url * @param userName * user name * @param password * password * @param maxSize * max size of the connection pool */ public MySQLConnectionPool(String databaseUrl, String userName, String password, int maxSize) < this.databaseUrl = databaseUrl; this.userName = userName; this.password = password; this.maxPoolSize = maxSize; >/** * Get an available connection * * @return An available connection * @throws SQLException * Fail to get an available connection */ public synchronized Connection getConnection() throws SQLException < Connection conn = null; if (isFull()) < throw new SQLException("The connection pool is full."); >conn = getConnectionFromPool(); // If there is no free connection, create a new one. if (conn == null) < conn = createNewConnectionForPool(); >// For Azure Database for MySQL, if there is no action on one connection for some // time, the connection is lost. By this, make sure the connection is // active. Otherwise reconnect it. conn = makeAvailable(conn); return conn; > /** * Return a connection to the pool * * @param conn * The connection * @throws SQLException * When the connection is returned already or it isn't gotten * from the pool. */ public synchronized void returnConnection(Connection conn) throws SQLException < if (conn == null) < throw new NullPointerException(); >if (!occupiedPool.remove(conn)) < throw new SQLException( "The connection is returned already or it isn't for this pool"); >freePool.push(conn); > /** * Verify if the connection is full. * * @return if the connection is full */ private synchronized boolean isFull() < return ((freePool.size() == 0) && (connNum >= maxPoolSize)); > /** * Create a connection for the pool * * @return the new created connection * @throws SQLException * When fail to create a new connection. */ private Connection createNewConnectionForPool() throws SQLException < Connection conn = createNewConnection(); connNum++; occupiedPool.add(conn); return conn; >/** * Crate a new connection * * @return the new created connection * @throws SQLException * When fail to create a new connection. */ private Connection createNewConnection() throws SQLException < Connection conn = null; conn = DriverManager.getConnection(databaseUrl, userName, password); return conn; >/** * Get a connection from the pool. If there is no free connection, return * null * * @return the connection. */ private Connection getConnectionFromPool() < Connection conn = null; if (freePool.size() >0) < conn = freePool.pop(); occupiedPool.add(conn); >return conn; > /** * Make sure the connection is available now. Otherwise, reconnect it. * * @param conn * The connection for verification. * @return the available connection. * @throws SQLException * Fail to get an available connection */ private Connection makeAvailable(Connection conn) throws SQLException < if (isConnectionAvailable(conn)) < return conn; >// If the connection is't available, reconnect it. occupiedPool.remove(conn); connNum--; conn.close(); conn = createNewConnection(); occupiedPool.add(conn); connNum++; return conn; > /** * By running a sql to verify if the connection is available * * @param conn * The connection for verification * @return if the connection is available for now. */ private boolean isConnectionAvailable(Connection conn) < try (Statement st = conn.createStatement()) < st.executeQuery(SQL_VERIFYCONN); return true; >catch (SQLException e) < return false; >> // Just an Example public static void main(String[] args) throws SQLException < Connection conn = null; MySQLConnectionPool pool = new MySQLConnectionPool( "jdbc:mysql://mysqlaasdevintic-sha.cloudapp.net:3306/", "", "", 2); try < conn = pool.getConnection(); try (Statement statement = conn.createStatement()) < ResultSet res = statement.executeQuery("show tables"); System.out.println("There are below tables:"); while (res.next()) < String tblName = res.getString(1); System.out.println(tblName); >> > finally < if (conn != null) < pool.returnConnection(conn); >> > >
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 за замечание).