7.1 Пул соединений (Connection pool)
Сегодня мы научимся работать с базой данных еще более профессионально. И поговорим мы сейчас о пуле потоков, или connection pool по-английски.
Коннект к базе данных требует затрат определенного времени. Особенно если база данных удаленная. Если под каждый запрос делать подключение к базе, то отклик нашего приложения будет невероятно низким по скорости. Не говоря уже о ресурсах, которые оно потребит.
В качестве решения таких проблем было предложено хранить соединения с базой в некотором множестве, которое называют пулом потоков.
Когда мы запрашиваем новое соединение, то connection pool его создаёт, а при закрытии он не закрывает его, а сохраняет в connection pool. И если мы снова запросим соединение у connection pool, то он даст нам одно из старых вместо того, чтобы создавать новое.
По сути приложение работает через специальный драйвер, который является оберткой для обычного JDBC-драйвера и который имеет дополнительный функционал по работе с пулом. Эту ситуацию можно представить такой картинкой:
Настройки для пула соединений программист может прописать вручную: количество активных соединений, время ожидания и т. д.
Для особенно экстремальных ситуаций можно написать свой connection pool: класс, который буде иметь список соединений. У него будет переопределена функция close, которая будет возвращать соединение обратно в список, и будет много других плюшек вроде таймера открытого соединения. Когда нет конекшина долгое время, соединение закрывается.
7.2* Интерфейс DataSource и ConnectionPoolDataSource
Пул потоков обычно работает в паре с объектом DataSource. Этот объект можно рассматривать как некую абстракцию удаленной базы данных. Само название Data Source можно дословно перевести как Источник Данных. Что как бы намекает.
DataSource-объекты используются для получения физического соединения с базой данных и являются альтернативой DriverManager. При этом нет необходимости регистрировать JDBC-драйвер. Нужно только выставить соответствующие параметры для установки соединения и выполнить метод getConnection().
При создании объекта типа DataSource локально (прямо в приложении) параметры соединения задаются соответствующими методами, предусмотренными поставщиком JDBC-драйвера. Эти методы не определены интерфейсом DataSource, так как параметры для соединения с СУБД разных производителей могут отличаться как по типу, так и по количеству (например, не для всех СУБД необходимо указывать тип драйвера или сетевой протокол).
Так, при работе с СУБД Oracle для использования соответствующих set и get методов необходимо получить объект типа OracleDataSource, который является экземпляром одноименного класса, реализующего интерфейс DataSource. Поэтому такой способ создания объектов типа DataSource делает твой код менее переносимым и зависимым от конкретной реализации драйвера.
Ниже приведен пример кода, иллюстрирующий локальное использование объектов типа DataSource.
import java.sql.*;
import javax.sql.*;
import oracle.jdbc.driver.*;
import oracle.jdbc.pool.*;
public class DataSource {
public static void main(String[] args) {
try {
OracleDataSource ods = new OracleDataSource();
ods.setUser("root");
ods.setPassword("secret");
ods.setDriverType("thin");
ods.setDatabaseName("test");
ods.setServerName("localhost");
ods.setPortNumber(1521);
Connection connection = ods.getConnection();
System.out.println("Connection successful!!!");
} catch (SQLException se) {
se.printStackTrace();
}
System.out.println("Goodbye!");
}
}
7.3* Интерфейс JNDI
Полностью возможности объектов типа DataSource проявляются в сочетании с использованием интерфейса JNDI. JNDI — это специальный сервис (что-то вроде каталога) для больших серверных приложений, который позволяет одним сервисам устанавливать соединение с другими.
Использование службы имен и каталогов позволяет хранить заранее созданные системным администратором объекты типа DataSource с предустановленными параметрами соединения. Ниже приведены некоторые стандартные имена свойств (параметров), разработанные компанией Sun:
Имя свойства | Тип данных Java | Назначение |
---|---|---|
databaseName | String | Имя базы данных |
serverName | String | Имя сервера |
user | String | Имя пользователя (ID) |
password | String | Пароль пользователя |
portNumber | Int | Номер порта сервера СУБД |
Комбинирование интерфейсов DataSource и JNDI играет ключевую роль в разработке многоуровневых корпоративных приложений, основанных на компонентной технологии J2EE.
В случае использования комбинации интерфейсов DataSource и JNDI в программном коде приложения необходимо только затребовать объект типа DataSource у службы имен и каталогов. При этом детали соединения и зависимый от конкретной реализации драйвера программный код оказывается скрытым от приложения в хранящемся в службе имен и каталогов объекте.
Таким образом совместное использование объектов типа DataSource и JNDI предполагает наличие двух выполняемых независимо друг от друга шагов:
- Необходимо сохранить именованный объект типа DataSource в службе имен и каталогов, используя метод
Context.bind()
пакетаjavax.naming
. - Затребовать в приложении объект типа DataSource у службы имен и каталогов, используя метод
Context.lookup()
. После чего можно использовать его методDataSource.getConnection()
для получения соединения с базой данных.
Ниже приведен пример совместного использования интерфейса JNDI и объекта типа OracleDataSource.
// Создаем ключевой JNDI объект
Hashtable env = new Hashtable();
env.put (Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.fscontext.RefFSContextFactory");
env.put (Context.PROVIDER_URL, "file:JNDI");
Context ctx = new InitialContext(env);
// Получаем объект DataSource по его имени
DataSource ods = (DataSource) ctx.lookup("myDatabase");
// Получаем Connection из объекта DataSource
Connection connection = ods.getConnection();
System.out.println("Connection successful!!!");
Разбирать работу JNDI мы не будем. Это выходит за рамки данного курса. Просто хочу, чтобы ты знал, что такой подход очень часто используется в крупных приложениях. Не теряйся, если увидишь в будущем такой код.
Неважно, как это работает. Теб нужно просто знать, что с помощью JNDI сервиса ты можешь получить proxy-объект любого сервиса, зарегистрированного в каталоге сервисов. Именно таким образом получают объект DataSource и с его помощью работают с базой данных.
7.4 Примеры Connection Pool’ов
Давай лучше вернемся к тому, с чего начали — к пулам соединений (connections pool).
Java-программисты в своих программах очень часто используют библиотеки, которые содержат в себе различные реализации connection pool’ов. Среди них есть три самых популярных:
- Apache Commons DBCP
- C3PO
- HikariCP
Первым создали хороший connection pool программисты из проекта Apache, он так же используется внутри веб-сервера Tomcat. Пример работы с ним:
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(){ }
}
Второй очень популярный пул — это C3PO. Странные названия, согласен. Название C3PO — это отсылка к имени робота c3po из Звездных Войн. А также CP — это сокращение от Connection Pool.
public class C3p0DataSource {
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 C3p0DataSource(){}
}
С документацией по нему можно ознакомиться на официальной странице.
И наконец самый популярный Connection Pool в наше время — это HikariCP:
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(){}
}
Вот его официальная страничка в GitHub.
И хорошая статья по конфигурированию.