스프링 DB 1편(데이터 접근 핵심 원리)

Ch02. 커넥션풀과 데이터소스 이해 - DataSource 예제

webmaster 2022. 4. 24. 12:45
728x90

DriverManager

@Test
public 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
public void dataSourceDriverManager() throws SQLException {
    //DriverManagerDataSource - 항상 새로운 커낵션을 획득
    //DataSource 를 통해서 가지고 온다는 차이가 있다
    DriverManagerDataSource dataSource = new DriverManagerDataSource(URL, USERNAME, PASSWORD);
    useDataSource(dataSource);
}

private void useDataSource(DataSource dataSource) throws SQLException {
    Connection con1 = dataSource.getConnection(); //사용하는 시점에는 URL, USERNAME, PASSWORD 정보가 필요가 없다
    Connection con2 = dataSource.getConnection();
    log.info("connection={}, class={}", con1, con1.getClass());
    log.info("connection={}, class={}", con2, con2.getClass());
}

스프링이 제공하는 DataSource 사용

  • DriverManagerDataSource는 DataSource를 통해서 커넥션을 획득할 수 있다. 참고로 DriverManagerDataSource는 스프링이 제공하는 코드이다.
  • 파라미터의 차이가 있다.
    • DriverManager는 커넥션을 획득할 때마다 URL , USERNAME , PASSWORD 같은 파라미터를 계속 전달해야 한다. 반면에 DataSource를 사용하는 방식은 처음 객체를 생성할 때만 필요한 파리 미터를 넘겨두고, 커넥션을 획득할 때는 단순히 dataSource.getConnection()만 호출하면 된다.

설정과 사용의 분리

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

사용: 설정은 신경 쓰지 않고, DataSource의 getConnection()만 호출해서 사용하면 된다.

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

커넥션 풀

@Test
public 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); //테스트는 별도의 쓰레드로 동작하기 때문에 Pool에 추가되는 로그를 보기 위해 별도의 Sleep을 주었다
}
private void useDataSource(DataSource dataSource) throws SQLException {
    Connection con1 = dataSource.getConnection(); //사용하는 시점에는 URL, USERNAME, PASSWORD 정보가 필요가 없다
    Connection con2 = dataSource.getConnection();
    Connection con3 = dataSource.getConnection();
    Connection con4 = dataSource.getConnection();
    Connection con5 = dataSource.getConnection();
    Connection con6 = dataSource.getConnection();
    Connection con7 = dataSource.getConnection();
    Connection con8 = dataSource.getConnection();
    Connection con9 = dataSource.getConnection();
    Connection con10 = dataSource.getConnection();
    Connection con11 = dataSource.getConnection();
    //ConnectionPool이 반환 될떄까지 대기를한다(일정 시간 후 예외 발생)
    //어느정도 기다렸다가 예외를 발생시킬지 설정 가능하다
    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을 확인할 수 있다.
  • 커넥션이 모두 사용 중일 때 요청을 하게 되면 일정 시간 커넥션을 얻기 위해 Block 상태가 되는데 이 Block시간이 어느 정도 이상이 되면 예외를 발생시킨다(설정 가능) 
    • 실무에서는 이러한 Block시간을 최대한 짧게 가지고 가서 사용자가 오랫동안 기다리지 않도록 한다.

커넥션 풀

728x90