본문 바로가기
IT 공부/자바와 웹 애플리케이션

[자바] - 커넥션 풀(HikariCP)과 DataSource 스펙

by exdus3156 2024. 1. 11.

 

1. JDBC 프로그래밍의 문제점

JDBC는 JavaSE의 표준 스펙 중 하나로, 데이터베이스 서버와 연결해 SQL 쿼리를 실행할 수 있게 만든 자바 표준 API다.

API에 불과하므로 구체적인 데이터베이스와 연결해 사용하기 위해서는 데이터베이스 회사(MySQL, Oracle, MariaDB, ....)에서 만든 JDBC 드라이버가 필요하다. 이 드라이버 라이브러리는 maven repository에서 아주 쉽게 다운받을 수 있다.

// 커넥션에 필요한 정보
String url = "jdbc:mariadb://localhost:3306/db";
String user = "user";
String password = "1234";

// JDBC 사용 코드
Class.forName("org.mariadb.jdbc.Driver");
Connection conn = DriverManager.getConnection();
PreparedStatement pstmt = conn.prepareStatement("select now()");
ResultSet rs = pstmt.executeQuery();
rs.next();
String now = rs.getString(1);

// close
rs.close();
pstmt.close();
conn.close();

코드의 대략적인 모습은 위와 같다. 핵심적인 클래스들은 Connection, DriverManager, PreparedStatement, ResultSet 이다. 이 클래스들의 패키지 네임은 아래와 같다.

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

패키지 경로 명을 보면 모두 JavaSE 표준이라는 것을 알 수 있다. DriverManager는 클래스이고, 이를 제외한 나머지는 모두 인터페이스다. 이 인터페이스들의 직접적인 구현체는 각 데이터베이스 회사에서 개발하고 배포하는 JDBC Driver다.

자바의 SQL 표준 API가 JDBC 이므로 자바에서 DB를 처리하는 라이브러리들은 대부분 JDBC를 사용한다. (표준을 지키지 않으면 표준이 아닐 것이다...!) 하지만 JDBC는 몇 가지 문제가 있는데, 대표적인 것이 바로 Connection 관리다.

실무에서 DB는 독립적인 서버에서 실행하는 경우가 많기 때문에 기본적으로 네트워크를 사용해 DB 서버와 JDBC Driver가 TCP/IP 통신을 해야 한다. 문제는 이러한 연결에는 많은 자원이 소모된다는 것이다. 연결 자체에도 오버헤드가 발생하지만, 그것을 매번 close() 해야 하기 때문에 코드도 복잡해진다.

이 연결 작업 자체의 부하를 줄이기 위해 등장한 개념이 Connection Pool이다.

 

 


2. 커넥션 풀(Connection Pool)

풀(pool)이란 무언가가 많이 모여 있는 것을 말한다. ConnectionPool은 Connection 객체들을 모은 것이다. 즉, 미리 TCP/IP 연결을 통해 많은 Connection 객체를 생성하고, 이것이 필요할 때 요청을 하면 Connection 자원을 빌려주는 것이다.

사용 측에서 커넥션을 해제(close)하면, 그것은 이전에 close하는 것과는 다르게 동작한다. 정말로 해제를 하는 것이 아니라 Connection Pool 관리자에게 사용한 connection을 반환하는 것이다. 만약 다른 사용자가 connection을 요청하면 pool에 있는 것 중 하나를 빌려준다.

Connection Pooling 개념은 개념일 뿐이다. 즉, 자바의 스펙에 불과하다. 이것을 구현한 구현체 라이브러리는 따로 다운받아 써야 한다. 여러가지가 있으나 대표적인 커넥션 풀 구현 라이브러리는 HikariCP다.

커넥션풀을 사용하는 코드는 생각보다 아주 단순한 모양이다. HikariCP와 같은 구현체 정보는 생략하고 인터페이스에만 집중해보자.

Connection conn = dataSource.getConnection();
//...
conn.close();

DataSource 라는 객체로부터 getConnection() 메소드를 통해 Connection 객체를 받는다. 그리고 사용법은 JDBC와 똑같다. 다 사용하고 conn.close()를 수행한다.

DataSource의 정체는 javax.sql.DataSource 스펙으로, JavaSE 표준 스펙 중 하나다. 이 인터페이스는 Connection Pooling을 사용하는 API다. 

( ※ javax라는 이름이 붙었는데도 JavaEE(JakartaEE)가 아니라 JavaSE 스펙인데, JakartaEE라고 모든 javax를 포괄하는 것은 아니다. jakartaEE 깃헙 링크에는 다음 설명이 있다. " Several javax package exist outside Jakarta EE and are therefore in no way affected. ")

HikariCP는 바로 이 DataSource 인터페이스를 구현한 라이브러리다. 

// 설정을 config 객체로 생성한다.
HikariConfig config = new HikariConfig();
config.setDriverClassName("org.mariadb.jdbc.Driver");
config.setJdbcUrl("jdbc:mariadb://localhost:3306/sampledb");
config.setUsername("****");
config.setPassword("****");
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");

// config 객체를 통해 DataSource 구현체를 생성한다.
DataSource dataSource = new HikariDataSource(config);

// 사용
Connection conn = dataSource.getConnection();
	// conn 사용 코드...
conn.close();

 

물론 HikariCP와 DataSource는 위와 같은 코드가 아니라 싱글톤, 혹은 스프링 빈으로 관리해야 할 것이다.