이번에는 DataSource 를 통해 커넥션 풀을 사용하는 예제를 알아봅시다.
@Slf4j
public class ConnectionTest {
@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); //커넥션 풀에서 커넥션 생성 시간 대기
}
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());
}
}
데이터베이스와의 연결을 관리하는 커넥션 풀을 생성합니다. 즉, HikariCP 커넥션 풀을 사용합니다.
HikariDataSource는 DataSource 인터페이스를 구현하고 있습니다.
그렇기 때문에 getConnection() 을 구현하고 있습니다.
public class HikariDataSource extends HikariConfig implements DataSource, Closeable {
}
반환 타입을 DataSource 로 할 수 있습니다.
DataSource dataSource = new HikariDataSource();
하지만 타입은 HikariDataSource 객체로 반환해야 url, username, password, poolsize, name 등을 설정할 수 있습니다. DataSource 객체는 그냥 인터페이스이기 때문이죠.
HikariCP 에서 제공하는 라이브러리입니다. 스프링에서 JDBC를 쓰면 자동으로 import 됩니다.
커넥션 풀에서 커넥션을 생성하는 작업은 애플리케이션 실행 속도에 영향을 주지 않기 위해 별도의 쓰레드에서 작동합니다.
별도의 쓰레드에서 동작하기 때문에 커넥션을 풀에 추가하기 전에 테스트가 먼저 종료되어 버립니다.
Thread.sleep을 통해 대기 시간을 주어야 커넥션 풀에 커넥션이 생성되는 시간이 생기고 풀에 추가하는 로그를 확인할 수 있습니다.
참고로 로그를 확인하면 MyPool connection adder 란 별도의 쓰레드에서 동작함을 알 수 있습니다.
→ 자세히
실행결과의 로그를 확인해봅시다. (로그 순서는 이해하기 쉽게 약간 수정했습니다.)
#커넥션 풀 초기화 정보 출력
HikariConfig - MyPool - configuration:
HikariConfig - maximumPoolSize................................10
HikariConfig - poolName................................"MyPool"
#커넥션 풀 전용 쓰레드가 커넥션 풀에 커넥션을 10개 채움
[MyPool connection adder] MyPool - Added connection conn0: url=jdbc:h2:.. user=SA
[MyPool connection adder] MyPool - Added connection conn1: url=jdbc:h2:.. user=SA
[MyPool connection adder] MyPool - Added connection conn2: url=jdbc:h2:.. user=SA
[MyPool connection adder] MyPool - Added connection conn3: url=jdbc:h2:.. user=SA
[MyPool connection adder] MyPool - Added connection conn4: url=jdbc:h2:.. user=SA
...
[MyPool connection adder] MyPool - Added connection conn9: url=jdbc:h2:.. user=SA
#커넥션 풀에서 커넥션 획득1
ConnectionTest - connection=HikariProxyConnection@446445803 wrapping conn0: url=jdbc:h2:tcp://localhost/~/test user=SA, class=class com.zaxxer.hikari.pool.HikariProxyConnection
#커넥션 풀에서 커넥션 획득2
ConnectionTest - connection=HikariProxyConnection@832292933 wrapping conn1: url=jdbc:h2:tcp://localhost/~/test user=SA, class=class com.zaxxer.hikari.pool.HikariProxyConnection
MyPool - After adding stats (total=10, active=2, idle=8, waiting=0)
HikariConfig
MyPool connection adder
별도의 MyPool connection adder 쓰레드를 사용해서 커넥션 풀에 커넥션을 채우고 있는 것을 확인할 수 있습니다. 이 쓰레드는 커넥션 풀에 커넥션을 최대 풀 수(10)까지 채웁니다.
그렇다면 왜 별도의 쓰레드를 사용해서 커넥션 풀에 커넥션을 채우는 것일까요?
커넥션 풀에 커넥션을 채우는 것은 상대적으로 오래 걸리는 일입니다. TCP/IP 연결을 하기 때문이죠.
애플리케이션을 실행할 때 커넥션 풀을 채울 때 까지 마냥 대기하고 있다면 애플리케이션 실행 시간이 늦어집니다.
따라서 이렇게 별도의 쓰레드를 사용해서 커넥션 풀을 채 워야 애플리케이션 실행 시간에 영향을 주지 않습니다.
커넥션 풀에서 커넥션 획득
커넥션 풀에서 커넥션을 획득하고 그 결과를 출력했습니다.
HikariProxyConnection 가 바로 Hikari가 만들어준 인스턴스 객체입니다. 내부에 JDBC connection 0번 1번이 랩핑되어 들어있다고 보면 됩니다.
위 코드는 커넥션 풀에서 커넥션을 2개 획득(getConnection()을 호출)하고 반환하지는 않았습니다. 따라서 풀에 있는 10개의 커넥션 중에 2개를 가지고 있는 상태죠.
그래서 마지막 로그를 보면 사용중인 커넥션 active=2, 풀에서 대기 상태인 커넥션 idle=8 을 확인할 수 있습니다.
MyPool - After adding stats (total=10, active=2, idle=8, waiting=0)
참고로 사용중인 커넥션은 꼭 close를 해줍시다.