ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [김영한 스프링] 05. JDBC 이해 - JDBC 개발 조회, 수정, 삭제
    Spring/스프링 DB 1편 - 데이터 접근 핵심 원리 2023. 10. 18. 00:27

    JDBC 개발 - 조회

     

    MemberRepositoryV0 - 회원 조회 추가

    package hello.jdbc.repository;
    
    import hello.jdbc.connection.DBConnectionUtil;
    import hello.jdbc.domain.Member;
    import lombok.extern.slf4j.Slf4j;
    
    import java.sql.*;
    import java.util.NoSuchElementException;
    
    /**
     * JDBC - DriverManager 사용
     */
    @Slf4j
    public class MemberRepositoryV0 {
    
        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()) {
                    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;
            }
        }
    
        public Member save(Member member) throws SQLException {
            String sql = "insert into member(member_id, money) values (?, ?)";
    
            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 e;
            } finally {
                close(con, pstmt, null);
            }
        }
    
        private void close(Connection con, Statement stmt, ResultSet rs) {
    
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    log.error("error", e);
                }
            }
    
            if (stmt != null) {
                try {
                    stmt.close();
                } catch (SQLException e) {
                    log.error("error", e);
                }
            }
    
            if (con != null) {
                try {
                    con.close();
                } catch (SQLException e) {
                    log.error("error", e);
                }
            }
        }
    
        private Connection getConnection() {
            return DBConnectionUtil.getConnection();
        }
    }

    findById() - 쿼리 실행

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

     

    executeQuery()

    ResultSet executeQuery() throws SQLException;

     

    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 결과 예시

    참고로 이 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 = ?

     

     

    MemberRepositoryV0Test - 회원 조회 추가

    package hello.jdbc.repository;
    
    import hello.jdbc.domain.Member;
    import lombok.extern.slf4j.Slf4j;
    import org.assertj.core.api.Assertions;
    import org.junit.jupiter.api.Test;
    
    import java.sql.SQLException;
    
    import static org.assertj.core.api.Assertions.*;
    import static org.junit.jupiter.api.Assertions.*;
    
    @Slf4j
    class MemberRepositoryV0Test {
    
        MemberRepositoryV0 repository = new MemberRepositoryV0();
    
        @Test
        void crud() throws SQLException {
            // 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 객체의 참조 값이 아니라 실제 데이터가 보이는 이유는 롬복의 @Data가 toString()을 적절히 오버라이딩 해서 보여주기 때문이다.
    • isEqualTo() : findMember.equals(member)를 비교한다. 결과가 참인 이유는 롬복의 @Data는 해당 객체의 모든 필드를 사용하도록 equals()를 오버라이딩 하기 때문이다.

     

    참고
    이 테스트는 2번 실행하면 PK 중복 오류가 발생한다. 이 경우 delete from member 쿼리로 데이터를 삭제한 다음에 다시 실행하자.

     

     

    JDBC 개발 - 수정, 삭제

     

    MemberRepositoryV0 - 회원 수정 추가

    package hello.jdbc.repository;
    
    import hello.jdbc.connection.DBConnectionUtil;
    import hello.jdbc.domain.Member;
    import lombok.extern.slf4j.Slf4j;
    
    import java.sql.*;
    import java.util.NoSuchElementException;
    
    /**
     * JDBC - DriverManager 사용
     */
    @Slf4j
    public class MemberRepositoryV0 {
    
        public void update (String memberId, int money) throws SQLException {
            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 e;
            } finally {
                close(con, pstmt, null);
            }
        }
    
        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()) {
                    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;
            }
        }
    
        public Member save(Member member) throws SQLException {
            String sql = "insert into member(member_id, money) values (?, ?)";
    
            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 e;
            } finally {
                close(con, pstmt, null);
            }
        }
    
        private void close(Connection con, Statement stmt, ResultSet rs) {
    
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    log.error("error", e);
                }
            }
    
            if (stmt != null) {
                try {
                    stmt.close();
                } catch (SQLException e) {
                    log.error("error", e);
                }
            }
    
            if (con != null) {
                try {
                    con.close();
                } catch (SQLException e) {
                    log.error("error", e);
                }
            }
        }
    
        private Connection getConnection() {
            return DBConnectionUtil.getConnection();
        }
    }

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

     

     

    MemberRepositoryV0Test - 회원 수정 추가

    package hello.jdbc.repository;
    
    import hello.jdbc.domain.Member;
    import lombok.extern.slf4j.Slf4j;
    import org.junit.jupiter.api.Test;
    
    import java.sql.SQLException;
    
    import static org.assertj.core.api.Assertions.*;
    
    @Slf4j
    class MemberRepositoryV0Test {
    
        MemberRepositoryV0 repository = new MemberRepositoryV0();
    
        @Test
        void crud() throws SQLException {
            // 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);
    
            // update : money 10000 -> 20000
            repository.update(member.getMemberId(), 20000);
            Member updatedMember = repository.findById(member.getMemberId());
            assertThat(updatedMember.getMoney()).isEqualTo(20000);
        }
    }

    회원 데이터의 money를 10000 -> 20000으로 수정하고, DB에서 데이터를 다시 조회해서 20000으로 변경되었는지 검증한다.

     

     

    로그

    MemberRepositoryV0 - resultSize=1

    pstmt.executeUpdate()의 결과가 1인 것을 확인할 수 있다. 이것은 해당 SQL에 영향을 받은 로우 수가 1개라는 뜻이다.

     

     

    결과

     

     

    MemberRepositoryV0 - 회원 삭제 추가

    package hello.jdbc.repository;
    
    import hello.jdbc.connection.DBConnectionUtil;
    import hello.jdbc.domain.Member;
    import lombok.extern.slf4j.Slf4j;
    
    import java.sql.*;
    import java.util.NoSuchElementException;
    
    /**
     * JDBC - DriverManager 사용
     */
    @Slf4j
    public class MemberRepositoryV0 {
    
        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);
            }
        }
    
        public void update (String memberId, int money) throws SQLException {
            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 e;
            } finally {
                close(con, pstmt, null);
            }
        }
    
        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()) {
                    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;
            }
        }
    
        public Member save(Member member) throws SQLException {
            String sql = "insert into member(member_id, money) values (?, ?)";
    
            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 e;
            } finally {
                close(con, pstmt, null);
            }
        }
    
        private void close(Connection con, Statement stmt, ResultSet rs) {
    
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    log.error("error", e);
                }
            }
    
            if (stmt != null) {
                try {
                    stmt.close();
                } catch (SQLException e) {
                    log.error("error", e);
                }
            }
    
            if (con != null) {
                try {
                    con.close();
                } catch (SQLException e) {
                    log.error("error", e);
                }
            }
        }
    
        private Connection getConnection() {
            return DBConnectionUtil.getConnection();
        }
    }

     

     

    MemberRepositoryV0Test - 회원 삭제 추가

    package hello.jdbc.repository;
    
    import hello.jdbc.domain.Member;
    import lombok.extern.slf4j.Slf4j;
    import org.junit.jupiter.api.Test;
    
    import java.sql.SQLException;
    
    import static org.assertj.core.api.Assertions.*;
    
    @Slf4j
    class MemberRepositoryV0Test {
    
        MemberRepositoryV0 repository = new MemberRepositoryV0();
    
        @Test
        void crud() throws SQLException {
            // 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);
    
            // 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());
    
        }
    }

     

     

    로그

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

     

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

     

     

    출처 :  https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-1

     

    스프링 DB 1편 - 데이터 접근 핵심 원리 - 인프런 | 강의

    백엔드 개발에 필요한 DB 데이터 접근 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 DB 접근 기술의 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., 백엔

    www.inflearn.com

Designed by Tistory.