4 분 소요

 인프런 스프링 DB 1편 - 데이터 접근 핵심 원리편을 학습하고 정리한 내용 입니다.

JDBC 이해 - 2에 저장까지 했으니, 조회 삭제를 구현해보자.

JDBC 개발 - 조회

MemberRepositoryV0 - 회원 조회 추가

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public Member findById(String memberId) throws Exception {  
    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()) {  
            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 (Exception e) {  
        log.error("db error", e);  
        throw e;  
    } finally {  
        close(con, pstmt, rs);  
    }  
}

findById() - 쿼리 실행

  • sql : 데이터 조회를 위한 select SQL을 준비한다.
  • rs = pstmt.executeQuery()데이터를 변경할 때는 executeUpdate()를 사용하지만, 데이터를 조회할 때는 executeQuery()를 사용한다. executeQuery()는 결과를 ResultSet에 담아서 반환한다.

MemberRepositoryV0Test - 회원 조회 추가

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Slf4j  
class MemberRepositoryV0Test {  
  
    MemberRepositoryV0 repository = new MemberRepositoryV0();  
  
    @Test  
    void crud() throws Exception {  
        //save  
        Member member = new Member("memberV0", 10000);  
        repository.save(member);  
  
        //findById  
        Member findMember = repository.findById(member.getMemberId());  
        log.info("findMember = {}", findMember);  
  
        assertThat(findMember).isEqualTo(member);  
    }  
}

다음과 같이 결과가 저장한 멤버와 그걸로 찾은 멤버가 같다고 나온다.

  • 회원을 등록하고 그 결과를 바로 조회해서 확인해보았다.
  • 참고로 실행 결과에 member객체의 참조 값이 아니라 실제 데이터가 보이는 이유는 롬복의 @DatatoString()을 적절히 오버라이딩 해서 보여주기 때문이다.
  • isEqualTo() : findMember.equals(member)를 비교한다. 결과가 참인 이유는 롬복의 @Data는 해당 객체의 모든 필드를 사용하도록 equals()를 오버라이딩 하기 때문이다.
    • 값과 인스턴스 비교
  • 참고로 이 테스트 코드는 2번 실행하면 오류난다. 같은 멤버ID가 계속 들어가기 때문에..

ResultSet

  • ResultSet은 다음과 같이 생긴 데이터 구조이다. 보통 select 쿼리의 결과가 순서대로 들어간다.
    • 예를 들어서 select member_id, money라고 지정하면 member_id, money라는 이름으로 데이터 가 저장된다.
    • 참고로 select * 을 사용하면 테이블의 모든 컬럼을 다 지정한다.
  • ResultSet 내부에 있는 커서(cursor)를 이동해서 다음 데이터를 조회할 수 있다.
  • rs.next() : 이것을 호출하면 커서가 다음으로 이동한다. 참고로 최초의 커서는 데이터를 가리키고 있지 않기 때문에 rs.next() 를 최초 한번은 호출해야 데이터를 조회할 수 있다.
    • rs.next()의 결과가 true면 커서의 이동 결과 데이터가 있다는 뜻이다.
    • rs.next()의 결과가 false면 더이상 커서가 가리키는 데이터가 없다는 뜻이다.
  • rs.getString("member_id") : 현재 커서가 가리키고 있는 위치의 member_id데이터를 String 타입 으로 반환한다.
  • rs.getInt("money") : 현재 커서가 가리키고 있는 위치의 money데이터를 int 타입으로 반환한다.

참고로 이 ResultSet의 결과 예시는 회원이 2명 조회되는 경우이다.

  • 1-1에서 rs.next()를 호출한다.
  • 1-2의 결과로 cursor가 다음으로 이동한다. 이 경우 cursor가 가리키는 데이터가 있으므로 true를 반환한다.
  • 2-1에서 rs.next()를 호출한다.
  • 2-2의 결과로 cursor가 다음으로 이동한다. 이 경우 cursor가 가리키는 데이터가 있으므로 true를 반환한다.
  • 3-1에서 rs.next()를 호출한다.
  • 3-2의 결과로 cursor가 다음으로 이동한다. 이 경우 cursor가 가리키는 데이터가 없으므로 false를 반환한다.

findById()에서는 회원 하나를 조회하는 것이 목적이다. 따라서 조회 결과가 항상 1건이므로 while대신에 if를 사용한다. 다음 SQL을 보면 PK인 member_id를 항상 지정하는 것을 확인할 수 있다.

SQL : select * from member where member_id = ?

JDBC 개발 - 수정, 삭제

수정과 삭제는 등록과 비슷하다. 등록, 수정, 삭제처럼 데이터를 변경하는 쿼리는 executeUpdate()를 사용하면 된다.

MemberRepositoryV0 - 회원 수정 추가

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void update(String memberId, int money) throws Exception {  
    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 (Exception e) {  
        log.error("db error", e);  
        throw e;  
    } finally {  
        close(con, pstmt, null);  
    }  
}

executeUpdate()는 쿼리를 실행하고 영향 받은 row수를 반환한다. 여기서는 하나의 데이터만 변경하기 때문에 결과로 1이 반환된다. 만약 회원이 100명이고, 모든 회원의 데이터를 한번에 수정하는 update sql을 실행하면 결과는 100이 된다.

MemberRepositoryV0Test - 회원 수정 추가

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test  
void crud() throws Exception {  
    //save  
    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);  
}

10000원을 20000원으로 바꿨다. 테스트도 잘 통과 된다.

db에도 잘 저장되었다.

MemberRepositoryV0 - 회원 삭제 추가

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void delete(String memberId) throws Exception {  
    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 (Exception e) {  
        log.error("db error", e);  
        throw e;  
    } finally {  
        close(con, pstmt, null);  
    }  
}

쿼리만 바뀌고 뭐 다른거 없다. memberId받아서 찾아서 삭제하는 것 뿐.

순수 JDBC를 쓰면 이런 반복이다.

MemberRepositoryV0Test - 회원 삭제 추가

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Slf4j  
class MemberRepositoryV0Test {  
  
    MemberRepositoryV0 repository = new MemberRepositoryV0();  
  
    @Test  
    void crud() throws Exception {  
        //save  
        Member member = new Member("memberV2", 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);  
  
    }  
}

마지막으로 삭제를 추가해서 이제 이 테스트는 여러번 돌려도 에러 나오지 않게 됐다.

로그를 남겨서 저리 나오지만, 테스트는 통과했다.

회원을 삭제한 다음 findById()를 통해서 조회한다. 회원이 없기 때문에 NoSuchElementException이 발생한다. assertThatThrownBy는 해당 예외가 발생해야 검증에 성공한다.

참고
마지막에 회원을 삭제하기 때문에 테스트가 정상 수행되면, 이제부터는 같은 테스트를 반복해서 실행할 수 있다. 물론 테스트 중간에 오류가 발생해서 삭제 로직을 수행할 수 없다면 테스트를 반복해서 실행할 수 없다.
트랜잭션을 활용하면 이 문제를 깔끔하게 해결할 수 있다.

태그: ,

카테고리:

업데이트:

댓글남기기