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 데이터 처리  (2) 2022.12.06
    애플리케이션 컨텍스트와 빈팩토리  (0) 2022.11.16

    댓글

Designed by Tistory.