
데이터베이스 커넥션을 획득할 때는 다음과 같은 복잡한 과정을 거친다
→ 문제점?
이를 해결하기 위해 커넥션들을 미리 만들어두고 사용하는 커넥션 풀이라는 방법이다. 커넥션 풀은 이름 그래도 커넥션을 관리하는 풀 이다.

커넥션 풀 오픈소스
커넥션을 이전의 코드처럼 JDBC DriverManager 를 직접 사용하여 커넥션을 생성할 수 도 있고, HikariCP 커넥션 풀에서 커넥션을 받아 사용할 수 도 있다.
이때 기존의 코드로는 커넥션을 획득하는 애플리케이션 코드도 변경되어야 한다. 의존관계가 DriverManager → HikariCP 로 변경되었기 때문이다. 따라서 커넥션을 획득하는 방법을 추상화 하여 해결할 수 있다.
커넥션을 획득하는 방법을 추상화 하기

jakarta.sql.DataSource 인터페이스를 제공한다.DataSource 는 커넥션을 획득하는 방법을 추상화한다.DataSource - interface
public interface DataSource {
Connection getConnection() throws SQLException;
}
getConnection() 을 사용하면 된다!
DriverManagerDataSource 라는 DataSource 를 구현한 클래스를 제공한다.즉, DriverManagerDataSource → 항상 새로운 커넥션을 획득한다.
@Test
void dataSourceDriverManager() throws SQLException {
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());
}
Code 설명 :
DataManagerDataSource → 새로운 커넥션을 생성하여 사용하는 dataSource
dataSource.getConnection(); → dataSource (DataManagerDataSource) 의 커넥션을 받아온다.
DataManagerDataSource 이기 때문에, 새로운 커넥션을 반환 받는다.
@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);
}
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());
}
HikariDataSource dataSource = new HikariDataSource(); 를 사용한다.HikariDataSource 는 Builder pattern 이 적용되어 있다. 필요한 설정 정보를 set 해주고 사용하면 된다.애플리케이션에 DataSource 를 적용해보자.
MemberRepositoryV1
package hello.jdbc.repository;
import hello.jdbc.domain.Member;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.support.JdbcUtils;
import javax.sql.DataSource;
import java.sql.*;
import java.util.NoSuchElementException;
/**
* JDBC - DataSource 사용, Jdbc Utils 사용
*/
@Slf4j
public class MemberRepositoryV1 {
private final DataSource dataSource;
public MemberRepositoryV1(DataSource dataSource) {
this.dataSource = dataSource;
}
public Member save(Member member) throws SQLException {
String sql = "insert into member(member_id, money) values (?,?)";
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
connection = getConnection();
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, member.getMemberId());
preparedStatement.setInt(2, member.getMoney());
preparedStatement.executeUpdate();
return member;
} catch (SQLException e) {
log.error("db error", e);
throw e;
} finally {
close(connection, preparedStatement, null);
}
}
public Member findById(String memberId) throws SQLException {
String sql = "select * from member where member_id = ?";
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = getConnection();
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, memberId);
resultSet = preparedStatement.executeQuery();
if (resultSet.next()) {
Member member = new Member();
member.setMemberId(resultSet.getString("member_id"));
member.setMoney(resultSet.getInt("money"));
return member;
} else {
throw new NoSuchElementException("member not found memberId = " + memberId);
}
} catch (SQLException e) {
log.error("db error", e);
throw e;
} finally {
close(connection, preparedStatement, resultSet);
}
}
public void update(String memberId, int money) throws SQLException {
String sql = "update member set money=? where member_id=?";
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
connection = getConnection();
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1, money);
preparedStatement.setString(2, memberId);
int resultSize = preparedStatement.executeUpdate();
log.info("resultSize={}", resultSize);
} catch (SQLException e) {
log.error("db error", e);
throw e;
} finally {
close(connection, preparedStatement, null);
}
}
public void delete(String memberId) {
String sql = "delete from member where member_id = ?";
Connection connection = null;
PreparedStatement preparedStatement = null;
try{
connection = getConnection();
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, memberId);
preparedStatement.executeUpdate();
log.info("delete memberId={}", memberId);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
private void close(Connection connection, Statement statement, ResultSet resultSet) {
JdbcUtils.closeResultSet(resultSet);
JdbcUtils.closeStatement(statement);
JdbcUtils.closeConnection(connection);
}
private Connection getConnection() throws SQLException {
Connection connection = dataSource.getConnection();
log.info("get connection ={}, class ={}", connection, connection.getClass());
return connection;
}
}
💡중요 Point
JdbcUtils 편의 메서드Test
package hello.jdbc.repository;
import com.zaxxer.hikari.HikariDataSource;
import hello.jdbc.domain.Member;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.sql.SQLException;
import java.util.NoSuchElementException;
import static hello.jdbc.connection.ConnectionConst.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@Slf4j
class MemberRepositoryV1Test {
MemberRepositoryV1 repository;
@BeforeEach
void beforeEach() {
//기본 DriverManager - 항상 새로운 커넥션 획득
// DriverManagerDataSource dataSource = new DriverManagerDataSource(URL, USERNAME, PASSWORD);
//커넥션 풀링
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(URL);
dataSource.setUsername(USERNAME);
dataSource.setPassword(PASSWORD);
repository = new MemberRepositoryV1(dataSource);
}
@Test
void crud() throws SQLException {
Member memberV0 = new Member("memberV100", 10000);
repository.save(memberV0);
//FindById
Member findMember = repository.findById(memberV0.getMemberId());
log.info("findMember = {}", findMember);
log.info("member != findMember {}", memberV0 == findMember);
assertThat(findMember).isEqualTo(memberV0);
//update money 10000 -> 20000
repository.update(memberV0.getMemberId(), 20000);
Member updatedMember = repository.findById(memberV0.getMemberId());
assertThat(updatedMember.getMoney()).isEqualTo(20000);
//delete
repository.delete(memberV0.getMemberId());
//등록 했다가 바로 삭제하는 것이기 때문에 볼 수 없다.
//그럼 검증을 어떻게 해야 하는가?
assertThatThrownBy(() -> repository.findById(memberV0.getMemberId())).isInstanceOf(NoSuchElementException.class);
}
}
💡중요 Point
애노테이션 “@beforeEach” 로 각 테스트가 시작 전 실행되도록 한다. 무엇을? 👇🏻
HikariDataSource dataSource = new HikariDataSource();