DB - 커넥션풀과 DataSource
우리가 지금까지 사용했던 서버들은 창을 닫으면 데이터가 날라간다. 그렇기 때문에 데이터베이스를 만들어서 데이터를 저장할 수 있는 공간을 따로 설정해야한다.
- Connection - 데이터베이스와 연결시켜주는 객체, getConnection을 통해 DB를 연결시켜줌
- ResultSet - 쿼리의 결과를 담은 객체
- Statement - 정적 쿼리를 전달
- Preparedstatement - sql문을 전달
private void close(Connection con, Statement stmt, ResultSet rs) {
JdbcUtils.closeResultSet(rs);
JdbcUtils.closeStatement(stmt);
JdbcUtils.closeConnection(con);
// close는 생성과 반대로 (con부터 생성하고 있으니 rs -> stmt -> con 순으로 닫아주기)
}
private Connection getConnection() throws SQLException {
Connection con = dataSoure.getConnection();
log.info("get connection={}, class={}", con, con.getClass());
return con;
}
dataSource의 getConnection메서드를 이용해서 getConnection을 만들어주고 save, findById, delete, update에 사용할 것이다.
close 메서드는 Connection, Statement, ResultSet을 사용하고 닫아줘야하는데 닫아줄 때 사용할 용도로 만든 메서드이다.
public Member save(Member member) {
String sql = "insert into member(member_id, money) values (?, ?)";
// (?, ?)가 아니라 (memberId, money)라 하면 보안상의 위험
Connection con = null;
PreparedStatement pstmt = null;
try {
con = getConnection();
pstmt = con.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 new RuntimeException(e);
} finally {
close(con, pstmt, null);
// 항상 close가 되도록 finally에서 호출해야 함
}
}
save 메서드는 아이디와 가지고 있는 돈을 저장하는 메서드이다. Connection은 아까 만들었던 getConnection 객체를 넣어주고, PreparedStatement를 통해 sql을 입력하고, sql의 ?에 첫 번째는 MemberId를, 두 번째에는 Money를 넣어준다.
finally에서 무조건 close를 해줘야한다.
public Member findById(String memberId) throws SQLException {
// 조회 기능
String sql = "select * from member where member_id = ?";
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = getConnection();
pstmt = con.prepareStatement(sql);
pstmt.setString(1, memberId);
rs = pstmt.executeQuery();
if (rs.next()) { // 최초의 커서는 아무것도 가리키지 않고 next할 때마다 다음 데이터를 가리킴
Member member = new Member();
member.setMemberId(rs.getString("member_id"));
member.setMoney(rs.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(con, pstmt, rs);
}
}
다음은 조회를 할 수 있는 FindById 메서드이다. 방식은 위와 동일하고, ResultSet이 들어갔는데
rs=pstmt.executeQuery(); 이후에 조건문을 보면 Member의 id를 확인하면서 맞으면 member를 반환하고 틀리면 다른 커서를 가리키는 것이다.
public void update(String memberId, int money) {
String sql = "update member set money=? where member_id=?";
Connection con = null;
PreparedStatement pstmt = null;
try {
con = getConnection();
pstmt = con.prepareStatement(sql);
pstmt.setInt(1, money);
pstmt.setString(2, memberId);
int resultSize = pstmt.executeUpdate();
log.info("resultSize={}", resultSize);
} catch (SQLException e) {
log.error("db error", e);
throw new RuntimeException(e);
} finally {
close(con, pstmt, null);
}
}
update에서는 그냥 save와 비슷하게 원래 있던 값들을 교체해주는 방식을 사용했다.
public void delete(String memberId) throws SQLException {
String sql = "delete from member where member_id=?";
Connection con = null;
PreparedStatement pstmt = null;
try {
con = getConnection();
pstmt = con.prepareStatement(sql);
pstmt.setString(1, memberId);
pstmt.executeUpdate();
} catch (SQLException e) {
log.error("db error", e);
throw e;
} finally {
close(con, pstmt, null);
}
}
마지막으로 delete 메서드인데, delete는 memberId를 받아와서 없애주는 메서드이다.
MemberRepositoryV1Test
@Slf4j
class MemberRepositoryV1Test {
MemberRepositoryV1 repository;
@BeforeEach
void beforeEach() {
// 커넥션 풀 사용 (Hikari)
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(URL);
dataSource.setUsername(USERNAME);
dataSource.setPoolName(PASSWORD);
repository = new MemberRepositoryV1(dataSource);
}
@Test
void crud() throws SQLException, InterruptedException {
Member member = new Member("memberV1", 10000);
repository.save(member);
}
}
Hikari를 이용해서 커넥션 풀을 사용하고, memberV1이라는 새로운 Member를 만들어서 실행하면 연결시켜둔 h2 데이터 베이스에
memberV1이 저장되게 된다.
@Test
void crud() throws SQLException, InterruptedException {
Member member = new Member("memberV1", 10000);
repository.save(member);
//findById
Member findMember = repository.findById(member.getMemberId());
log.info("findMember={}", findMember);
assertThat(findMember).isEqualTo(member);
//update: money : 10000 -> 20000
repository.update(member.getMemberId(), 20000);
Member updatedMember = repository.findById(member.getMemberId());
assertThat(updatedMember.getMoney()).isEqualTo(20000);
//delete
repository.delete(member.getMemberId());
assertThatThrownBy(() -> repository.findById(member.getMemberId()))
.isInstanceOf(NoSuchElementException.class);
Thread.sleep(1000);
}
save를 제외한 다른 메서드들도 테스트에서 잘 실행된다.