[Spring DB] JDBC 개념과 DriverManager

Loopy·2023년 1월 15일
0

스프링

목록 보기
1/16
post-thumbnail

☁️ JDBC 등장 이유

과거

애플리케이션 서버와 DB는 다음의 세 단계를 거쳐서 상호작용 한다.

  1. 주로 TCP/IP를 통해 커넥션 연결
  2. 애플리케이션 서버는 DB가 이해할 수 있는 SQL을 연결된 커넥션을 통해 DB에 전달
  3. DB는 전달된 SQL을 수행하고 그 결과를 응답

하지만 각각의 데이터베이스를 사용하는 방법, 커넥션을 연결 그리고 결과 응답을 받는 방법이 모두 달랐다. 따라서 DB 종류를 변경하게 되면(ex) MySQL -> ORACLE) 다음과 같은 문제가 발생했다.

  1. 애플리케이션 서버의 DB 로직까지 변경해야 한다.
  2. 개발자가 위 세단계의 방법을 DB 마다 따로 공부해야 한다.

이런 문제를 해결하기 위해, JDBC 라는 자바 표준이 등장하게 된다

☁️ JDBC란?

JDBC(Java Database Connectivity) 는, 자바에서 데이터베이스에 접속할 수 있도록 하는 자바 API이다.

이렇듯 JDBC 표준 인터페이스는 연결/SQL 요청/결과 응답의 과정을 추상화하였다.

그리고 인터페이스만으로는 기능 동작을 하지 않으니, 각각의 데이터베이스 회사들은 자신의 DB에 맞게 JDBC 표준 인터페이스를 구현해서 JDBC Driver 라는 라이브러리로 제공하고 있다.

장점

애플리케이션 로직이 JDBC 표준 인터페이스에만 의존하므로, DB를 변경하고 싶을때 JDBC 구현 라이브러리만 변경하면 기존 서버의 로직은 그대로 유지할 수 있다.

단점

SQL 자체는 DB마다 다른 문법을 가지고 있기 때문에, 결국 데이터베이스를 변경하면 JDBC 코드를 제외한 SQL은 해당 데이터베이스에 맞도록 변경해야 한다.

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

또한, 반복되는 중복 코드 뿐만 아니라 커넥션 리소스를 직접 반환해줘야 하기 때문에 제대로 반환하지 않는다면 커넥션이 계속 유지가 되는 리소스 누수의 문제도 발생할 수 있다.

☁️ JDBC와 최신 데이터 접근 기술

JDBC는 매우 오래되었고 사용법도 복잡한 만큼, 최근에는 SQL Mapper, ORM 같은 JDBC를 편리하게 사용하는 기술들을 사용하고 있다. 자바 Persistence Framework이라 하며, 둘다 JDBC 를 내부적으로 사용하고 있다는 것에 주의하자.

📚 Persistence Layer와 Framework
Persistence Layer프로그램의 영속성을 부여해주는 계층으로, 영속성이란 데이터를 생성한 프로그램이 종료되도 영구 저장되어 사라지지 않는 특성을 의미한다. 그리고 Persistence Framework 는 JDBC의 대안으로 프로그래밍의 복잡성이나 중복 없이 DB와 연동되는 시스템을 개발할 수 있는 프레임워크를 의미한다.

1. SQL Mapper

SQL Mapper 의 구현체로는 Spring JdbcTemplate, MyBatis 가 있다.

장점은, SQL만 작성하면 SQL Mapper 가 나머지 복잡한 일은 해준다는 것이다(SQL 응답 결과를 객체로 반환, JDBC 반복 코드 제거 등). 하지만 직접 SQL을 작성해야 한다는 단점이 있다.

2. ORM

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

반복적인 SQL을 직접 작성하지 않고 ORM이 동적으로 생성해주기 때문에 생산성이 증가한다는 장점이 있지만, 실무에서 사용하려면 깊이있게 학습해야 한다.

☁️ JDBC(1): H2 DB 연결

JDBC 커넥션 인터페이스와 구현체

public abstract class ConnectionConst {
    public static final String URL = "jdbc:h2:tcp://localhost/~/db";
    ...
}
public class DBConnectionUtil {

    public static Connection getConnection() {
        try {
            Connection connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
            return connection;
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}
  • DriverManager.getConnection() : 라이브러리에 있는 DB 드라이버를 찾아서 해당 드라이버가 제공하는 실제 커넥션 객체 반환

우리는 H2 데이터베이스의 URL 을 인자로 넘겼으니, H2 데이터베이스 드라이버가 작동해서 실제 데이터베이스와 커넥션을 맺고 그 결과를 반환해줄 것이다. H2 드라이버는 전용 커넥션인 org.h2.jdbc.JdbcConnection 를 제공한다.

외부 라이브러리을 찾아서 해당 폴더로 들어가보면 H2의 JdbcConnection 구현체가 자바의 Connection 표준 커넥션 인터페이스를 구현하고 있는 것을 확인할 수 있다.

DriverManager.getConnection 동작 흐름

그렇다면 자바는 어떻게 H2 드라이버를 찾는 걸까?
JDBC에서 제공하는 DriverManger는, 라이브러리에 등록한 데이터베이스 드라이버들을 관리하고 커넥션을 획득하는 기능을 제공한다.

  1. DrvierManager는 커넥션 요청이 들어오면, 라이브러리에 등록된 드라이버 목록들을 자동으로 인식하고 순서대로 URL/이름/비밀번호 정보들을 드라이버들에게 넘겨서 커넥션을 획득할 수 있는지 물어본다.

  2. 각각의 드라이버는 정보를 확인하고 본인이 처리할 수 있는 요청이라면 실제 DB에 연결해서 커넥션을 획득하고 구현체를 클라이언트에게 반환한다.

☁️ JDBC 기본 문법

1. 커넥션 획득 및 SQL문 수행

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

        Connection con = null;
        PreparedStatement pState = null;

        try {
            con = getConnection();
            pState = con.prepareStatement(sql);
            pState.setString(1, member.getMemberId());
            pState.setInt(2, member.getMoney());
            pState.executeUpdate();  // 데이터 저장/수정/삭제
            return member;
        } catch (SQLException e) {
            log.error("db error", e);
            throw e;
        } finally {
            close(con, pState, null);   // 모든 자원의 반납을 보장
        }
    }
  • Connection.prepareStatement(sql) : 데이터베이스에 전달할 SQL과 파라미터로 전달할 데이터들을 준비한다.
  • PreparedStatement.executeUpdate() : Statement 를 통해 준비된 SQL을 커넥션을 통해 실제 데이터베이스에 전달한다.

📚 PreparedStatement와 SQL Injection

String memberId = "select * from member";
String sql = "insert into member(member_id, money) value ("+ memberId +", "+ money +")";

PrepareStatement가 부모인 Statement 와 다른 점은 바로 파라미터 바인딩(?)을 지원한다는 것이다.

위의 코드처럼 ? 를 통해서가 아닌 직접 인자로 들어갈 변수를 넣는다면 SQL 구문으로 치환되기 때문에 데이터베이스 정보들이 모두 유출될 수 있는 문제가 발생한다. 파라미터 바인딩을 하면 단순 데이터(문자열)로 치환되기 때문에 파라미터에 SQL문을 넣어도 인식이 되지 않아 예방할 수 있다.

2. 커넥션 반환 및 리소스 정리

JDBC 에서 가장 중요하자 취약점이 되는 것은 바로 리소스 정리를 직접 해줘야 한다는 것이다. 자원 생성의 역순으로 반환을 진행하면 된다.

 private void close(Connection c, Statement s, ResultSet r) {
        if (s != null) {
            try {
                s.close();      
            } catch (SQLException e) {
                log.error("error", e);
            }
        }

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

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

자원을 하나씩 반납할때 앞쪽에서 예외가 발생해도 뒤쪽 코드에 영향을 주지 않아야 하기 때문에, 매번 try-catch을 통해 예외를 잡아야 한다.

profile
개인용으로 공부하는 공간입니다. 잘못된 부분은 피드백 부탁드립니다!

0개의 댓글