JDBC & Connection Pool

하루·2025년 10월 13일

JAVA

목록 보기
1/8

JDBC란

JDBC는 Java DataBase Connectivity로 Java Application와 RDBMS 사이를 연결해주는 standard Java API이다.
기본적인 특징으로는 다음과 같다.

  • RDBMS를 관리하는 인터페이스 제공
  • SQL 쿼리 가능
  • SQL 쿼리 결과 볼 수 있음
  • RDBMS와의 연결을 관리
  • platform, database independent (platform을 변경하거나 Database를 변경해도 그대로 작동한다.)

JDBC는 보통 2개의 부분으로 이루어져 있다.
JDBC API(인터페이스 부분으로 Java 표준에서 정의한 공통 인터페이스 집합으로 Connection, Statement, ResultSet이 있다.)
그리고 다른 하나는 JDBC Driver(각 DBMS에 맞게 JDBC API를 구현한 클래스 라이브러리로, JDBC 외부에서 만든 실제 구현체)이다.

여기서 Driver Manager는 JDBC API의 내부 클래스로 드라이버 관리 기능을 담당한다.


Driver Manager

RDBMS와의 연결을 관리하고, 어떤 JDBC Driver 구현체를 사용할지 관리하는 역할을 한다.기본적으로 Connection Pool 기능이 없기 때문에, 단순히 드라이버를 로드하고, 커넥션을 생성하는 역할이다.

-> 근데 현재 거의 안 씀...! DataSource로 대체되었다.


JDBC API

  • Connection: 연결
  • Statement: SQL 연결 및 전달
  • ResultSet: 결과 응답 관리

이처럼 공통적인 인터페이스를 정의해놓은 API이다.


JDBC Driver

이는 외부 벤더(Oracle, MySQL 등)에서 JDBC API를 구현하여 실제 DB와 통신을 수행한다.


JDBC 구현 및 연결

JDBC를 연결하기 위해서는 연결할 데이터베이스 종류(MySQL, Oracle 등)에 따라 어떤 드라이버를 사용할 것인지 알아야 한다.
그다음 데이터베이스의 주소, 그리고 사용할 계정(username, password)을 알아야 한다.

이 4가지 정보 중에서 어떤 드라이버를 사용할 것인지는 Gradle을 사용하면 build.gradle에, Maven을 사용하면 pom.xml에 추가하면 된다.

build.gradle 예시

plugins {
    id 'java'
}

repositories {
    mavenCentral()
}

dependencies {
    // JDBC 드라이버
    implementation 'mysql:mysql-connector-java:8.0.33'

    // Spring JDBC (스프링 기반이라면)
    implementation 'org.springframework:spring-jdbc:5.3.39'
}

pom.xml예시

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
                             http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>jdbc-demo</artifactId>
    <version>1.0.0</version>

    <dependencies>
        <!-- JDBC 드라이버 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.33</version>
        </dependency>

        <!-- Spring JDBC -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.3.39</version>
        </dependency>
    </dependencies>
</project>

실제 프로젝트에서 사용한 코드

plugins {
	id 'java'
	id 'org.springframework.boot' version '3.5.4'
	id 'io.spring.dependency-management' version '1.1.7'
}

...생략

repositories {
	mavenCentral()
}

dependencies {
	... 생략
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	
	runtimeOnly 'com.h2database:h2'
	runtimeOnly 'com.mysql:mysql-connector-j'
  ... 생략
}
...생략

나머지 3개의 정보(RDBMS 주소, username, password)는 application.yml에 저장하면 된다.

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/testdb?useSSL=false&serverTimezone=UTC
    username: root
    password: 1234

이렇게 4가지 정보를 다 추가하면, Spring Boot에서 내부적으로 DataSource 객체를 자동 생성(Bean으로 등록)해준다.
(과거에는 Driver Manager의 getConnection을 사용해야 했지만, 현재는 자동으로 Spring Boot에서 처리해주므로 개발자가 직접 관리할 필요가 없다.)

이제 SQL문을 작성해 DB로 보낸다.
이때 SELECT와 같은 DQL문은 결과가 ResultSet으로 오고, INSERT, DELETE와 같은 DML문은 성공적으로 처리된 행의 수를 반환한다.
SQL문이 하나 끝나면 자원을 Connection Pool로 다시 반환한다.


Connection Pool

커넥션 풀의 등장 배경부터 보자. APPLICATION과 DB는 TCP/IP를 사용해서 연결되기 때문에, 매 QUERY마다 연결 과정을 다시 수행해야 한다. 이 뿐만이 아니라, 데이터베이스 세션 생성, 인증, 트랜잭션 준비 과정 등이 추가로도 있기 때문에 매우 시간 소모적(time-consuming)이다.
따라서 미리 여러 개의 Connection을 만들어 두고 이를 모아둔 Connection Pool을 사용한다.
허가된 경우 Connection Pool에서 하나의 Connection을 가져와 사용한 뒤, 반납하면 된다.
이로써 매번 연결/해제하는 번거로움이 사라진다.

이는 “Reducing Overhead with Databases” = Database Connection Cache Implementation이라고 할 수 있다.

대표적인 예로는 HikariCP, Tomcat, dbcp2, c3p0, vibur 등이 있다.


HikariCP

HikariCP는 Spring Boot에서 공식적으로 지원하는 Connection Pool이며, 다른 Connection Pool보다 빠르다고 알려져 있다(HikariCP 피셜).
연결 방법은 다음과 같다.

  1. Dependency를 추가한다.
  2. 수동으로 DataSource를 생성하거나, application.yml에 RDBMS 주소 및 유저 정보를 설정한다.
  3. 이렇게 설정하면 Spring Boot가 자동으로 Connection Pool을 생성하고 관리한다.
    개발자가 직접 Connection Pool을 생성하거나 초기화, 관리할 필요가 없다.
  4. Connection Pool과 관련해서 Pool size 등을 정의하고 싶은 경우는 application.yml에서
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/testdb
    username: root
    password: 1234
    hikari:
      maximum-pool-size: 10
      minimum-idle: 2
      idle-timeout: 30000

과 같이 정리할 수 있다.
여기서, 대표적으로 관리할 수 있는 변수들은 다음과 같다.

설정 키설명기본값
maximum-pool-size최대 커넥션 수10
minimum-idle최소 유후 커넥션 수보통 maximum-pool-size와 동일
idle-timeout커넥션이 유후 상태로 유지될 최대 시간600000ms(10분)
connection-timeout커넥션 얻기 위한 최대 대기 시간(ms)30000ms(30초)
max-lifetime최대 생존 시간1800000ms(30분)
pool-name이름HikariPool-1

추가 설명 :
minimum-idle

  • 설명 :손님이 없더라도, 갑자기 몰려들 것을 대비해 항상 계산대에 대기시키는 최소 계산원 수

  • 목적: 갑작스러운 요청(손님) 증가에 빠르게 대응하기 위함

idle-timeout

  • 설명: 대기하던 계산원이 일정 시간(예: 10분) 동안 계속 아무 일도 안 하면 "지금은 필요 없으니 잠시 창고 정리해" 라며 계산대에서 빼는 시간

목적: 불필요한 자원(계산원) 낭비를 막기 위함

상황에 따라서 조절하기

문제 1

우리 서비스는 동시 접속자 수가 갑자기 늘어나면서 "Connection is not available, request timed out" 라는 에러 로그가 자주 보여. 애플리케이션 응답 속도도 전반적으로 느려졌어.

내 정답 : maximum-pool-size 늘리기
내 이유 : connection pool에 있는 connection 리소스가 없어서, 대기 시간이 발생. 대기자들은 적어도 connection-timeout만큼 기다려야 함. 그만큼 대기했는데, connection이 안 생기면 connection timeouted .

설명 : 동시에 처리할 수 있는 DB 작업의 한계를 늘려야 합니다.

문제 2

평소에는 괜찮은데, 특정 이벤트 시간(ex. 선착순 쿠폰 발급)만 되면 순간적으로 요청이 몰리면서 서비스가 잠깐 버벅거려. 미리 대비할 수 있도록 여유 커넥션을 항상 준비해두고 싶어.

내 정답 : minimum-pool-size 늘리기 ?
내 이유 : 갑자기 폭파해도 감당할 수 있는 최소한의 pool size가 있어야 할 것 같아서

정답 : minimum-idle : 트래픽 급증에 대비해 최소한의 유휴 커넥션 수를 확보합니다. (남아 도는 )

문제 3

애플리케이션을 배포하고 나면 처음엔 잘 동작하는데, 사용자가 없는 새벽 시간(약 10분 이상)이 지나고 첫 요청이 오면 "Connection has been closed" 같은 DB 연결 오류가 발생해.

내 정답 : maximum-lifetime
내 이유 : pool 자체의 생존 시간으로, 너무 짧아서 끊김.

정답 : idle-timeout: 방화벽 등이 커넥션을 끊기 전에 풀이 먼저 유휴 커넥션을 제거하도록 설정합니다.

문제 4

DB에 부하가 몰렸을 때, 사용자가 30초씩 하염없이 기다리게 하는 것보다 차라리 "잠시 후 다시 시도해주세요" 라는 메시지를 빨리 보여주고 싶어.

내 정답 : connection-timeout 감소

문제 5

회사 DBA가 "모든 DB 커넥션은 보안 정책 및 메모리 누수 방지를 위해 최대 1시간 주기로 교체해야 합니다"라는 가이드를 줬어.

내 정답 : max-lifetime

문제 6

우리 애플리케이션은 사용자의 기본 정보를 저장하는 DB와 로그를 쌓는 DB, 이렇게 두 개를 사용해. 모니터링을 할 때 로그에 찍히는 "HikariPool-1", "HikariPool-2" 만으로는 어떤 DB 풀인지 구분이 어려워.

내 정답 : pool-name

Tomcat

과거 Tomcat까지 사용했던 경험이 있다. Tomcat JDBC Connection Pool도 Spring Boot에서 많이 사용된다. HiKariCP와 비슷하게 설정할 수 있다. 똑같이 application.yml에서 설정할 수 있다.

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/testdb
    username: root
    password: 1234
    type: org.apache.tomcat.jdbc.pool.DataSource
    tomcat:
      max-active: 10        # 최대 활성 커넥션 수
      max-idle: 10          # 최대 유휴 커넥션 수
      min-idle: 2           # 최소 유휴 커넥션 수
      max-wait: 30000       # 커넥션 요청 대기 시간(ms)
      remove-abandoned: true
      remove-abandoned-timeout: 60  # 유휴 커넥션 제거 시간(초)
      test-on-borrow: true
      validation-query: SELECT 1

설정키 옵션도 동일하다.

... 하지만! 보통 SpringBoot에서는 HikariCP를 권장한다.

Connection Leak

이는 Connection Pool에서 Connection을 빌려간 후, 사용이 끝나도 반납하지 않아서 Pool이 고갈되는 현상이다. (이를 위해서 try-with-resources 구문을 사용하면 된다고 한다)

// try-with-resources를 사용하면 코드 블록이 끝나면 자동으로 connection.close()가 호출되어 Pool에 반납됩니다.
try (Connection connection = dataSource.getConnection()) {
   // ... 비즈니스 로직 ...
} catch (SQLException e) {
   // ... 예외 처리 ...
}

DataSource

JDBC에서 데이터베이스 연결을 관리하고 제공하기 위한 Interface이다. (DriverManager를 대체하는 역할로, DriverManager 보다 효율적이고, 자동으로 Connection Pool을 관리할 수 있는 JDBC 연결 관리자이다)

  • 처음에 Datasource가 DB 연결을 할 때 Connection 자체를 여러개 미리 생성해 둔다.
  • 새로운 요청이 들어올 때마다 Driver manager는 Connection Pool 기능이 없었기에 매번 connection을 생성해야 했지만, Datasource는 Pool에 있는 Connection을 대여해줌
  • 사용이 끝나면 실제로 종료하는 것이 아닌, Pool에 반환하도록 한다.

기본 DataSource 추가 코드

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/testdb
    username: root
    password: 1234
    driver-class-name: com.mysql.cj.jdbc.Driver
    hikari:![](https://velog.velcdn.com/images/frowny/post/dc866578-8692-4a01-aec3-b3e54249f1d2/image.png)

Datasource가 Hikari라는 connection pool (또는 Tomcat이라는 connection pool)을 사용하는 것

0개의 댓글