Используем пул соединений к Базе Данных: Hikari Connection Pool + JDBC

Продолжаю делиться полезной информацией, которая помогала мне в выполнении недавнего тестового задания: без использования фреймворков (вроде 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

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход /  Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход /  Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход /  Изменить )

Connecting to %s