DriverManger
는 드라이버를 통해서 connection을 획득하고, 우리는 드라이버를 통해서 connection을 조회한다. 하지만 connection을 가져오는 것은 많은 자원을 요구한다. 그래서 스레드풀처럼 미리 커넥션들을 만들어놓고 사용하는 것을 Connection Pool이라고 한다.
App을 실행하는 시점에 Connection Pool은 필요한 Connection을 미리 확보해서 Pool에 저장한다. 대부분 기본 갯수는 10개이다.
ConnectionPool을 사용하면 새로운 connection을 획득하는 것이 아니라 미리 커넥션 풀에 만들어진 connection을 참조해서 쓰면 된다. 여기서 중요한 점은 connection을 종료하지 않고 살아있느 상태로 pool에 반환해야한다는 것이다.
위 그림처럼 connection을 가져오는 방법은 다양하다. 그래서 connection을 가져오는 방법을 추상화한 것이 DataSource
이다.
public interface DataSource {
Connection getConnection() throws SQLException;
}
대부분의 Connection Pool은 DataSource
를 이미 구현했다. 그래서 개발자는 Connection Pool에 의존하는 것이 아니라 DataSource
에 의존하면된다.
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());
}
@Test
void dataSourceDriverManager() throws SQLException {
DataSource 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());
}
}
Connection을 가져오는 두 방법에 대한 테스트코드이다. 두 방법에는 극명한 차이가 있다. 바로 설정과 사용의 분리이다.
DriverManger
의 경우 사용을 하기 위해서 getConnection()
의 파라미터에 매번 설정 정보를 넘겨야한다. 하지만 DataSource
의 경우에는 설정을 한 번만 하면된다. 그래서 나중에 수정을 할 때도 한 번만 수정을 하면 된다.
@Test
void dataSourceConnectionPool() throws SQLException, InterruptedException {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(URL);
dataSource.setUsername(USERNAME);
dataSource.setPassword(PASSWORD);
dataSource.setMaximumPoolSize(10);
dataSource.setPoolName("MyPool");
useDataSource(dataSource);
Thread.sleep(1000);
}
추가 테스트 코드이다. HikariDatatSource
객체를 사용하면 setter를 이용해서 parameter를 설정할 수 있고 풀의 갯수와 이름에 대해서도 설정이 가능하다.
커넥션 풀에 커넥션을 채우는 과정을 보면은 별도의 스레드를 사용해서 채운다. 이는 외부와 통신을 하는 connection을 획득하는 작업은 많은 시간을 필요로하기 때문이다. 이러한 시간 지연은 App 실행 속도를 늦춘다. 그래서 별도의 스레드를 사용해서 커넥션 풀에 데이터를 채우는 것이다.
@Slf4j
@RequiredArgsConstructor
public class MemberRepositoryV1 {
private final DataSource dataSource;
public Member save(Member member) throws SQLException {
String sql = "insert into member(member_id, money) values (?, ?)";
Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = getConnection();
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, member.getMemberId());
pstmt.setInt(2, member.getMoney());
pstmt.executeUpdate(); // 실행
return member;
} catch (SQLException e) {
log.error("db error", e);
throw e;
} finally {
pstmt.close();
conn.close();
}
}
private void close(Connection conn, Statement stmt, ResultSet rs) {
JdbcUtils.closeResultSet(rs);
JdbcUtils.closeStatement(stmt);
JdbcUtils.closeConnection(conn);
}
private Connection getConnection() throws SQLException {
Connection con = dataSource.getConnection();
log.info("get connection={}, class={}", con, con.getClass());
return con;
}
}
DataSource
는 외부에서 주입받아서 사용한다. 이제 DBConnectionUtil
을 사용해서 connection을 획득하지 않는다. 이는 곧 DriverManger
를 사용하지 않는다는 뜻이기도 하다.
JdbcUtils
를 이용하면 커넥션을 더 편리하고 안전하게 닫을 수 있다.