
데이터베이스 커넥션을 매번 획득

문제
고객이 애플리케이션을 사용할 때, SQL을 실행하는 시간 뿐만 아니라 커넥션을 새로 만드는 시간이 추가되기 때문에 결과적으로 응답 속도에 영향을 준다. 이것은 사용자에게 좋지 않은 경험을 줄 수 있다.
이런 문제를 한번에 해결하는 아이디어가 바로 커넥션을 미리 생성해두고 사용하는 커넥션 풀이라는 방법이다.
커넥션 풀 사용

이점
commons-dbcp2, tomcat-jdbc pool, HikariCP 등이 있다.커넥션을 얻는 방법은 앞서 학습한 JDBC DriverManager를 직접 사용하거나, 커넥션 풀을 사용하는 등 다양한 방법이 존재한다.
커넥션을 획득하는 다양한 방법

DriverManager를 통해 커넥션 획득하다가 커넥션 풀로 변경 시 애플리케이션 코드도 함께 변경해야 한다.커넥션을 획득하는 방법을 추상화

javax.sql.DataSource라는 인터페이스를 제공한다.DataSource는 커넥션을 획득하는 방법을 추상화 하는 인터페이스이다.먼저 기존에 개발했던 DriverManager를 통해서 커넥션을 획득하는 방법을 확인해보자.
ConnectionTest
package com.example.jdbc.connection;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import static com.example.jdbc.connection.ConnectionConst.*;
@Slf4j
public class ConnectionTest {
@Test
void driverManager() throws SQLException {
Connection con1 = DriverManager.getConnection(URL, USERNAME, PASSWORD);
Connection con2 = DriverManager.getConnection(URL, USERNAME, PASSWORD);
log.info("connection={}, class={}", con1, con1.getClass());
log.info("connection={}, class={}", con2, con2.getClass());
}
}

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는 커넥션을 획득할 때 마다 URL, USERNAME, PASSWORD 같은 파라미터를 계속 전달해야 한다. 반면에 DataSource를 사용하는 방식은 처음 객체를 생성할 때만 필요한 파리미터를 넘겨두고, 커넥션을 획득할 때는 단순히 dataSource.getConnection()만 호출하면 된다.설정과 사용의 분리
DataSource를 만들고 필요한 속성들을 사용해서 URL , USERNAME , PASSWORD 같은 부분을 입력하는 것을 말한다. 이렇게 설정과 관련된 속성들은 한 곳에 있는 것이 향후 변경에 더 유연하게 대처할 수 있다.DataSource의 getConnection()만 호출해서 사용하면 된다.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); // 커넥션 풀에서 커넥션 생성 시간 대기
}

HikariConfig
- HikariCP 관련 설정을 확인할 수 있다. 풀의 이름(MyPool)과 최대 풀 수(10)을 확인할 수 있다.
MyPool connection adder
이번에는 애플리케이션에 DataSource를 적용해보자.
기존 코드를 복사해서 새로 만들었다. V0 -> V1
MemberRepositoryV1
private final DataSource dataSource;
private Connection getConnection() throws SQLException {
Connection con = dataSource.getConnection();
log.info("get connection={}, class={}", con, con.getClass());
return con;
}
// 변경
private void close(Connection con, Statement stmt, ResultSet rs) {
// 간편
JdbcUtils.closeResultSet(rs);
JdbcUtils.closeStatement(stmt);
JdbcUtils.closeConnection(con);
}
// 나머지 동일
MemberRepositoryV1Test (DriverManager - 항상 새로운 커넥션 획득)
package com.example.jdbc.repository;
import com.example.jdbc.domain.Member;
import lombok.extern.slf4j.Slf4j;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import java.sql.SQLException;
import java.util.NoSuchElementException;
import static com.example.jdbc.connection.ConnectionConst.*;
@Slf4j
public class MemberRepositoryV1Test {
MemberRepositoryV1 repository;
@BeforeEach
void beforeEach(){
DriverManagerDataSource dataSource = new DriverManagerDataSource(URL, USERNAME, PASSWORD);
repository = new MemberRepositoryV1(dataSource);
}
@Test
void crud() throws SQLException {
//save
Member member = new Member("memberV0", 10000);
repository.save(member);
//findById
Member findMember = repository.findById(member.getMemberId());
log.info("findMember={}", findMember);
Assertions.assertThat(findMember).isEqualTo(member);
//update: money: 10000 -> 20000
repository.update(member.getMemberId(), 20000);
Member updatedMember = repository.findById(member.getMemberId());
Assertions.assertThat(updatedMember.getMoney()).isEqualTo(20000);
//delete
repository.delete(member.getMemberId());
Assertions.assertThatThrownBy(() -> repository.findById(member.getMemberId())).isInstanceOf(NoSuchElementException.class);
}
}

DriverManagerDataSource를 사용하면 conn0~5 번호를 통해서 항상 새로운 커넥션이 생성되어서 사용되는 것을 확인할 수 있다. (스크린 샷에서는 conn0 - conn1)MemberRepositoryV1Test(HikariDataSource 커넥션 풀링)
@BeforeEach
void beforeEach(){
// DriverManagerDataSource dataSource = new DriverManagerDataSource(URL, USERNAME, PASSWORD);
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(URL);
dataSource.setUsername(USERNAME);
dataSource.setPassword(PASSWORD);
repository = new MemberRepositoryV1(dataSource);
}

DriverManagerDataSource를HikariDataSource로 변경해도MemberRepositoryV1의 코드는 전혀 변경하지 않아도 된다.MemberRepositoryV1는DataSource인터페이스에만 의존하기 때문이다.
참고
김영한: 스프링 DB 1편 - 데이터 접근 핵심 원리(인프런)
Github - https://github.com/b2b2004/Spring_DB