[Spring] JDBC / CRUD

Manx·2022년 7월 4일
0

spring

목록 보기
19/24

인프런 '스프링 DB 1편' - 김영한님의 강의 내용입니다.

JDBC란

JDBC(Java Database Connectivity) : 자바에서 데이터베이스에 접속할 수 있도록 하는 자바 API다.
각 데이터베이스마다 애플리케이션 서버와 연결하고, SQL을 주고받는 방식이 다르기 때문에 DB 마다 적용하기가 매우 번거롭다. 그래서 JDBC라는 표준 인터페이스를 DB마다 JDBC Driver를 통해 동일한 방법으로 접근할 수 있도록 제공해준다.

  • java.sql.Connection : 연결
  • java.sql.Statement : SQL을 담은 내용
  • java.sql.ResultSet : SQL 요청 응답

SQL Mapper

JDBC를 편리하게 사용하도록 도와준다. 그러나 개발자가 직접 SQL을 작성해야한다.
SQL을 작성해야 한다는 단점이 있지만, 비교적 쉽게 개발할 수 있다.
ex) Spring JDBC Template, MyBatis

ORM

객체를 관계형 데이터베이스 테이블과 매핑해주는 기술.
개발자가 직접 SQL을 작성하지 않아도 되지만, 난이도가 높다.
ex) JPA, 하이버네이트

모든 기술의 밑바닥은 JDBC이다.

JDBC CRUD

H2 DB를 활용했다.

연결

package hello.jdbc.connection;

public abstract class ConnectionConst {
    public static final String URL = "jdbc:h2:tcp://localhost/~/test";
    public static final String USERNAME = "sa";
    public static final String PASSWORD = "";
}

저장

  • getConnection() : DB와 연걸

  • prepareStatemenst(sql) : DB에 쿼리를 날림

    • '?' 를 통한 파라미터 바인딩을 통해 sql injection 공격을 예방할 수 있다.
      예를 들어 바인딩이 아닌 그냥 문자로 사용할 경우 select 문을 날려 DB를 외부에서 쉽게 볼 수 있다.
  • close() : DB와 통신을 TCP/IP 방식으로 통신하기 때문에 사용하지 않을 경우에는 꼭 반납을 해주어야 한다.

    • Connection, PreparedStatement 둘 다 반납을 해야 하는데, Connection.close()에서 Exception이 터지면 PreparedStatement.close()가 실행되지 않을 수 있기 때문에 별도로 처리해야 한다.
  • pstmt.executeUpdate(); : 쿼리 실행에 영향을 받은 row 수를 리턴한다.

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

        Connection con = null;
        PreparedStatement pstmt = null;

        try {
        	// DB 연결
            con = DBConnectionUtil.getConnection();
            //sql injection 공격을 예방하기 위해 '?'를 통한 파라미터 바인딩을 가능하게 한다.
            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 {
            // TCP/IP 로 계속해서 통신하기 때문에 꼭 닫아줘야 한다.
            close(con, pstmt, null);
        }

    }
    
private void close(Connection con, Statement stmt, ResultSet rs) {
// pstmt.close()에서 Exception 발생하면 con.close() 실행이 안되기 때문에 예외 처리를 해줘야 한다.

        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.info("error", e);
            }
        }
    }

조회

저장과 비슷한 방법이지만, DB를 Update를 하는 것이 아닌 SQL만 실행하는 것 이므로 pstmt.executeQuery()를 실행함.
executeQuery()를 통해 ResultSet에 쿼리를 실행한 결과가 담긴다.
ResultSet 내부에서 cursor로 데이터를 조회하는데, 최초의 커서는 데이터를 가리키고 있지 않기 때문에 rs.next()를 최초 한번은 호출해야 데이터를 조회할 수 있다.
만약 다음 데이터가 없으면 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;
        } finally {
            close(con, pstmt, rs);
        }
    }

업데이트와 삭제는 비슷하니 생략하고 테스트코드만 업로드할 것이다.

TestCode

findMember.isEqualTo(member)가 True가 되는 이유는 lombok에서 제공해주는 @Data 어노테이션에서 기본적으로 Equals, HashCode를 오버라이딩 해주기 때문이다.

Assertions.assertThatThrownBy()를 통해 delete 된 값을 조회할 경우 NoSuchElementException이 터지는 것으로 테스트할 수 있다.

@Slf4j
class MemberRepositoryV0Test {

    MemberRepositoryV0 repository = new MemberRepositoryV0();

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

        //findById
        Member findMember = repository.findById(member.getMemberId());
        log.info("findMember={}", findMember);
        Assertions.assertThat(findMember).isEqualTo(member);

        //update : money: 10000 -> 20000
        repository.update(member.getMemberId(), 20000);
        Member updateMember = repository.findById(member.getMemberId());
        Assertions.assertThat(updateMember.getMoney()).isEqualTo(20000);

        //delete
        repository.delete(member.getMemberId());
        Assertions.assertThatThrownBy(() -> repository.findById(member.getMemberId()))
                .isInstanceOf(NoSuchElementException.class);
    }
}

CRUD하면서 try catch 문에 비슷한 코드들이 계속 중복되는데 앞으로 이것들을 해결하기 위한 기술들을 배우며 공부할 예정이다.

profile
백엔드 개발자

0개의 댓글