JDBC

김회민·2023년 2월 25일
0

Spring Data

목록 보기
3/7

JDBC란?

Java Database Connectivity

Java언어에서 Database와 통신할 수 있도록 만들어진 Java 표준 API이다.

인터페이스로 이루어져 있기 때문에 독립적으로 사용할 수는 없지만, 프로그래머는 JDBC를 이용해 코드를 작성하고 다양한 DBMS에서 구현한 JDBC Driver와 연결하는 것만으로 손쉽게 교체할 수 있다는 장점이 있다.

대표적으로 DriverManager, Connection, Statement, ResultSet 인터페이스를 지원하며 각 역할은 다음과 같다.

인터페이스역할
java.sql.DriverManager연결
java.sql.ConnectionsetAutoCommit, commit, rollback
java.sql.StatementSQL문 매핑, 전달
java.sql.ResultSetSQL문 요청 결과값

JDBC 사용법

DriverManager

import java.sql.DriverManager;

DriverManager.getConnection(URL, USERNAME, PASSWORD);

DriverManager는 DBMS와의 커넥션 획득을 담당하는 객체이다. getConnection() 메서드를 이용해 연결하고 싶은 DB의 주소, 사용자 이름과 비밀번호를 입력하여 연결할 수 있다.

그런데, DriverManager의 가장 큰 문제점이 있는데, 그것은 바로 getConnection()으로 연결을 할 때마다 새로운 커넥션을 생성한다는 것이다. 그래서 커넥션 풀을 사용하려면 다른 방법으로 해야한다.

Connection

import java.sql.DriverManager;
import java.sql.Connection;

Connection con = DriverManager.getConnection(URL, USERNAME, PASSWORD);
con.close();

DriverManager의 getConnection() 메서드를 이용해 생성된 커넥션을 담는 객체이다.
연결을 종료해주기 위해선 close() 메서드를 사용해야 한다.

(Prepared) Statement

import java.sql.PreparedStatement;

String sql = "select * from member where member_id = ?";
PreparedStatement pstmt = con.prepareStatement(sql);
pstmt.setString(1, "UUID");
pstmt.executeQuery();
import java.sql.PreparedStatement;

String sql = "insert into member(member_id, money) values (?, ?)";
PreparedStatement pstmt = con.prepareStatement(sql);
pstmt.setString(1, "UUID");
pstmt.setInt(2, 5000);
pstmt.executeUpdate();

위에서 생성한 Connection의 prepareStatement() 메서드를 이용해 쿼리를 담는다.

그 후, 쿼리문의 ? 위치마다 setString(), setInt(), setBoolean() 등등을 이용해 알맞는 타입에 맞게 데이터를 지정한다. 첫 번째 인자는 ?의 위치, 두 번째 인자는 들어갈 값에 해당한다.

해당 쿼리문이 조회인 경우 executeQuery(), 변경인 경우 executeUpdate() 메서드를 사용하여 DBMS에게 쿼리문을 보낸다.

pstmt.close();
con.close();

Statement 또한 해제를 해주어야 하는데, 순서를 주의해야 한다. Connection을 먼저 닫아버리면 Statement는 닫을 수 없다.

ResultSet

import java.sql.ResultSet;

ResultSet rs = pstmt.executeQuery();
List<Member> members = new ArrayList<>();
while (rs.next() != null) {
    members.add(
        new Member(rs.getString("member_id"), rs.getInt("money"))
    );
}

Statement로 쿼리문을 보냈다면 ResultSet으로 그 결과를 받을 수 있다.

next() 메서드는 해당 쿼리문 결과의 Row에 대한 커서 위치를 한 칸 증가시키는 메서드이다. next() 메서드를 호출하지않고 값을 가져오려고 시도한다면 NPE 예외가 발생한다.

값을 가져올 때도 인자에 스키마 값을 문자열로 입력하고 그에 해당하는 타입에 맞춰서 getString(), getInt() 메서드를 호출해야한다.

rs.close();
pstmt.close();
con.close();

ResultSet 또한 해제를 해주어야 한다. 이때도 순서에 주의해서 닫아주자.

try-catch의 강제

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

try {
    con = DriverManager.getConnection(URL, USERNAME, PASSWORD);
    con.setAutoCommit(false);

    ///////////////
    // 비즈니스 로직
    String sql = "select * from member where member_id = ?";
    pstmt = con.prepareStatement(sql);
    pstmt.setString(1, "UUID");
    rs = pstmt.executeQuery();
    ///////////////

    con.commit();
} catch(SQLException e) {
    con.rollback();
} finally {
    if(rs != null) { rs.close(); }
    if(pstmt != null) { pstmt.close(); }
    if(con != null) { con.close(); }
}

위 코드들은 중간에 문제가 발생하면 SQLException을 던지게 된다. 그런데, 트랜잭션 단위로 실행이 되는 DBMS의 특성상 중간에 실패하면 rollback을 해주어야 하는데, 이는 자동으로 해주지 않는다. 또한, close() 메서드를 호출해주지 않는다면 DBMS와의 커넥션이 종료되지 않고 계속해서 남아있게 되며, 이게 계속 쌓이면 DB장애가 발생할 수 있다.

물론 위 3개의 객체들도 AutoClose를 지원하기 때문에 try-with-resources를 사용하여 해결할 수 있지만, 매 비즈니스 로직마다 위의 코드를 반복해서 작성해야하는 것은 변함이 없다.

JDBC의 한계

JDBC의 등장으로 각 DBMS마다 다른 커넥션 연결, SQL 전달, 응답받는 방법을 더이상 신경쓰지 않아도 된다는 장점이 생겼지만, 실질적으로 명령을 수행하기 위한 SQL문법이 표준화가 안되었다는 치명적인 단점이 남아있다.

또한, 위 3개가 통합이 되었더라도 매 트랜잭션마다 동일한 코드를 계속해서 작성해야 한다. Connection으로 DB와 연결하고, SQL문을 작성해서 Statement에 담은 다음 DB에게 보내고, ResultSet으로 결과를 받아서 하나하나 바인딩하고, 혹시나 오류가 일어나면 rollback해야하니까 try-catch도 넣고, 최종적으로는 커넥션을 해제하는 것까지 모든 트랜잭션마다 한번씩 작성해주어야 한다. 이는 비즈니스 로직의 코드와 섞여서 읽기도 힘들고 유지보수하기도 힘든 끔찍한 상황이 벌어지기 쉽다.

그래서 시간이 지나면서 이를 해결하기 위한 여러 방법이 나왔는데, 그 중에 대표적으로 SQL Mapper와 ORM이 있다.

SQL Mapper

SQL Mapper는 이름에서도 말해주듯이 매 트랜잭션마다 발생하는 공통적인 처리를 묶어서 매핑해주는 기술이다. 위에서 말한 연결, SQL문 보내기, 결과 받기, rollback, commit, 예외 핸들링이 이에 해당하며 함수형 프로그래밍에서 보이는 Callback 방식을 이용해 구현되어져 있는 것이 특징이다.

이로 인해, 코드를 작성할 때 위의 로직을 신경쓰지 않고 비즈니스 로직에만 집중할 수 있도록 되었지만, 각 DBMS마다 미묘하게 다른 SQL문을 통합하지 못했다는 단점이 있다.

대표적인 기술로는 JdbcTemplate, MyBatis가 있다.

ORM

ORM은 Object Releation Manager의 약자로써, 쉽게 말해서 DBMS의 테이블과 객체를 매핑하는 기술이다. 이 기술을 사용하면 위에서 말한 과정 외에도 간단한 SQL문의 작성이 없어진 것 뿐만 아니라 각 테이블과의 관계마저도 객체 지향 언어의 객체 단위로 끌고와 작성할 수 있다는 강점이 있다.

즉, SQL문 작성 없이 평소에 하던 대로 객체를 작성하고 관계를 맺으면 ORM이 알아서 알맞는 SQL문을 작성하고 DBMS에게 보내게 된다는 말이다. 말만 들으면 이게 도대체 무슨 마법같은 일이냐라고 할 수 있지만, 내부적으로 어떤 일이 일어나는지 학습하는데 어려움이 있고, 여러 문제에 대한 대처법을 익히는데 시간이 걸리는 편이다. 또한, 정말 복잡한 로직이 필요하다면 어쩔 수 없이 SQL문을 작성해야할 때도 있다.

대표적인 기술로는 JPA, Hibernate, TypeORM이 있다.

profile
백엔드 개발자 지망생

0개의 댓글