ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 🌊 Connection Pool
    🌱 spring 2023. 1. 13. 16:26

    인프런 김영한 님의 스프링 DB 1 강의를 듣고 정리한 글

    (결제하고 못 들은 강의 듣고 다시 내용 정리하기)

     

    커넥션풀 ❓


    데이터베이스 커넥션을 획득 과정

    1. DB 드라이버를 통해 커넥션 조회
    2. TCP/IP 커넥션 연결 (DB 드라이버 - DB)
    3. 커넥션이 연결되면 Id, PW와 같은 부가정보 DB에 전달
    4. 내부 인증을 완료 후 내부에 DB 세션 전달
    5. DB는 커넥션 생성 완료 응답을 보낸다.
    6. DB 드라이버는 커넥션 객체를 생성해 클라이언트에 반환

     

    매번 커넥션을 새로 만드는 것은 매우 복잡하고 시간도 많이 걸린다.

    커넥션을 미리 생성해두고 사용하는 ‘커넥션 풀’ 이라는 방법을 사용.

     

    커넥션 풀 초기화

    애플리케이션을 시작하는 시점에 커넥션 풀은 필요한 만큼 커넥션을 미리 풀에 보관

    • default - 10개

     

    ❗커넥션 풀에 들어있는 커넥션은 DB와 TCP/IP로 커넥션이 연결되어 있는 상태이다.

    • 즉시 SQL을 DB에 전달할 수 있다.

     

    커넥션 풀 사용

    이제는 DB 드라이버를 통해 새로운 커넥션을 획득하지 않는다 ❌

     

    커넥션 풀을 통해 이미 생성되어 있는 커넥션을 객체 참조로 그냥 가져다 사용한다.

    • 커넥션 풀에 커넥션 요청 → 커넥션 풀은 가지고 있는 커넥션 중 하나 반환
    • 받은 커넥션을 사용해 SQL을 데이터베이스에 전달, 그 결과를 받아 처리
    • 커넥션을 모두 사용하면 다시 커넥션 풀에 반환

     

    ❓❗ DataSource


    커넥션을 얻는 여러 가지 방법

    1. DriverManager를 통해 획득
    2. 커넥션 풀을 사용해 획득

     

    DriverManager → 커넥션 풀로 변경 시 문제 발생

    • 애플리케이션 코드 변경 필요
    • 의존관계가 DriverManager 에서 HikariCP 같은 커넥션 풀로 변경

     

    커넥션을 획득하는 방법을 추상화

    자바에서는 이런 문제를 해결하기 위해 javax.sql.DataSource 라는 인터페이스를 제공한다.

    • DataSource는 커넥션을 획득하는 방법을 추상화하는 인터페이스
    • 핵심 기능 : 커넥션 조회

     

    DataSource 인터페이스

    public interface DataSource  extends CommonDataSource, Wrapper {
    
      Connection getConnection() throws SQLException;
    
      Connection getConnection(String username, String password)
        throws SQLException;
      ...
    }

    대부분의 커넥션 풀은 DataSource 인터페이스를 이미 구현해 두었다.

    • 개발자는 HikariCP 커넥션 풀, DBCP2 커넥션 풀 등의 코드를 직접 의존하는 것이 아니다 ❌
    • DataSource 인터페이스만 의존 ❗

     

    ⚠️ DriverManager는 DataSource 인터페이스를 사용하지 않는다.

    따라서 DriverManager → DataSource 기반 커넥션 풀로 변경하려면 코드를 고쳐야 한다.

    • 이를 해결하기 위해 스프링은 DriverManagerDataSource 라는 DataSource를 구현 클래스를 제공

     

    ⌨️ DataSource code


    기존 DriverManager를 통해 커넥션 획득

    @Slf4j
    public class ConnectionTest {
    
      @Test
      void driverManager() throws SQLException {
        Connection connection = DriverManager.getConnection(ConnectionConst.URL,
            ConnectionConst.USERNAME,
            ConnectionConst.PASSWORD);
    
        Connection connection2 = DriverManager.getConnection(ConnectionConst.URL,
            ConnectionConst.USERNAME,
            ConnectionConst.PASSWORD);
    
        log.info("connection = {}, class = {}", connection, connection.getClass());
        log.info("connection = {}, class = {}", connection2, connection2.getClass());
    
      }
    }

     

    결과

     

    DriverManagerDataSource 사용

    @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());
      }
    
    }

     

    결과

     

    차이점

    기존 DriverManager를 통해 커넥션을 획득하는 방법과 DataSource를 통해 커넥션을 획득하는 방법의 차이점

    • DriverManager는 커넥션을 획득(getConnection())할 때마다 파라미터를 계속 전달한다.
    • 하지만 DataSource를 사용하는 방식은 처음 객체를 생성할 때만 필요한 파라미터를 넘겨준다.

     

    📖 설정과 사용의 분리

    설정

    • DataSource를 만들고 필요한 속성들을 사용해 입력
    • 설정과 관련된 속성들은 한 곳에 있는 것이 변경에 유연하게 대처할 수 있다.

    사용

    • 설정은 신경 쓰지 않고 DataSource의 getConnection()만 호출해 사용한다.


    따라서, DataSource를 사용하는 곳에서는 속성들에 의존하지 않고 그냥 DataSource만 주입받아 getConnection()만 호출하면 된다.

    • 리포지토리는 DataSource만 의존하고 속성들은 몰라도 된다.

     

    커넥션 풀

    @Test
    void dataSourceConnectionPool() throws SQLException, InterruptedException {
      //커넥션 풀링: HikariProxyConnection(Proxy) -> JdbcConnection(Target) 
      HikariDataSource dataSource = new HikariDataSource(); 
        
      dataSource.setJdbcUrl(URL);
      dataSource.setUsername(USERNAME);
      dataSource.setPassword(PASSWORD); 
      dataSource.setMaximumPoolSize(10); // 풀의 최대 사이즈 설정
      dataSource.setPoolName("MyPool"); // 풀의 이름 설정
        
      useDataSource(dataSource);
      Thread.sleep(1000); // 커넥션 풀에서 커넥션 생성 시간 대기
    }
    • 커넥션 풀에서 커넥션을 생성하는 작업은 애플리케이션 실행 속도에 영향을 주지 않기 위해 별도의 스레드에서 작동한다.
    • 별도 쓰레드에서 동작해 테스트가 먼저 종료되어 버린다. → 따라서 대기 시간을 줌

     

    결과

    • 활성 2개, 대기 8개 → 전체 10개 커넥션 생성
    • 활성 2개인 이유 - close를 안 해줘서 활성화되어있다.

     

    📖 MyPool connection adder

    • 별도의 스레드를 통해 커넥션 풀에 커넥션을 채운다. (최대 풀 수 까지)

     

    왜 별도의 쓰레드를 사용❓

    커넥션 풀에 커넥션을 채우는 작업은 상대적으로 오래 걸림.

    • 따라서 별도의 쓰레드를 통해 애플리케이션 실행 시간에 영향을 주지 않게 한다.

     

    DataSource 적용

    커넥션을 DataSource에서 가져오도록 한다.

    private final DataSource dataSource;
    
    private Connection getConnection() throws SQLException {
      Connection connection = dataSource.getConnection();
    
      log.info("get connection={}, class={}", connection, connection.getClass());
    
      return connection;
    }

     

    connection, statement, resultset 을 close 해주는 코드를 JdbcUtils를 통해 간단히 리팩토링

    private void close(Connection con, Statement stmt, ResultSet rs) {
      JdbcUtils.closeResultSet(rs);
      JdbcUtils.closeStatement(stmt);
      JdbcUtils.closeConnection(con);
    }

     

    📖 JdbcUtils

    • 스프링은 JDBC를 편리하게 다룰 수 있는 JdbcUtils 라는 편의 메서드를 제공
    • 커넥션을 좀 더 편리하게 닫을 수 있다.

     

    DriverManagerDataSource VS HikariDataSource

    DriverManagerDataSource 를 사용하면 항상 새로운 커넥션이 생성되어 사용된다

     

    반면, Hikari 같은 커넥션 풀을 사용하면 커넥션을 재사용한다.

    • 커넥션을 사용하고 다시 돌려준다.

     

    참고자료

    https://www.inflearn.com/course/스프링-db-1

     

    '🌱 spring' 카테고리의 다른 글

    🐱 Tomcat  (0) 2023.03.12
    ThreadLocal  (0) 2023.01.18
    JDBC ❓  (0) 2023.01.13
    Spring Interceptor에서 Request 데이터 처리  (1) 2022.12.06
    애플리케이션 컨텍스트와 빈팩토리  (0) 2022.11.16

    댓글

Designed by Tistory.