코딩 도구/백엔드 개발 (Backend Development)

[Spring DB] 커넥션 풀과 DataSource

MKISOS 2025. 5. 12. 05:37
반응형

[Spring DB] 커넥션 풀과 DataSource 이해

이 글은 인프런 김영한님의 "스프링 DB 1편 - 데이터 접근 핵심 원리" 강의를 수강하고 정리한 내용이다.

 

이번 정리에서는 데이터베이스 커넥션의 비효율을 해결하기 위한 커넥션 풀과, 커넥션 획득 방식을 추상화하는 DataSource 인터페이스의 역할과 이점을 다룬다. 실제 코드 예제를 통해 DriverManager, DriverManagerDataSource, HikariCP 커넥션 풀 적용 방식도 함께 살펴본다.


1. 커넥션 풀의 필요성

커넥션을 매번 새로 만드는 비용

  • 커넥션 획득 과정은 TCP/IP 연결, 인증, 세션 생성 등 복잡하고 비용이 크다.
  • 특히 SQL 실행 시간 외에 커넥션 생성 시간이 추가되어 응답 지연이 발생한다.
  • 이러한 반복은 시스템 자원을 낭비하고 사용자 경험에도 악영향을 준다.

커넥션 풀 개념

  • 애플리케이션 시작 시 미리 커넥션을 여러 개 생성해 두고, 요청 시 하나씩 빌려쓰는 방식이다.
  • 사용이 끝난 커넥션은 종료하지 않고 커넥션 풀에 반환하여 재사용한다.
  • 기본 풀 사이즈는 10개 내외이며, 서비스 성격에 따라 설정한다.

커넥션 풀 장점

  • 커넥션 생성 비용 제거 → 성능 향상
  • 커넥션 수를 제한 → DB를 보호
  • 실무에서는 커넥션 풀 사용이 기본이다

2. DataSource의 역할

문제 상황

  • 기존 JDBC 방식에서는 DriverManager.getConnection(url, username, password)를 호출할 때마다 커넥션이 새로 생성된다.
  • 커넥션 풀 도입 시 코드도 함께 수정해야 하므로 유연성이 떨어진다.

DataSource 인터페이스

public interface DataSource {
    Connection getConnection() throws SQLException;
}
  • 커넥션을 얻는 방식 자체를 추상화한 표준 인터페이스이다.
  • JDBC 표준이며, 대부분의 커넥션 풀 구현체가 이를 구현하고 있다.

장점

  • 코드가 DataSource에만 의존하면, 구현체 교체만으로 커넥션 획득 방식을 변경 가능
  • 설정과 사용이 분리되어 유지보수에 유리함

3. DataSource 예제 - DriverManager

DriverManager를 직접 사용하는 방식

Connection con = DriverManager.getConnection(URL, USERNAME, PASSWORD);
  • 매 호출 시마다 커넥션이 새로 생성된다.

스프링 제공 DataSource: DriverManagerDataSource

DataSource dataSource = new DriverManagerDataSource(URL, USERNAME, PASSWORD);
Connection con = dataSource.getConnection();
  • 내부적으로 DriverManager를 사용하지만, DataSource 인터페이스를 구현함
  • 설정 정보는 객체 생성 시 한 번만 입력하고, 이후에는 getConnection()만 호출하면 된다.

설정과 사용 분리

  • 설정: DataSource 생성 및 URL, 계정 정보 등록
  • 사용: getConnection() 호출만으로 커넥션 획득

설정 정보를 한 곳에 모아두면, 이후 변경 시 코드 변경 범위가 줄어든다.


4. DataSource 예제 - 커넥션 풀(HikariCP)

HikariCP 설정

HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(URL);
dataSource.setUsername(USERNAME);
dataSource.setPassword(PASSWORD);
dataSource.setMaximumPoolSize(10);
dataSource.setPoolName("MyPool");
  • 커넥션 풀 최대 개수와 이름 설정 가능
  • 커넥션 생성은 별도 쓰레드에서 수행되어 애플리케이션 실행 성능에 영향 주지 않음
  • 테스트 시 로그 확인을 위해 Thread.sleep(1000) 추가

실행 로그 분석

  • 커넥션 풀 초기화: 10개 커넥션 생성 (conn0 ~ conn9)
  • 커넥션 획득: HikariProxyConnection 객체로 래핑되어 반환
  • 상태: active=2, idle=8, waiting=0 과 같은 통계 로그 확인 가능

5. DataSource를 활용한 리포지토리 설계

MemberRepositoryV1

public class MemberRepositoryV1 {
    private final DataSource dataSource;

    public MemberRepositoryV1(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    private Connection getConnection() throws SQLException {
        Connection con = dataSource.getConnection();
        log.info("get connection={}, class={}", con, con.getClass());
        return con;
    }
}
  • DataSource는 생성자 주입으로 받는다.
  • 이제 직접 만든 DBConnectionUtil이 필요하지 않다.
  • JdbcUtils를 사용해 커넥션, Statement, ResultSet을 안전하게 닫을 수 있다.

테스트 클래스에서 적용

HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(URL);
dataSource.setUsername(USERNAME);
dataSource.setPassword(PASSWORD);
repository = new MemberRepositoryV1(dataSource);
  • DriverManagerDataSourceHikariDataSource로 변경해도 리포지토리 코드는 수정할 필요 없다.
  • 인터페이스 기반 설계의 장점이 드러나는 부분이다.

6. 마무리 정리

  • 커넥션 풀은 커넥션 재사용으로 성능 향상과 DB 보호 효과를 가진다.
  • 실무에서는 항상 커넥션 풀(HikariCP 등)을 사용하는 것이 기본이다.
  • DataSource 인터페이스를 통해 커넥션 획득 방식을 추상화하면, 구현체 교체 시 코드 변경 없이 대응 가능하다.
  • 스프링은 DriverManagerDataSource, HikariDataSource 등 다양한 DataSource 구현체를 제공하므로 상황에 따라 유연하게 선택할 수 있다.
  • 설정과 사용을 분리하고, 의존성 주입을 활용하면 유지보수성과 확장성이 높아진다.
반응형