Рубрики
Без рубрики

Простое руководство по объединения соединений на Java

Краткий обзор нескольких популярных решений объединения соединений, а также быстрое погружение в реализацию пользовательского пула соединений

Автор оригинала: baeldung.

1. Обзор

Объединение соединений — это хорошо известная модель доступа к данным, основной целью которой является сокращение накладных расходов, связанных с выполнением подключений к базам данных и операциями с чтением/записи баз данных.

В двух словах, пул соединения на самом базовом уровне является реализацией кэша подключения базы данных , которые могут быть настроены в соответствии с конкретными требованиями.

В этом учебнике мы сделаем быстрый обзор нескольких популярных рамок объединения соединений, и мы узнаем, как реализовать с нуля наш собственный пул подключения.

2. Почему соединение бассейн?

Вопрос, конечно, риторический.

Если мы проанализируем последовательность шагов, участвующих в типичном жизненном цикле соединения базы данных, мы поймем, почему:

  1. Открытие подключения к базе данных с помощью драйвера базы данных
  2. Открытие TCP розетка для чтения/записи данных
  3. Чтение/написание данных через розетку
  4. Закрытие соединения
  5. Закрытие розетки

Становится очевидным, что соединения баз данных являются довольно дорогостоящими операциями , и как таковой, должны быть сведены к минимуму в каждом возможном случае использования (в случаях края, просто избежать).

Здесь в игру вступают реализации объединения соединений.

Просто реализуя контейнер для подключения к базе данных, который позволяет нам повторно использовать ряд существующих соединений, мы можем эффективно сэкономить затраты на выполнение огромного количества дорогостоящих поездок по базе данных, тем самым повышая общую производительность наших приложений, управляемых базой данных.

3. Рамки объединения соединений JDBC

С прагматической точки зрения, реализация пула соединений с нуля просто бессмысленна, учитывая количество доступных там «готовых к предприятию» рамок объединения соединений.

Из дидактической, которая является целью этой статьи, это не так.

Несмотря на это, прежде чем мы узнаем, как реализовать базовый пул соединений, давайте сначала продемонстрируем несколько популярных рамок объединения соединений.

3.1. Apache Commons DBCP

Начнем эту быструю облаву с Компонентная группа Apache Commons dbCP , полное подключение объединения JDBC рамки:

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(){ }
}

В этом случае мы использовали класс обертки со статическим блоком для легкой настройки свойств DBCP.

Вот как получить объединение связи с DBCPDataИсточник класс:

Connection con = DBCPDataSource.getConnection();

3.2. ХикариКП

Двигаясь дальше, давайте посмотрим на ХикариКП , молниеносная структура объединения соединения JDBC, созданная Бретт Вулдридж (Для получения подробной информации о том, как настроить и получить большую часть из 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(){}
}

Аналогичным образом, вот как получить объединение связи с ХикариКПДатаИсточник класс:

Connection con = HikariCPDataSource.getConnection();

3.3. C3PO

Последним в этом обзоре является C3PO , мощная система объединения связей и заявлений JDBC4, разработанная Стивом Уолдманом:

public class C3poDataSource {

    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 C3poDataSource(){}
}

Как и ожидалось, получить объединение связи с C3poDataИсточник класс похож на предыдущие примеры:

Connection con = C3poDataSource.getConnection();

4. Простая реализация

Чтобы лучше понять лежащую в основе логику объединения соединений, давайте создадим простую реализацию.

Начнем с слабо связанных дизайн, основанный только на одном интерфейсе:

public interface ConnectionPool {
    Connection getConnection();
    boolean releaseConnection(Connection connection);
    String getUrl();
    String getUser();
    String getPassword();
}

СвязьПул интерфейс определяет общедоступный API базового пула соединений.

Теперь давайте создадим реализацию, которая обеспечивает некоторые основные функциональные возможности, включая получение и выпуск объединения соединения:

public class BasicConnectionPool 
  implements ConnectionPool {

    private String url;
    private String user;
    private String password;
    private List connectionPool;
    private List usedConnections = new ArrayList<>();
    private static int INITIAL_POOL_SIZE = 10;
    
    public static BasicConnectionPool create(
      String url, String user, 
      String password) throws SQLException {
 
        List pool = new ArrayList<>(INITIAL_POOL_SIZE);
        for (int i = 0; i < INITIAL_POOL_SIZE; i++) {
            pool.add(createConnection(url, user, password));
        }
        return new BasicConnectionPool(url, user, password, pool);
    }
    
    // standard constructors
    
    @Override
    public Connection getConnection() {
        Connection connection = connectionPool
          .remove(connectionPool.size() - 1);
        usedConnections.add(connection);
        return connection;
    }
    
    @Override
    public boolean releaseConnection(Connection connection) {
        connectionPool.add(connection);
        return usedConnections.remove(connection);
    }
    
    private static Connection createConnection(
      String url, String user, String password) 
      throws SQLException {
        return DriverManager.getConnection(url, user, password);
    }
    
    public int getSize() {
        return connectionPool.size() + usedConnections.size();
    }

    // standard getters
}

Хотя довольно наивно, BasicConnectionPool класс обеспечивает минимальную функциональность, которую мы ожидаем от типичной реализации объединения соединений.

В двух словах класс инициализирует пул соединения на основе ArrayList что хранит 10 соединений, которые могут быть легко повторно использованы.

Можно создать соединения JDBC с ВодительManager класс и с Ресурс данных реализации .

Поскольку гораздо лучше сохранить создание агностики базы данных соединений, мы использовали первое, в рамках создать () статический метод фабрики.

В этом случае мы поместили метод в BasicConnectionPool , потому что это единственная реализация интерфейса.

В более сложном дизайне, с несколькими СвязьПул реализации, было бы предпочтительнее разместить его в интерфейсе, поэтому получить более гибкий дизайн и более высокий уровень сплоченности.

Наиболее актуальным моментом, чтобы подчеркнуть здесь является то, что как только бассейн создан, соединения извлечены из пула, так что нет необходимости создавать новые .

Кроме того, когда соединение выпущено, оно фактически возвращается обратно в пул, так что другие клиенты могут повторно использовать его .

Дальнейшее взаимодействие с базовой базой данных не происходит, например, явный вызов Соединение близко () метод.

5. Использование класса BasicConnectionPool

Как и ожидалось, используя наши BasicConnectionPool класс прост.

Давайте создадим простой унитарный тест и объединим в памяти H2 связь:

@Test
public whenCalledgetConnection_thenCorrect() {
    ConnectionPool connectionPool = BasicConnectionPool
      .create("jdbc:h2:mem:test", "user", "password");
 
    assertTrue(connectionPool.getConnection().isValid(1));
}

6. Дальнейшие усовершенствования и рефакторинг

Конечно, есть много возможностей для настройки/расширения текущей функциональности нашего соединения объединения реализации.

Например, мы могли бы рефакторинг getConnection () метод, и добавить поддержку для максимального размера пула. Если все доступные соединения взяты, а текущий размер пула меньше настроенного максимума, метод создаст новое соединение.

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

@Override
public Connection getConnection() throws SQLException {
    if (connectionPool.isEmpty()) {
        if (usedConnections.size() < MAX_POOL_SIZE) {
            connectionPool.add(createConnection(url, user, password));
        } else {
            throw new RuntimeException(
              "Maximum pool size reached, no available connections!");
        }
    }

    Connection connection = connectionPool
      .remove(connectionPool.size() - 1);

    if(!connection.isValid(MAX_TIMEOUT)){
        connection = createConnection(url, user, password);
    }

    usedConnections.add(connection);
    return connection;
}

Обратите внимание, что метод теперь СЛЭксцепция , то есть нам придется обновить подпись интерфейса, а также.

Или мы могли бы добавить метод, чтобы изящно закрыть наш экземпляр пула соединения:

public void shutdown() throws SQLException {
    usedConnections.forEach(this::releaseConnection);
    for (Connection c : connectionPool) {
        c.close();
    }
    connectionPool.clear();
}

В готовых к производству реализациях пул соединений должен предоставить ряд дополнительных функций, таких как возможность отслеживания соединений, которые в настоящее время используются, поддержка подготовленного объединения заявлений и так далее.

По мере того как мы будем держать вещи просто, мы опустить как снести как снести эти дополнительные характеристики и держать осуществление non-thread-безопасно ради ясности.

7. Заключение

В этой статье мы подробно изучили, что такое объединение соединений, и узнали, как свернуть собственную реализацию объединения соединений.

Конечно, мы не должны начинать с нуля каждый раз, когда мы хотим добавить полное подключение объединения слоя для наших приложений.

Вот почему мы сделали сначала простой обзор, показывающий некоторые из самых популярных фреймворка пула соединений, так что мы можем иметь четкое представление о том, как работать с ними, и забрать тот, который наилучшим образом соответствует нашим требованиям.

Как обычно, все образцы кода, показанные в этой статье, доступны более на GitHub .