GitHub

https://github.com/Choidongjun0830

Spring

JDBC

gogi masidda 2024. 1. 31. 17:32

클라이언트가 애플리케이션 서버를 통해 데이터를 저장하거나 조회하면, 애플리케이션 서버는 데이터베이스를 사용한다.

  1. 커넥션 연결
  2. SQL 전달: 애플리케이션 서버는 DB가 이해할 수 있는 SQL을 연결된 커넥션으로 전달
  3. 결과 응답: SQL을 수행하고 결과 응답.

JDBC를 직접 코딩할 일은 웬만하면 없다. Sql Mapper나 ORM 기술을 사용하기 때문에

하지만 JDBC를 이해할 필요는 있다.

@Slf4j
public class DBConnectionUtil {

    public static Connection getConnection() {
        try {
            Connection connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
            //라이브러리에 있는 데이터베이스 드라이버를 찾아서 해당 드라이버가 제공해주는 커넥션 반환
            log.info("get connection = {}, class = {}", connection, connection.getClass()); //객체 정보와 타입 정보
            return connection;
        } catch (SQLException e) {
            throw new IllegalStateException(e);
        }
    }
}
/**
 * JDBC - Driver Manager 사용
 */
@Slf4j
public class MemberRepositoryV0 {

    Member save(Member member) throws SQLException {
        String sql = "insert into member(member_id, money) values (?, ?)";

        Connection con = null;
        PreparedStatement pstmt = null; //PreparedStatement는 파라미터 할당 기능이 추가된 것.

        try {
            con = getConnection();
            pstmt = con.prepareStatement(sql);
            pstmt.setString(1, member.getMemberId());
            pstmt.setInt(2, member.getMoney());
            pstmt.executeUpdate(); //데이터를 변경할 때. 위에서 설정한게 실행됨. 영향받은 row 수만큼 반환
            return member;
        } catch (SQLException e) {
            log.error("DB error", e);
            throw e;
        } finally {
            close(con, pstmt, null); //finally에서 close해야 항상 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 = DBConnectionUtil.getConnection();
            pstmt = con.prepareStatement(sql);
            pstmt.setString(1, memberId);

            rs = pstmt.executeQuery(); //select할 때는 executeQuery. 결과를 ResultSet에 담아서 반환
            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);
        }
    }

    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 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);
        }
    }

    private void close(Connection con, Statement stmt, ResultSet rs) {
        //ResultSet은 결과를 조회할 때(select문)에서 사용
        if(rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                log.info("error ", e);
            }
        }

        if(stmt != null) {
            try {
                stmt.close();
            } catch (SQLException e) {
                log.info("error ", e);
            }
        }

        if(con != null) {
            try {
                con.close();
            } catch (SQLException e) {
                log.info("error ", e);

            }
        }

        //시작과 역순으로 닫아주기
        //이렇게 각각을 try/catch로 해야 서로에게 영향을 주지 않음.
        //하나에서 exception이 터져도 다른 하나가 close가 안되는 일이 생기지 않음.
    }

    private static Connection getConnection() {
        return DBConnectionUtil.getConnection();
    }
}

 

Test

@Slf4j
class MemberRepositoryV0Test {

    MemberRepositoryV0 repository = new MemberRepositoryV0();

    @Test
    void crud() throws SQLException {
        //save
        Member member = new Member("memberV40", 10000);
        repository.save(member);

        //findById
        Member findMember = repository.findById(member.getMemberId());
        log.info("findMember = {}", findMember);
        log.info("member == findMember = {}", member == findMember); //동일성(identity) 두 객체의 주소값이 같은지
        log.info("member equals findMember = {}", member.equals(findMember)); //동등성(equality) 두 객체의 내용이 같은지
        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());
        Assertions.assertThatThrownBy(()-> repository.findById(member.getMemberId()))
                .isInstanceOf(NoSuchElementException.class);

    }
}

마지막에 회원을 삭제하므로 memberId를 바꾸지 않아도 같은 테스트를 반복해서 수행할 수 있다.

하지만 테스트 중간에 오류가 발생하면 삭제가 되지 않아 반복해서 수행할 수 없게 된다.

이 문제는 트랜잭션을 활용하면 해결할 수 있다.

 

728x90

'Spring' 카테고리의 다른 글

트랜잭션  (3) 2024.02.08
커넥션 풀  (0) 2024.02.01
Spring Toy Project-1  (3) 2024.01.31
스프링 타입 컨버터  (1) 2024.01.15
API 예외 처리  (0) 2024.01.13