본문 바로가기
공부방

[TIL 06/27] JDBC

by hseong 2023. 6. 30.

1. JDBC

JDBC 란?

JDBC(Java Database Connectivity)는 자바에서 데이터베이스에 접속할 수 있도록 하는 자바 API이다. 데이터베이스에서 자료를 쿼리하거나 업데이트하는 방법을 제공한다.

  • DB 연결, SQL과 과련된 작업을 수행할 수 있는 표준 인터페이스이다.
  • 이를 이용하면 DB 종류에 상관없이 SQL문을 실행하고 처리할 수 있다.
  • 자바 애플리케이션에서 영속성 계층을 처리하기 위해 JDK 1.1에 포함되어 출시한 컴포넌트이다.

아키텍처

JDBC 인터페이스는 JDBC API와 JDBC 드라이버 두 개의 계층으로 나뉜다. 백엔드 엔지니어들은 JDBC API를 이용해서 드라이버와 커넥션을 맺고 쿼리를 날린다.
JDBC API는 다음과 같은 기능을 제공한다.

  • Connection - 연결
  • Statement - SQL 전달
  • ResultSet - 결과 응답

그리고 각각의 DB 벤더사들은 자사의 DB에 맞도록 JDBC 드라이버를 개발해서 배포한다. 대표적으로 MySQL의 경우 MySQL Connector/J 라는 JDBC 드라이버를 배포한다.

2. JDBC 사용

데이터베이스에 연결하려면 JDBC가 제공하는 DriverManager.getConnection()을 사용하면 된다.
DriverManager는 라이브러이에 등록된 드라이버 목록을 자동으로 인식하여 커넥션을 획득할 수 있는지 확인한다.

public static void main(String[] args) {
    String sql = "select * from voucher";

    Connection con = null;
    PreparedStatement pstmt = null;
    ResultSet rs = null;
    try {
        con = DriverManager.getConnection("jdbc:h2:tcp//localhost/~/test", "sa", "");
        pstmt = con.prepareStatement(sql);
        rs = pstmt.executeQuery();
        while(rs.next()) {
            String name = rs.getString("name");
            UUID customerId = UUID.fromString(rs.getString("customer_id"));
        }
    } catch (SQLException e) {
        log.error("DB error", e);
    } finally {
    try {
        if(rs != null) {
            rs.close();
        }
        if (pstmt != null) {
            pstmt.close();
        }
        if (con != null) {
            con.close();
        }
    } catch (SQLException e) {
        log.error("Got error while closing connection", e);
    }
    }
}

Connection을 이용해 PreparedStatement를 만들고 executeQuery()를 통해 쿼리를 실행한다. select 쿼리의 결과는 ResultSet에 순서대로 들어간다.
ResultSet은 내부에 커서를 가지고 있으며 최초의 커서는 데이터를 가리키고 있지 않기 때문에 rs.next()를 호출해야 데이터를 조회할 수 있다.
데이터에 대한 조회는 rs.getxxx("필드 이름")을 통해 현재 커서가 가리키고 있는 row의 데이터를 해당하는 타입(xxx)으로 반환한다.
작업이 모두 수행되었으면 finally를 통해서 사용한 리소스를 정리해주어야 한다.

  • 리소스를 정리하는 부분은 try-with-resource를 사용하여 간결하게 작성할 수 있다.
  • PreparedStatement?를 통한 파라미터 바인딩을 가능하게 해준다. Statement를 통해 rawQuery를 그래도 전달하게 되는 경우 SQL Injection을 통해 공격자에게 데이터가 노출될 수 있다.
  • Statement를 사용하면 매번 쿼리의 문장을 분석 -> 컴파일 -> 실행 3단계를 거친다. PreparedStatement는 처음 한 번만 3단계를 거치고 캐시에 담아서 재사용한다. 보안성은 물론 성능상에도 이점을 가진다.

3. CRUD

앞서 조회를 수행했으니 이번엔 삽입, 수정, 삭제를 수행해본다.

Insert

public Customer save(Customer customer) {
    try(
            Connection con = DriverManager.getConnection("jdbc:h2:tcp://localhost/~/test", "sa", "");
            PreparedStatement pstmt = con.prepareStatement("insert into customers(customer_id, name) values (?, ?)")
    ) {
        pstmt.setString(1, customer.getCustomerId().toString());
        pstmt.setString(2, customer.getName());

        pstmt.executeUpdate();
        return customer;
    } catch (SQLException e) {
        log.error("DB error", e);
        throw new RuntimeException(e);
    }
}

Update

public void update(Customer customer) {
    String sql = "update customers set name = ? where customer_id = ?";

    try (
            Connection con = DriverManager.getConnection("jdbc:h2:tcp://localhost/~/test", "sa", "");
            PreparedStatement pstmt = con.prepareStatement(sql)
    ) {
        pstmt.setString(1, customer.getName());
        pstmt.setString(2, customer.getCustomerId().toString());
        int executeUpdate = pstmt.executeUpdate();
        log.info("updatedSize={}", executeUpdate);
    } catch (SQLException e) {
        log.error("DB error", e);
        throw new RuntimeException(e)
    }
}

Delete

public void delete(UUID customerId) {
    String sql = "delete from customers where customer_id = ?";

    try (
            Connection con = DriverManager.getConnection("jdbc:h2:tcp://localhost/~/test", "sa", "");
            PreparedStatement pstmt = con.prepareStatement(sql)
    ) {
        pstmt.setString(1, customerId.toString());
        pstmt.executeUpdate();
    } catch (SQLException e) {
        log.error("DB error", e);
        throw new RuntimeException(e)
    }
}