스프링/스프링 기초DB

DB - 커넥션풀과 DataSource

chanhee01 2023. 3. 14. 16:54

우리가 지금까지 사용했던 서버들은 창을 닫으면 데이터가 날라간다. 그렇기 때문에 데이터베이스를 만들어서 데이터를 저장할 수 있는 공간을 따로 설정해야한다.

 

 

  • 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를 제외한 다른 메서드들도 테스트에서 잘 실행된다.