
Продолжаю делиться полезной информацией, которая помогала мне в выполнении недавнего тестового задания: без использования фреймворков (вроде Spring) нужно было организовать работу с базой данных сразу у нескольких потоков.
В прошлой статье я описал, как подключаться к базе данных (на примере H2) и как потом с ней работать (писать запросы с параметрами и без), а теперь пришло время разобраться с ситуацией, когда одного подключения на всю программу может не хватить.
В таком случае на помощь приходят пулы (pool) – компоненты, которые хранят набор ресурсов. Клиент пула эти ресурсы может «брать в аренду» и использовать. Главное их потом вернуть. А так как в этой статье речь про подключения к базе данных (объекты типа Connection), то и пулы нужны соответствующие: Connection Pool.
Плюсы пулов в том, что они позволяют ограничивать использование ресурсов, а значит, экономят память, а также время на создание и установку подключений. Ну и они упрощают жизнь разработчику: вам не нужно самим беспокоиться о том, как же правильно организовать раздачу ресурсов, чтобы неожиданно не получилось, как с сорокой-белобокой («этому дала и этому дала а этому не дала»).
Добавление зависимости HikariCP в pom.xml
Конечно, есть разные пулы, но я остановился на HikariCP. В моём проекте был не особо принципиален выбор, и я просто выбрал один из самых популярных вариантов. Итак, чтобы добавить выбранный пул, я прописал в pom.xml зависимость:
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>4.0.3</version>
</dependency>
И если вы хотите увидеть логи Hikari, то добавьте SLF4J Simple Binding: выводит сообщения в System.err – уровень Info или выше.
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.0</version>
<scope>test</scope>
</dependency>
Настройка пула соединений
Пришло время заняться классом DBConnector, который в приложении отвечает за раздачу соединений. В прошлой статье метод getConnection() просто каждый раз создавал подключение к базе, но теперь поручим это объекту типа HikariDataSource:
private static HikariDataSource dataSource;
Вот такое поле я добавил в класс. И этот ресурс можно по-всякому настраивать перед использованием, за это отвечает класс HikariConfig:
private static HikariConfig config = new HikariConfig();
Поработал я с настройками в статическом блоке, но можно, конечно, и как-нибудь по-другому это организовать.
static {
config.setJdbcUrl("jdbc:h2:~/test");
config.setUsername("sa");
config.setPassword("");
config.setConnectionTimeout(50000);
config.setMaximumPoolSize(100);
dataSource = new HikariDataSource(config);
}
Основные настройки остаются всё теми же, что и в прошлой статье: это адрес базы (так как я юзаю h2 для простоты, то у меня и значение jdbc:h2:~/test), логин и пароль для подключения к базе (я использую стандартные данные). Но есть и более продвинутые настройки: мне по заданию нужно было обеспечить работу до ста потоков, поэтому и размер пула я обозначил в 100, таймаут на всякий случай увеличил до 50 секунд (что было пустой затеей)…
Настроек вообще хватает, на каждую из них есть по методу с соответствующим именем, но также есть метод addDataSourceProperty(), в который передаётся пара значений: строка с названием и значение. C этим методом настройка выглядела бы следующим образом:
config.addDataSourceProperty("connectionTimeout", 50000);
config.addDataSourceProperty("maximumPoolSize", 100);
А вот и список настроек:
- autoCommit
- connectionTimeout
- idleTimeout
- maxLifetime
- connectionTestQuery
- connectionInitSql
- validationTimeout
- maximumPoolSize
- poolName
- allowPoolSuspension
- readOnly
- transactionIsolation
- leakDetectionThreshold
Под конец мы инициализируем переменную dataSource – вызываем конструктор с параметром: new HikariDataSource(config).
Использование подключений с HikariCP
В методе getConnection больших перемен нет. Если раньше мы просили подключение у DriverManager, то теперь делаем это у dataSource. В остальном всё то же самое.
@SneakyThrows
public Connection getConnection() throws SQLException {
Class.forName(driver);
Connection connection = dataSource.getConnection();
connection.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
return connection;
}
Соответственно, и использование объекта остаётся тем же – в клиенте ничего менять не нужно, так как объект connection возвращается абсолютно такой же, как и раньше. Взглянем на пример с методом, отвечающим за удаление записи из базы данных:
public void delete(Long id) {
String sql = "DELETE FROM task WHERE task_id=?";
try (Connection connection = connector.getConnection();
PreparedStatement ps = connection.prepareStatement(sql)) {
ps.setLong(1, id);
ps.executeUpdate();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
Как и положено, этот метод и его родительский класс даже не подозревают, что теперь соединение им подсунул какой-то Хикари.
Подробнее о работе с объектами в базе, о сервисах и создании REST приложения без Spring напишу в следующей статье. Созданный с пулом DBConnector там пригодится.
1 Comment