ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [김영한 스프링] 07. 커넥션풀과 데이터소스 이해 - DataSource 예제
    Spring/스프링 DB 1편 - 데이터 접근 핵심 원리 2023. 10. 18. 03:06

    DataSource 예제1 - DriverManager

     

    기존에 개발했던 DriverManager를 통해서 커넥션을 획득하는 방법을 확인해 보자.

     

     

    ConnectionTest - 드라이버 매니저

    package hello.jdbc.connection;
    
    import lombok.extern.slf4j.Slf4j;
    import org.junit.jupiter.api.Test;
    
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.SQLException;
    
    import static hello.jdbc.connection.ConnectionConst.*;
    
    @Slf4j
    public class ConnectionTest {
    
        @Test
        void driverManager() throws SQLException {
            Connection con1 = DriverManager.getConnection(URL, USERNAME, PASSWORD);
            Connection con2 = DriverManager.getConnection(URL, USERNAME, PASSWORD);
    
            log.info("connection={}, class={}", con1, con1.getClass());
            log.info("connection={}, class={}", con2, con2.getClass());
        }
    }

    test/java/hello/jdbc/connection/ConnectionTest 생성

     

     

    로그

     

    스프링이 제공하는 DataSource가 적용된 DriverManager인 DriverManagerDataSource를 사용해 보자.

     

     

    ConnectionTest - 데이터소스 드라이버 매니저 추가

    package hello.jdbc.connection;
    
    import lombok.extern.slf4j.Slf4j;
    import org.junit.jupiter.api.Test;
    import org.springframework.jdbc.datasource.DriverManagerDataSource;
    
    import javax.sql.DataSource;
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.SQLException;
    
    import static hello.jdbc.connection.ConnectionConst.*;
    
    @Slf4j
    public class ConnectionTest {
    
        @Test
        void dataSourceDriverManager() throws SQLException {
            // DriverManagerDataSource - 항상 새로운 커넥션을 획득
            DriverManagerDataSource dataSource = new DriverManagerDataSource(URL, USERNAME, PASSWORD);
            useDataSource(dataSource);
        }
    
        private void useDataSource(DataSource dataSource) throws SQLException {
    
            Connection con1 = dataSource.getConnection();
            Connection con2 = dataSource.getConnection();
    
            log.info("connection={}, class={}", con1, con1.getClass());
            log.info("connection={}, class={}", con2, con2.getClass());
        }
    
        @Test
        void driverManager() throws SQLException {
    
            Connection con1 = DriverManager.getConnection(URL, USERNAME, PASSWORD);
            Connection con2 = DriverManager.getConnection(URL, USERNAME, PASSWORD);
    
            log.info("connection={}, class={}", con1, con1.getClass());
            log.info("connection={}, class={}", con2, con2.getClass());
        }
    }

     

     

    로그

    기존 코드와 비슷하지만 DriverManagerDataSource는 DataSource를 통해서 커넥션을 획득할 수 있다. 참고로 DriverManagerDataSource는 스프링이 제공하는 코드이다.

     

     

    파라미터 차이

    기존 DriverManager를 통해서 커넥션을 획득하는 방법과 DataSource를 통해서 커넥션을 획득하는 방법에는 큰 차이가 있다.


    DriverManager

    DriverManager.getConnection(URL, USERNAME, PASSWORD)
    DriverManager.getConnection(URL, USERNAME, PASSWORD)

     

    DataSource

    void dataSourceDriverManager() throws SQLException {
        DriverManagerDataSource dataSource = new DriverManagerDataSource(URL, USERNAME, PASSWORD);
        useDataSource(dataSource);
    }

    private void useDataSource(DataSource dataSource) throws SQLException {
        Connection con1 = dataSource.getConnection();
        Connection con2 = dataSource.getConnection();
        log.info("connection={}, class={}", con1, con1.getClass());
        log.info("connection={}, class={}", con2, con2.getClass());
    }
    • DriverManager는 커넥션을 획득할 때마다 URL, USERNAME, PASSWORD 같은 파라미터를 계속 전달해야 한다. 반면에 DataSource를 사용하는 방식은 처음 객체를 생성할 때만 필요한 파리미터를 넘겨두고, 커넥션을 획득할 때는 단순히 dataSource.getConnection()만 호출하면 된다.

     

    설정과 사용의 분리

    • 설정 : DataSource를 만들고 필요한 속성들을 사용해서 URL , USERNAME , PASSWORD 같은 부분을 입력하는 것을 말한다. 이렇게 설정과 관련된 속성들은 한 곳에 있는 것이 향후 변경에 더 유연하게 대처할 수 있다.
    • 사용 : 설정은 신경 쓰지 않고, DataSource의 getConnection()만 호출해서 사용하면 된다.

     

    설정과 사용의 분리 설명

    • 이 부분이 작아 보이지만 큰 차이를 만들어내는데, 필요한 데이터를 DataSource가 만들어지는 시점에 미리 다 넣어두게 되면, DataSource를 사용하는 곳에서는 dataSource.getConnection()만 호출하면 되므로, URL, USERNAME, PASSWORD 같은 속성들에 의존하지 않아도 된다. 그냥 DataSource만 주입받아서 getConnection()만 호출하면 된다.
    • 쉽게 이야기해서 리포지토리(Repository)는 DataSource만 의존하고, 이런 속성을 몰라도 된다.
    • 애플리케이션을 개발해 보면 보통 설정은 한 곳에서 하지만, 사용은 수많은 곳에서 하게 된다.
    • 덕분에 객체를 설정하는 부분과, 사용하는 부분을 좀 더 명확하게 분리할 수 있다

     

     

    DataSource 예제2 - 커넥션 풀

     

    DataSource를 통해 커넥션 풀을 사용하는 예제를 알아보자.

     

     

    ConnectionTest - 데이터소스 커넥션 풀 추가

    package hello.jdbc.connection;
    
    import com.zaxxer.hikari.HikariDataSource;
    import lombok.extern.slf4j.Slf4j;
    import org.junit.jupiter.api.Test;
    import org.springframework.jdbc.datasource.DriverManagerDataSource;
    
    import javax.sql.DataSource;
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.SQLException;
    
    import static hello.jdbc.connection.ConnectionConst.*;
    
    @Slf4j
    public class ConnectionTest {
    
        @Test
        void dataSourceConnectionPool() throws SQLException, InterruptedException {
            // 커넥션 풀링
            HikariDataSource dataSource = new HikariDataSource();
            dataSource.setJdbcUrl(URL);
            dataSource.setUsername(USERNAME);
            dataSource.setPassword(PASSWORD);
            dataSource.setMaximumPoolSize(10);
            dataSource.setPoolName("MyPool");
    
            useDataSource(dataSource);
            Thread.sleep(1000);
        }
    
        @Test
        void dataSourceDriverManager() throws SQLException {
            // DriverManagerDataSource - 항상 새로운 커넥션을 획득
            DriverManagerDataSource dataSource = new DriverManagerDataSource(URL, USERNAME, PASSWORD);
            useDataSource(dataSource);
        }
    
        private void useDataSource(DataSource dataSource) throws SQLException {
    
            Connection con1 = dataSource.getConnection();
            Connection con2 = dataSource.getConnection();
    
            log.info("connection={}, class={}", con1, con1.getClass());
            log.info("connection={}, class={}", con2, con2.getClass());
        }
    
        @Test
        void driverManager() throws SQLException {
    
            Connection con1 = DriverManager.getConnection(URL, USERNAME, PASSWORD);
            Connection con2 = DriverManager.getConnection(URL, USERNAME, PASSWORD);
    
            log.info("connection={}, class={}", con1, con1.getClass());
            log.info("connection={}, class={}", con2, con2.getClass());
        }
    }

    • HikariCP 커넥션 풀을 사용한다. HikariDataSource는 DataSource 인터페이스를 구현하고 있다.
    • 커넥션 풀 최대 사이즈를 10으로 지정하고, 풀의 이름을 MyPool이라고 지정했다.
    • 커넥션 풀에서 커넥션을 생성하는 작업은 애플리케이션 실행 속도에 영향을 주지 않기 위해 별도의 쓰레드에서 작동한다. 별도의 쓰레드에서 동작하기 때문에 테스트가 먼저 종료되어 버린다. 예제처럼 Thread.sleep을 통해 대기 시간을 주어야 쓰레드 풀에 커넥션이 생성되는 로그를 확인할 수 있다.

     

     

    로그

    HikariConfig

    HikariCP 관련 설정을 확인할 수 있다. 풀의 이름(MyPool)과 최대 풀 수(10)를 확인할 수 있다.

     

    MyPool connection adder

    별도의 쓰레드 사용해서 커넥션 풀에 커넥션을 채우고 있는 것을 확인할 수 있다. 이 쓰레드는 커넥션 풀에 커넥션을 최대 풀 수(10)까지 채운다.

    그렇다면 왜 별도의 쓰레드를 사용해서 커넥션 풀에 커넥션을 채우는 것일까?

    커넥션 풀에 커넥션을 채우는 것은 상대적으로 오래 걸리는 일이다. 애플리케이션을 실행할 때 커넥션 풀을 채울 때까지 마냥 대기하고 있다면 애플리케이션 실행 시간이 늦어진다. 따라서 이렇게 별도의 쓰레드를 사용해서 커넥션 풀을 채워야 애플리케이션 실행 시간에 영향을 주지 않는다.

     

    커넥션 풀에서 커넥션 획득

    커넥션 풀에서 커넥션을 획득하고 그 결과를 출력했다. 여기서는 커넥션 풀에서 커넥션을 2개 획득하고 반환하지는 않았다. 따라서 풀에 있는 10개의 커넥션 중에 2개를 가지고 있는 상태이다. 그래서 마지막 로그를 보면 사용 중인 커넥션 active=2, 풀에서 대기 상태인 커넥션 idle=8을 확인할 수 있다.

    MyPool - After adding stats (total=10, active=2, idle=8, waiting=0)

     

    참고
    HikariCP 커넥션 풀에 대한 더 자세한 내용은 다음 공식 사이트를 참고하자.
    https://github.com/brettwooldridge/HikariCP

     

     

    출처 :  https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-1

     

    스프링 DB 1편 - 데이터 접근 핵심 원리 - 인프런 | 강의

    백엔드 개발에 필요한 DB 데이터 접근 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 DB 접근 기술의 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., 백엔

    www.inflearn.com

Designed by Tistory.