커넥션풀과 데이터소스 이해

꾸준하게 달리기~·2023년 8월 29일
0

스프링 + 자바

목록 보기
20/20
post-thumbnail

커넥션 풀 이해

커넥션 객체는, DB와 내 소스코드를 연결(connect)해주는 객체이다.

은 취준하는 우리라면 많이 보았을 글, 인재 pool 상시 등록 이라는 말처럼,
모으다 라는 동사이다.

그럼 커넥션 풀은, 커넥션 객체를 모으다 라는 뜻이 된다.

일단, 먼저 커넥션 풀 없이,
DB 커넥션을 매번 획득하는 경우를 생각해보자.
그럼 커넥션을 얻는 경우마다 다음 과정을 거치게 된다.

  1. 소스코드가 DB 드라이버 통해 커넥션 조회
  2. DB드라이버가 TCP/IP 연결으로 커넥션을 연결
  3. 연결된 후 DB에 DB URL, ID, PWD 정보 전달
  4. 인증 후 내부 DB 세션 생성
  5. DB 드라이버가 드디어 커넥션 객체 생성해서 반환

그림으로 보자면, 아래와 같다.


DB에 접근할 일은 많고, 그럼 그럴 일이 있어 커넥션 객체를 얻어야 할 때마다 위의 과정을 거친다는건 자원낭비가 심하다.
이러한 문제를 해결하기 위해, 위에서 간략하게 설명했던
커넥션 풀을 사용하는것이다.

위의 1~5번 인증 과정을 거쳐서 얻는 객체를
커넥션 객체 하나가 아니라, 커넥션 풀 자체를 얻어오면 된다.
해당 사진은 다음과 같다.

한번 커넥션 풀 객체를 얻어온 후에는,
이미 커넥션 풀에 생성되어 있는 커넥션을 가져다 쓰기만 하면 된다.
커넥션을 사용하고 반환할 때는, 커넥션을 종료하는 것이 아니라
살아있는 상태로 커넥션 풀에 반환해야 한다.
(커넥션 풀이 아니라 DB 드라이버를 통해 커넥션 객체 하나를 얻어 사용했다면, 사용한 커넥션은 종료시켜주어야 한다.)

커넥션 풀 오픈소스로는, HikariCP를 많이 사용한다.



DataSource 이해

위에서, 커넥션풀에 대해 설명했다.

여기서 잘 생각해보자.
우리가 커넥션 객체를 사용할 방법은 여러가지가 있다.
HikariCP 오픈소스를 이용해서 커넥션 풀을 얻어서 사용할 수 있고,
DB 드라이버 를 이용해서 커넥션 객체 하나를 얻어 사용할 수도 있다.

(DBCP2도 커넥션 풀을 위한 오픈소스입니다!)

하지만, 위와 같이 커넥션을 얻어 사용하는 방법은 여러가지가 있는데,
해당 방법은 각각 다르다.
즉, DB 매니저, HikariCP 등 각각의 의존관계마다 사용법(커넥션 객체 얻어오는 방법)이 다르다는 소리이다.

자바에서는, 이런 문제를 해결하기 위해 DataSource라는 인터페이스를 제공한다.
즉, DataSource는 커넥션 획득 방법을 추상화한 인터페이스이다.





DataSource 예제1 - DriverManager

커넥션 객체를 하나씩 가져오는 DriverManager,

DriverManager에 DataSource인터페이스를 사용한 DriverManagerDataSource의 사용 예시를 들겠다.

    @Test
    void diverManager() throws SQLException {
        //DriverManager 는 커넥션 풀 아닌 커넥션 자체 획득
        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
    void dataSourceDriverManager() throws SQLException {
        //DriverManagerDataSource : DriverManager + DataSource
        
        //위의 diverManager() 매서드에선 DataSource 인터페이스 없이 DriverManager 를 사용했었음.
        //여기선 DataSource 인터페이스 사용.
        //아래의 DriverManagerDataSource 클래스를 타고 올라가다 보면 DataSource 인터페이스가 나옴.
        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은 커넥션 객체를 하나하나 얻어오기에,
각각 다른 커넥션 객체를 얻어온 것을 볼 수 있다.
(con0, 1, 2, 3)


코드를 다시 집중해서 봐보도록 하자.
diverManager() 매서드가 순수 DriverManager이고,
dataSourceDriverManager() 매서드가 DriverManagerDataSource : DriverManager + DataSource 이다.

둘의 차이점이 보이시는가?
만약 보인다면, 정말 대단한 눈썰미 + 관찰력을 가졌다고 생각한다.

둘의 차이점은 크게 없는것같지만,
설정과 사용의 분리 가 핵심이다.


diverManager() 에선 아래의 방식을 통해 커넥션 객체를 얻어온다.

Connection con1 = DriverManager.getConnection(URL, USERNAME, PASSWORD);

즉, 필요한 값들 (URL, USERNAME, PASSWORD)를 사용해서 커넥션 객체 자체를 얻는다


하지만,
dataSourceDriverManager() 에선 아래의 방식을 통해 커넥션 객체를 얻어온다.

DriverManagerDataSource dataSource = new DriverManagerDataSource(URL, USERNAME, PASSWORD);
Connection con1 = dataSource.getConnection();

첫줄을 통해 필요한 값들을 넣어 설정을 완료하고,
두번째 줄에서 dataSource객체를 이용해 커넥션 객체를 얻는다.
이게 바로 설정과 사용의 분리이다.

이 방식을 통해
객체를 설정하는 부분과 사용하는 부분을 좀 더 명확하게 분리할 수 있다.



DataSource 예제2 - 커넥션 풀 (HikariCP)

아래의 테스트코드를 작성했다.

    @Test
    void dataSourceConnectionPool() throws SQLException, InterruptedException {
        //커넥션 풀링
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setJdbcUrl(URL);
        dataSource.setUsername(USERNAME);
        dataSource.setPassword(PASSWORD);
        dataSource.setMaximumPoolSize(10); //커넥션풀 안의 커넥션 개수 10개로 설정
        dataSource.setPoolName("MyPool"); //이름 MyPool 설정

        useDataSource(dataSource);

        //커넥션 풀에서 커넥션을 생성하는 작업은 어플리케이션 속도에 영향주지 않기 위해
        //별도의 스레드에서 작동한다.
        //그래서 커넥션 객체를 커넥션 풀에 추가하는 로그를 보기 위해서 사용한다.
        Thread.sleep(1000);
    }

해당 코드의 로그를 보면 아래와 같다.

MyPool connection adder 에서 커넥션 풀에 커넥션을 채우는 것을 볼 수 있다.


정리
우리가 DB와 소통하는 방법은 보통
커넥션 객체(DB 소스코드 연결 객체) 얻기
-> SQL문 날리기
-> 결과 얻어오기 이다.

하지만, 자세한 방식은 DB마다 다르다.
해당 문제를 해결하기 위해 JDBC 표준 인터페이스가 등장했다.

또한, 커넥션 객체를 얻는 방식도 다르다.
예를 들어,
DriverManager은 커넥션 객체 하나마다 DB와 소통하여 하나씩 얻어오고,
HikariCP는 커넥션 풀을 얻어 해당 풀을 관리한다.

커넥션 객체를 얻는 방식을 통일하기 위해
DataSource 표준 인터페이스가 등장했다.

HikariCP는 내부적으로 사용하고 있고,
DriverManager은 DriverManagerDataSource클래스를 통해 사용하고 있다.


소스코드 : https://github.com/ingeon2/JDBC
사진출처 : 김영한님 pdf

profile
반갑습니다~! 좋은하루 보내세요 :)

0개의 댓글