Connection Pool & DataSource

DBConncetion을 생성하는 과정

db_connection_creation

DB와 컨넥션을 위해 driver에서 TCP/IP 연결 작업을 수행해서, 필요한 아이디, 비밀번호와 같은 정보를 전달해서 DB와의 컨넥션을 생성해서 반환한다.

하지만, getConnection()를 수행하면 매번, 이와 같은 과정을 반복해서 수행하게 된다, 이는 매우 복잡하고 시간이 많이 소모 되는 일이다.

매번 쿼리 수행에 앞서 연결 작업을 수행해야되므로, 실제로 쿼리 수행 시간 보다 연결 설정에 시간을 더 사용하는 꼴이 되어, 반응성에도 좋지 못하다.

이를 위해 미리 커넥션을 여러 개 만들어서, 필요할 때마다 pool을 통해서 연결을 받아오는 Connection Pool 개념을 사용한다.

Connection Pool

connection_pool

어플리케이션을 구동할 때, 미리 커넥션을 여러 만들어서 connection pool을 구성한다.

이렇게 되면, DB로 쿼리를 요청할 경우가 있으면, 커넥션을 생성할 필요없이, Connection Pool로부터 connection을 요청하면 된다.

또한, connection을 다 사용한 경우, pool에 다시 반납해서, 다음에 다시 활용될 수 있도록 한다.

DataSource

datasource

DB로 부터 컨넥션을 받는 방법은 여러 가지가 있다. 기존의 커넥션을 매번 생성하는 DriverManager 방식과, Connection Pool을 이용하는 dbcp2 connection pool, hikariCP pool, 등이 존재하는데, 가령, DriverManager 방식을 이용하다 hikariCP 방식으로 변경을 하려고 하면 application 전반에 걸쳐 수정해야하는 부분이 존재한다.

datasource1

하지만 DataSource 객체를 이용하면 이러한 수정 작업을 하지 않아도 된다. DataSource 라는 인터페이스를 통해, 서로 다른 연결 방식에 대해서 추상화되어 있다. 따라서, 각각의 방식은 특정 구현 클래스를 통해 내부 로직이 구현되어 있고, 어플리케이션에서는 실제로 인터페이스를 이용해서 커넥션을 얻으면 된다.

public interface DataSource{
    Connection getConnection() throws SQLException;
}

이와 같이 인터페이스에만 의존하도록 설계하여, OCP+DIP를 만족하고 있다.

DriverManagerDataSource

기존의 DriverManager은 내부적으로 DataSource를 구현하고 있지 않아서, DataSource를 활용해서 DriverManager 방식을 이용할 수 없다. 그래서 Spring에서는 DriverManager을 이용할 수 있도록 DataSource를 구현한 DriverManagerDataSource를 제공한다.

DriverManagerDataSource Test

@Test
void dataSourceDriverManager() throws SQLException {
    //DataSource 등록
    DataSource dataSource = new DriverManagerDataSource(URL, USERNAME, PASSWORD);
    
    //DataSource 사용
    Connection conn1=dataSource.getConnection();
    Connection conn2=dataSource.getConnection();

    log.info("connection={}, class={}", conn1, conn1.getClass());
    log.info("connection={}, class={}", conn2, conn2.getClass());
}

위와 같이 DataSource를 이용해서 connection을 받아오는 것을 확인할 수 있다.

DB와 관련된, URL, USERNAME, PASSWORD와 같은 정보를 DB Connection을 받아올때는 사용하지 않는 것을 확인할 수 있다. 한번의 등록에서만 해당 속성 정보를 사용하게 되며, 실제로 사용할 때는 위의 설정 정보를 필요로 하지 않는다.

이렇게 등록과 사용의 분리하는 것이 좋은 설계 방식이다. 이렇게 설계를 하게 되면, 나중에 설정에 변경이 생기더라도, 등록하는 과정만 변경해주면 되므로 변경에 유연하게 대처하는 것이 가능하다.

또한, 등록 과정을 외부에 구성해 놓게 되면, Repository는 DataSource에만 의존해서 DB와의 연결을 진행할 수 있다.

hikariCP

Connection Pool을 이용해서 DB connection을 받아오는 방식을 활용해보자

@Test
void dataSourceConnectionPool() throws SQLException, InterruptedException {
    //DataSource 등록
    DataSource hikariDataSource = new HikariDataSource();
    hikariDataSource.setJdbcUrl(URL);
    hikariDataSource.setUsername(USERNAME);
    hikariDataSource.setPassword(PASSWORD);
    hikariDataSource.setMaximumPoolSize(10);
    hikariDataSource.setPoolName("MyPool");

    //DataSource 사용
    Connection conn1=dataSource.getConnection();
    Connection conn2=dataSource.getConnection();

    log.info("connection={}, class={}", conn1, conn1.getClass());
    log.info("connection={}, class={}", conn2, conn2.getClass());
    Thread.sleep(1000);
}

커넥션 풀에서 커넥션을 생성하는 작업은 어플리케이션 실행속도에 영향을 주지 않기 위해 별도의 쓰레드에서 작동한다. 커넥션을 생성하는 작업은 시간이 많이 소요되는 작업임으로 별도의 쓰레드를 이용해서 작동하는 것이다.

JdbcUtils

Spring에서는 JDBC를 편리하게 다룰 수 있는 JdbcUtils라는 편의메소드를 제공한다.

JdbcUtils의 close를 이용하면 기존의 close 기능을 더욱 간편하게 이용 가능하다.

기존의 close

private void close(Connection conn, Statement statement, ResultSet resultSet){
    if (resultSet != null) {
        try {
            resultSet.close();
        } catch (SQLException e) {
            log.info("error", e);
        }
    }
    if (statement != null) {
        try {
            statement.close();
        } catch (SQLException e) {
            log.info("error", e);
        }
    }
    if (conn != null) {
        try {
            conn.close();
        } catch (SQLException e) {
            log.info("error", e);
        }
    }
}

JdbcUtils close

private void close(Connection conn, Statement statement, ResultSet resultSet){
    JdbcUtils.closeConnection(conn);
    JdbcUtils.closeStatement(statement);
    JdbcUtils.closeResultSet(resultSet);
}

References

link: inflearn

link:springdb

댓글남기기