[Spring DB] 1.JDBC

Updated:

JDBC

Client가 데이터를 저장하는 작업을 수행할 때 앱이나 웹에서 Application Server을 거쳐 DB에 접근한다. 이 과정에서 Application Server와 DB 사이의 연결에 대해 알아보자

배경지식

Application Server와 DB 간의 통신

jdbc1

  1. 커넥션 연결: 대부분 TCP/IP를 이용하여 연결
  2. SQL 전달: Application Server 측에서 작성한 SQL을 DB로 전달
  3. 결과 응답: DB 측에서 SQL을 실행한 결과를 Application Server로 전달

1. JDBC

과거에는 위 3가지 과정을 각각의 DB마다 다른 방식으로 수행했기 때문에 DB가 변경되면 Application Server의 코드들도 변경할 수 밖에 없었다…

이 문제를 해결하기 위해 등장한 것이 JDBC 표준 인터페이스이다. Java Database Connectivity로 Java에서 DB에 접속할 수 있도록 도와주는 API이다

jdbc2

JDBC 표준 인터페이스는 하위 3가지 기능을 정의한다

  1. java.sql.Connection: 커넥션 연결을 담당
  2. java.sql.Statement: SQL을 담고있음
  3. java.sql.ResultSet: SQL 요청에 대한 응답을 담고있음

=> JDBC 표준 인터페이스를 MySQL, Oracle 등 각각의 DB에 맞게 구현한 것을 JDBC 드라이버라고 부른다. Application 로직은 JDBC 표준 인터페이스(추상화)에 의존하기 때문에 다른 종류의 DB를 사용하고 싶다면 JDBC 드라이버만 바꿔주면 된다

2. 최신 데이터 접근 기술: SQL Mapper, ORM

최근에는 JDBC를 직접 사용하기 보다는 JDBC를 편리하게 사용할 수 있도록 도와주는 SQL Mapper와 ORM을 사용한다. 이 2가지 각각의 기술을 통해 손쉽게 SQL을 JDBC로 전달한다

2-1. SQL Mapper

jdbc3

  • SQL 응답 결과를 객체로 편리하게 변환해주고 JDBC의 반복코드를 줄여준다는 장점
  • 개발자가 SQL을 직접 작성해야하는 단점
  • eg) JdbcTemplate, MyBatis

2-2. ORM

jdbc4

  • 객체를 RDB의 테이블과 매핑해주는 기술로 개발자가 직접 SQL을 작성할 필요가 없이 JPA가 사용하는 DB에 따라 SQL문을 작성하여 실행한다
  • Java 진영 ORM 표준 인터페이스가 JPA이고 대부분 이를 구현한 라이브러리로 Hibernate를 사용한다
  • eg) JPA

3. 순수 JDBC 개발하기

3-1. Connection 연결

Connection connection = DriverManager.getConnection("jdbc:h2:tcp://localhost/~/test", "sa", "");

jdbc5

  1. Application 로직에서 DB와 연결이 필요하다면 DriverManager.getConnection()을 호출한다
  2. DriverManager는 라이브러리로 등록된 드라이버 목록 모두에게 연결 요청한다
    • DriverManager.getConnection()의 url 파라미터 값을 통해 사용할 수 있는 드라이버가 결정된다
    • url 파라미터 값 앞부분이 jdbc:h2이면 H2 드라이버를 사용하는 식이다
  3. 해당 연결을 처리할 수 있는 드라이버는 connection을 Application 로직에 반환한다

3-2. Statement에 SQL 담아 ResultSet으로 응답받기

public Member findById(String memberId) throws SQLException {
        String sql = "select * from member where member_id = ?";

        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        try {
            conn = DriverManager.getConnection("jdbc:h2:tcp://localhost/~/test", "sa", "");
            pstmt = conn.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("No Member for Id:" + memberId);
            }
        } catch (SQLException e) {
            log.error("DB error", e);
            throw e;
        } finally {
            if (pstmt != null) {
                try {
                    pstmt.close();
                } catch (SQLException e) {
                    log.info("error", e);
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    log.info("error", e);
                }
            }
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    log.info("error", e);
                }
            }
        }
    }
  • SQL을 담기 위한 Statement로 PreparedStatement를 사용한다
  • DB에서 해당 SQL을 실행한 결과를 담는 ResultSet을 사용한다
    • ResultSet의 최초 커서는 데이터를 가르키고 있지 않으므로 rs.next()를 적어도 최초 한번 호출해야 한다
    • rs.next()의 반환값이 true이면 데이터가 있다는 뜻이고, false이면 없다는 뜻이다
    • 반환값을 여러개로 예상하는 경우에는 while(rs.next())를 이용한다
  • finally문에서 Connection, Statement, ResultSet을 종료시킬때 에러가 발생해도 서로에게 영향을 주지 않도록 로직을 작성한다

Leave a comment