[Spring DB 1편] 1. JDBC

HJ·2023년 1월 24일
0

Spring DB 1편

목록 보기
1/7
post-thumbnail

김영한 님의 스프링 DB 1편 - 데이터 접근 핵심 원리 강의를 보고 작성한 내용입니다.
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-1/dashboard


1. JDBC 등장 배경

1-1. 클라이언트가 DB를 사용하는 방법

  • 앱이나 웹 브라우저는 직접 DB에 연결되진 않고, 어플리케이션 서버에 요청을 보내면 어플리케이션 서버가 필요한 비지니스 로직을 수행하고 필요한 데이터를 DB에 저장

1-2. 어플리케이션 서버와 DB

  • 어플리케이션 서버가 DB를 사용하는 과정

    • 커넥션 연결 : 주로 TCP/IP를 사용해서 커넥션 연결을 수행

    • SQL 전달 : 어플리케이션 서버는 DB가 이해할 수 있는 SQL을 연결된 커넥션을 통해 DB에 전달

    • 결과 응답 : DB는 전달된 SQL을 수행하고 그 결과를 응답, 어플리케이션 서버는 응답 결과를 활용

  • BUT> DB마다 커넥션을 연결하는 방법, SQL을 전달하는 방법, 결과를 응답 받는 방법이 모두 다르다는 문제점이 존재

    • DB를 변경하면 어플리케이션 서버에 작성된 DB 사용 코드도 함께 변경해야한다

    • 개발자가 각 DB마다 커넥션 연결, SQL 전달, 결과 응답 받는 방법을 학습해야한다

  • ➡️이런 문제를 해결하기 위해 JDBC 등장


1-3. JDBC 표준 인터페이스

  • JDBC( Java Database Connectivity )는 자바에서 DB에 접속할 수 있도록 하는 자바 API

  • JDBC는 DB에서 자료를 쿼리하거나 업데이트하는 방법을 제공

  • JDBC 표준 인터페이스

    • java.sql.Connection - 연결

    • java.sql.Statement - SQL을 담은 내용

    • java.sql.ResultSet - SQL 요청 응답

  • JDBC 인터페이스를 각각의 DB회사에서 자신의 DB에 맞도록 구현해서 라이브러리로 제공하는 것이 JDBC 드라이버

  • 즉, JDBC 드라이버는 JDBC 표준 인터페이스를 구현해서 만든 드라이버


1-4. JDBC 드라이버

  • 어떤 DB의 드라이버를 사용하는지에 관계 없이 개발자는 어플리케이션 로직을 작성할 때 JDBC 표준 인터페이스에만 맞춰서 개발하면 된다

  • DB가 바뀌어도 어플리케이션 로직은 JDBC 표준 인터페이스를 그대로 사용하고 드라이버
    ( 구현체 )만 변경하면 된다


1-5. JDBC의 등장으로 해결된 문제

  1. DB를 변경했을 때 어플리케이션 서버의 DB 사용 코드를 함께 변경해야하는 문제

    • 어플리케이션 로직이 이제 JDBC 표준 인터페이스에만 의존하기 때문에 DB를 변경하고 싶으면 JDBC 구현 라이브러리만 변경하면 된다

    • 어플리케이션 서버의 사용 코드는 그대로 유지된다

  2. 각 DB의 커넥션 연결, SQL 전달, 결과를 응답 받는 방법을 학습해야하는 문제

    • JDBC 표준 인터페이스 사용법만 학습하면 많은 DB에 모두 동일하게 적용 가능

1-6. 참고

  • 각 DB마다 SQL, 데이터 타입 등의 일부 사용법이 다르고, 실무에서 기본으로 사용하는 페이징 SQL도 각 DB마다 사용법이 다르다

  • DB를 변경해도 JDBC 코드는 변경하지 않아도 되지만 SQL은 변화한 DB에 맞게 수정해야한다

  • JPA(Java Persistence API)를 사용하면 이렇게 각각의 데이터베이스마다 다른 SQL을 정의해야 하는 문제도 많은 부분 해결할 수 있다




2. JDBC와 최신 데이터 접근 기술

2-1. JDBC 직접 사용

  • JDBC를 직접 사용하는 방식

  • JDBC는 오래된 기술이기도 하고 사용하는 방법이 복잡하기 때문에 직접 JDBC를 사용하기 보다는 JDBC를 편리하게 사용할 수 있는 다양한 기술들을 활용

  • 대표적으로 SQL Mapper, ORM 기술로 나눌 수 있다


2-2. SQL Mapper

  • SQL Mapper는 SQL 응답 결과를 객체로 편리하게 변환해준다

  • JDBC의 반복 코드를 제거해준다

  • but> 개발자가 직접 SQL을 작성해야한다

  • 대표적으로 스프링 JDBC Template, MyBatis가 있다


2-3. ORM 기술

  • ORM은 객체를 관계형 데이터베이스 테이블과 매핑해주는 기술

  • 객체를 전달하면 구현체가 객체의 매핑 정보를 보고 쿼리를 직접 만들어낸다

  • 즉, 반복적인 SQL을 직접 작성하지 않는다

  • ORM 기술이 개발자 대신 SQL을 동적으로 만들어 실행해준다

  • 각각의 데이터베이스마다 다른 SQL을 사용하는 문제도 중간에서 해결해준다

  • 대표적으로 JPA, 하이버네이트, 이클립스링크가 있다

  • JPA는 자바 진영의 ORM 표준 인터페이스이고, 이것을 구현한 것으로 하이버네이트와 이클립스링크 등의 구현 기술이 있다




3. DB 연결

3-1. getConnection()

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);
    }
}
  • DriverManager.getConnection() : 라이브러리에 있는 DB 드라이버를 찾아 해당 드라이버가 제공하는 커넥션( Connection )을 반환해준다

  • 위의 코드는 H2 데이터베이스 드라이버가 작동해서 실제 DB와 커넥션을 맺고 결과를 반환해준다

  • Connection은 인터페이스이고 getConnection()을 통해 가져온 것은 구현체( Connection 구현체 )

    • 로그는 아래처럼 찍히는데 가져오는 구현체가 JdbcConnection라는 의미

    • class=class org.h2.jdbc.JdbcConnection

  • JdbcConnection는 H2 데이터베이스 드라이버가 제공하는 H2 전용 커넥션이고 JDBC 표준 커넥션 인터페이스인 java.sql.Connection 인터페이스를 구현하고 있다


3-2. 연결 이해

  • JDBC는 java.sql.Connection 표준 커넥션 인터페이스를 정의한다

  • H2 데이터베이스 드라이버는 JDBC Connectin 인터페이스를 구현한 org.h2.jdbc.JdbcConnection을 제공

  • JdbcConnection이 있어야 H2 데이터베이스와 통신이 가능하다


3-3. DriverManager 커넥션 요청 흐름

  • JDBC가 제공하는 DriverManager는 라이브러리에 등록된 DB 드라이버들을 관리하고, 커넥션을 획득하는 기능을 제공
  1. 어플리케이션 로직에서 커넥션이 필요하면 DriverManager.getConnection()을 호출

  2. DriverManager는 라이브러리에 등록된 드라이버 목록을 자동으로 인식하고 이 드라이버들에게 순서대로 정보를 넘겨서 커넥션을 획득할 수 있는지 확인

    • 이 때 넘기는 정보는 URL, 이름, 비밀번호 등 접속에 필요한 추가 정보
  3. 각각의 드라이버는 URL 정보를 체크해서 본인이 처리할 수 있는 요청인지 확인해서 처리할 수 있는 경우 실제 데이터베이스에 연결해서 커넥션을 획득하고 이 커넥션을 클라이언트에 반환, 본인이 처리할 수 없으면 처리할 수 없다는 결과를 반환하고 다음 드라이버에게 순서가 넘어간다

  4. 이렇게 찾아진 커넥션 구현체가 클라이언트에게 반환된다




4. JDBC 수업

  • 학교 데이터베이스시스템 수업에서 JDBC를 배울 때 공부하면서 보기 편하게 정리한 내용




5. JDBC - 등록

public class MemberRepositoryV0 {

    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.info("db error", e);
            throw e;
        } finally {
            close(con, pstmt, null);
        }

    }
}
  • getConnection() : 이전에 만든 DBConnectionUtil을 통해 데이터베이스 커넥션을 획득
  • Statement에 파라미터 바인딩 기능이 추가된 것이 PreparedStatement
  • con.prepareStatement(sql) : 데이터베이스에 전달할 SQL과 파라미터로 전달할 데이터들을 준비

    • SQL의 ? 자리에 파라미터를 넣기 위해 setXXX() 메서드를 이용하고, 인덱스는 1부터 시작한다
  • pstmt.executeUpdate()

    • PreparedStatement를 통해 준비된 SQL을 커넥션을 통해 데이터베이스에 전달하면 실제로 쿼리가 수행된다

    • 위 메서드는 int를 반환하는데 영향 받은 DB row의 수를 의미

  • Connection과 PreparedStatement를 획득한 역순으로 닫아 주어야한다

    • 실제 TCP/IP를 커넥션을 이용해 외부 리소스를 사용하는 것이기 때문에 닫지 않으면 커넥션이 끊어지지 않고 계속 유지되기 때문

    • try 쪽에서 예외가 발생해도 리소스 정리를 실행하기 위해 finally에 작성

    • finally에서 리소스를 정리해야하기 때문에 미리 Connection con = null; 처럼 선언을 해두어야 한다

  • finally에서 바로 Connection, PreparedStatement 를 닫지 않은 이유

    • finally에서 pstmt를 닫는 중에 예외가 발생하면 Connection을 닫을 수 없다

    • 예외가 발생해도 다른 리소스를 정상적으로 닫을 수 있도록 각 close() 역시 try, catch로 묶어서 닫아야한다

    • Connection, PreparedStatement, ResultSet을 각각 try, catch로 묶어서 닫기 위한 메서드가 close()




6. JDBC - 조회

6-1. 코드

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("memberId"));
            member.setMoney(rs.getInt("money"));
            return member;
        } else{
            throw new NoSuchElementException("member not found memberId = " + memberId);
        }
    }
    ...
}
  • catch, finally는 이전과 동일해서 생략

  • rs = pstmt.executeQuery()

    • 데이터를 변경할 때는 executeUpdate() 를 사용하지만, 데이터를 조회할 때는 executeQuery() 를 사용

    • executeQuery() 는 결과를 ResultSet 에 담아서 반환


6-2. ResultSet

  • ResultSet은 select 쿼리의 결과가 순서대로 들어간다

  • ResultSet 내부에 있는 커서( cursor )를 이동해서 다음 데이터를 조회할 수 있다

  • rs.next()

    • 커서가 다음으로 이동

    • 커서가 이동했을 때 데이터가 있으면 true, 없으면 false를 반환

    • 최초의 커서는 데이터를 가리키고 있지 않기 때문에 rs.next()를 최초 한번은 호출해야 데이터를 조회할 수 있다

  • getXXX() : 커서가 가리키고 있는 위치의 데이터를 XXX 타입으로 반환




7. 삭제 테스트

repository.delete(member.getMemberId());
assertThatThrownBy(() -> repository.findById(member.getMemberId()))
        .isInstanceOf(NoSuchElementException.class);
  • 삭제를 진행한 후 제대로 삭제되었는지 확인하는 테스트

  • 조회 메서드에서 조회되지 않았을 때 NoSuchElementException 예외를 던지는 것을 활용

  • assertThatThrownBy를 통해 삭제된 멤버를 조회했을 때 NoSuchElementException이 발생하면 정상적으로 지워진 것으로 판단

0개의 댓글