Home JDBC
Post
Cancel

JDBC

공부하게 된 배경

전부터 ORM에 대해서 공부하며 JDBC에 대해서 정확히 정리해야겠다고 생각했는데, HikariCP를 포스팅하면서 이참에 이어서 JDBC를 포스팅하는 것이 좋겠다고 생각해 포스팅하게 되었다.

HikariCP(Connection Pool) 포스팅 보러가기

Spring Data JDBC, Spring Data JPA 등과 같은 기술이 등장하면서 JDBC API를 직접적으로 사용하는 일은 줄어들었지만, 이러한 기술들도 데이터베이스와 연동하기 위해서 내부적으로 JDBC를 이용하기 때문에 JDBC의 동작 흐름에 대해 알아둘 필요가 있다.

다루는 내용

이 글에서는 JDBC 를 설명하며 연관 지식으로

  • JDBC 드라이버
  • Statement vs PreparedStatement
  • JdbcTemplate
  • Connection Pool

에 대해서도 다룬다.

JDBC(Java DataBase Connectivity) 란?


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

사용하는 DBMS에 따라 어플리케이션에서 DB를 다루는 방식이 다르다면, 사용자가 알아야할 것이 늘어나고 개발하는 데에 많은 어려움이 있을 것이다. 그래서 JDBC를 통해 추상화된 인터페이스를 제공하고, 각 벤더사에서 각자의 DBMS에 따라 구현해놓은 드라이버를 설치하여 사용하는 방식으로 DB에 접근한다. 이를 통해 어떤 DB를 사용하든 개발자가 JDBC를 사용하는 방법은 변하지 않는다.

정리

  • JDBC는 DBMS에 종속되지 않는 관련 API를 제공한다. (이를 통해 서로 다른 DBMS일지라도 일관적인 방법으로 접근 가능)
  • JDBC API는 JDK1에서 제공하며(java.sql 패키지) JDBC 프로그래밍을 위해서는 JDBC 드라이버가 필요하다. JDBC 드라이버는 각 DBMS 회사에서 제공하는 라이브러리 압축파일(jar)을 말한다.
    • Java API : 자바를 사용하여 쉽게 구현할 수 있도록 한 클래스 라이브러리의 집합. 즉, 복잡하지만 필요한 클래스들을 미리 구현하여 사용자가 쉽게 구현하도록 하는 API

JDBC의 동작 흐름


JDBC 과정

주요 클래스 및 인터페이스


JDBC 표준 인터페이스

  • JDBC는 3가지 기능을 표준 인터페이스로 정의하여 제공한다.
    • java.sql.Connection - 연결
    • java.sql.Statement - SQL을 담은 내용
    • java.sql.ResultSet - SQL 요청 응답

java.sql Package의 주요 클래스


  1. java.sql.Driver
    • DB와 연결하는 Driver class를 만들 때 반드시 implements 해야하는 Interface. 즉, JDBC 드라이버의 중심이 되는 Interface.
  2. java.sql.DriverManger
    • JDBC Driver 집합을 관리하는 클래스. 이 클래스를 통해 DB Connection을 얻어올 수 있다.
    • Class.forName() 메서드를 통해 자동으로 객체가 생성된다.
  3. java.sql.Connection
    • 데이터베이스와 연결정보를 가지는 Interface.
    • DriverManager로부터 Connection 객체를 가져온다.
  4. java.sql.Statement
    • SQL문을 DB에 전송하는 방법을 정의한 Interface. Connection을 통해 가져온다.
    • Connection 클래스의, 인자가 없는 createStatement() 메소드로 호출
  5. java.sql.ResultSet
    • SELECT 실행 결과를 조회할 수 있는 방법을 정의한 Interface.
    • SQL문의 결과를 저장하는 객체
  6. java.sql.PreparedStatement
    • Statement의 하위 Interface. SQL문을 미리 컴파일하여 실행 속도를 높인다.
    • Connection 클래스의, SQL을 인자값으로 받는 prepareStatement(sql) 메소드로 호출
  7. java.sql.CallableStatement
    • PreparedStatement의 하위 Interface. DBMS의 Stored procedure를 호출한다.
    • PL/SQL을 호출할 때 사용

여기에서 가장 핵심이 되는 JDBC 드라이버에 대해서 조금 더 자세히 설명하겠다.

JDBC 드라이버

: 데이터베이스와의 통신을 담당하는 인터페이스

JDBC 드라이버에 관한 설명

출처 : https://devlog-wjdrbs96.tistory.com/139

  • JDBC는 Java 애플리케이션 내에서 JDBC API를 사용하여 데이터베이스에 접근하는 단순한 구조이다. JDBC API를 사용하기 위해서는 JDBC 드라이버를 먼저 로딩한 후 데이터베이스와 연결하게 된다.
  • 각각의 데이터베이스에 알맞은 JDBC 드라이버의 구현체를 이용해서 특정 벤더(Oracle, MSSQL, MySQL 등)의 데이터베이스에 접근할 수 있다.
  • 즉, JDBC 드라이버란 DBMS 회사들이 자신들의 데이터베이스 시스템에 접근할 수 있도록 표준 JDBC 인터페이스에 명시된 메소드들을 구현한 것이다.

JDBC 개발 단계


아래에서 살펴볼 JDBC의 모든 기능은 예외를 발생시킬 수 있기 때문에 try-catch 문으로 처리해야 한다는 것에 유의하자.

JDBC를 사용하기 위한 환경 설정(조건)

  1. DBMS 서버 설치
    • MySQL, Oracle, DB2 등
  2. JDBC Driver 설치
    • 각 DBMS 페이지에서 제공하는 파일을 다운받기 (*.jar 파일)
  3. JDBC API
    • JDK에 포함

1. JDBC Driver loading

  • 드라이버 인터페이스를 구현한 클래스를 로딩한다.
    • Class.forName() 메서드를 통해 MySQL에서 제공하는 Driver클래스를 JVM Method Area에 로딩시킨다.
  • 각 벤더사마다 클래스 이름이 다르며 MySQL의 경우 “com.mysql.jdbc.Driver“다.
  • JDBC Driver를 로딩하게 되면 DriverManager의 static 메서드를 사용할 수 있다.
  • 드라이버 로딩 실패 시 ClassNotFoundException이 발생할 수 있으므로 catch문에서 해당 Exception에 대한 처리가 필요하다.
1
Class.forName("com.mysql.jdbc.Driver");

2. Connection 객체 생성

  • DriverManager의 getConnection 메서드를 이용하여 Connection 객체를 얻는다. 이 객체를 이용하여 Statement를 작성할 수 있다.(다음 3번에서 설명)
  • Connection을 얻기 위해 필요한 URL 역시 벤더사마다 다르다.
    • MySQL의 경우 “jdbc:mysql://{IP}:{Port}/{사용할DB이름}“이다.
  • DBMS(MySQL)에 접근 가능한 계정의 ID와 Password가 필요하다.
  • 예시
    • IP : localhost, Port : default port(생략), 사용할DB이름 : testDB, (serverTimezone : 기준 시간대를 Query String 형식으로 설정가능)
1
2
3
4
String url = "jdbc:mysql://localhost/testDB?serverTimezone=UTC";
String id = "testid";
String pw ="1234";
Connection conn = DriverManager.getConnection(url,id,pw);

3. Statement / PreparedStatement 객체 생성

  • 연결이 완료되면, Statement 나 PreparedStatement 객체를 이용하여 쿼리문을 전송해야한다.
  • 위에서 얻은 Connection 객체의 createStatement(), prepareStatement() 메서드를 호출하여 각 객체를 얻을 수 있다.
  • 쿼리를 수행할 때 동적으로 할당해야 하는 값이 있으면 PreparedStatement 객체를 사용하고, 동적으로 할당할 필요가 없으면 Statement 객체를 사용한다.
  • 두 객체는 캐시(cache) 사용 여부에 있어서도 차이가 있다. PreparedStatement는 동일한 쿼리 실행 시, 해당 쿼리가 캐싱되어 구문분석, 컴파일, 최적화 단계를 건너뛰게 된다. 따라서 DB에 부하도 적게주고 성능도 좋다.

4. executeUpdate() / executeQuery() 실행

  • 작성한 쿼리들이 executeUpdate()executeQuery() 메서드의 인자로 전달되어 실행된다.
  • 쿼리의 결과가 있으면 executeQuery() 메서드를 호출하여 ResultSet 객체에 담고, 쿼리의 결과가 없으면 executeUpdate() 메서드를 호출하여 int형 변수에 결과값(영향을 미친 row의 개수)을 할당한다.

5. (SELECT의 경우) ResultSet 반환

  • 쿼리문이 SELECT라면 ResultSet으로 받고, 그 이외에는 int 타입으로 받는다.
  • ResultSet에는 SELECT 쿼리 결과의 레코드들이 담겨있다.

3,4,5번 과정에 대한 예시 코드

1
2
3
4
5
//Statement 사용법.

Statement stmt = conn.createStatement(); //쿼리 수행을 위한 Statement 객체 생성(SQL문이 달라져도 동일한 Statement 객체로 재사용 가능)
String sql = "SELECT name, owner, date_format(birth, '%Y년%m월%d일') date FROM pet"; //SQL 쿼리 작성(세미콜론';' 제외)
ResultSet rs = stmt.executeQuery(sql); // 쿼리 수행. 조회된 레코드들은 ResultSet 객체에 추가.
1
2
3
4
5
6
//PreparedStatement 사용법.

PreparedStatement pstmt = conn.prepareStatement("SELECT * from book where id=?"); //쿼리 수행을 위한 PreparedStatement 객체 생성(SQL문마다 객체 생성해야함. 재사용 불가)
int id = 3;
pstmt.setInt(1, id); //id 값은 임의로 3 할당.
ResultSet rs = pstmt.executeQuery(); // 쿼리 수행. 조회된 레코드들은 ResultSet 객체에 추가.
  • 만약 실행 결과를 확인하고 싶다면 아래와 같이 출력 가능하다.
1
2
3
4
5
6
7
8
9
while(rs.next()){ //.next() : 커서를 다음 레코드(행)로 이동시키는 메소드
// 레코드의 칼럼은 배열과 달리 0부터 시작하지 않고 1부터 시작.
// 데이터베이스에서 가져오는 데이터의 타입에 맞게 getString 또는 getInt 등을 호출한다.
  String name = rs.getString(1); // 또는 getString("name");
  String owner = rs.getString(2);
  String date = rs.getString(3);

  System.out.println(name + " " + owner + " " + date);
}
  • INSERT나 UPDATE 문은 아래와 같이 작성 가능하다.
1
2
3
4
5
6
7
8
9
10
11
12
String sql = "INSERT INTO pet VALUES (?,?,?,?,?,?)";
PreparedStatement pstmt = conn.prepareStatement(sql); //쿼리 수행을 위한 PreparedStatement 객체 생성

// 데이터 binding
pstmt.setString(1, name);
pstmt.setString(2, owner);
pstmt.setString(3, species);
pstmt.setString(4, gender);
pstmt.setString(5, birth);
pstmt.setString(6, death);

int count = pstmt.executeUpdate(); // 쿼리 수행.

6. ResultSet, Statement, Connection 순으로 close

  • SQL 질의가 끝났으면 사용했던 자원을 역순으로 모두 해제한다.
  • 각 객체의 .close() 매서드를 사용한다.
1
2
3
4
5
6
7
8
try {
	if (res !=null) res.close();
	if (pstmt != null) pstmt.close();
	if (conn != null) conn.close();
	System.out.println("연결 종료");
} catch(Exception e2) {
	e2.printStackTrace();
}

여기까지의 전체적인 순서를 그림으로 확인해보면 아래와 같다. JDBC 실행순서

추가 개발 상식

JDBC 실행 과정 중 사용되는 Connection 객체를 미리 만들어 놓고 꺼내어 쓰는 기법이 Connection Pool (ex. HikariCP) 이다. 이러한 기법을 사용하면 Connection 생성 및 종료(close)에 따른 오버헤드를 최소화하여 성능을 향상시킬 수 있다.
HikariCP(Connection Pool) 포스팅 보러가기

Statement vs PreparedStatement


실무에서는 Statement는 쓰지 않고 PreparedStatement만 사용하는데 그 이유는 뭘까?

우선 두 객체의 동작 방식에 대해서 알아본 뒤, 차이점을 비교해보자.

Statement 및 PreparedStatement의 동일한 동작 방식

  1. 구문 분석 (Parsing) 및 정규화 (Normalization)
    • Query 문법 확인 및 데이터베이스, 테이블 존재여부 확인
  2. 컴파일 (Compliation)
    • Query 컴파일
  3. Query 최적화 (Query Optimization Phase)
    • Query 실행 방법의 최적 계획 선택
  4. 캐시 (Cache)
    • Query 최적화 단계에서 선택된 계획이 캐시에 저장되어 동일한 Query 실행 시, 1 ~ 3단계를 실행하지 않고 캐시를 통해 찾음
  5. 실행 (Execution Phase)
    • Query가 실행된 값이 담긴 객체 (ResultSet)를 사용자에게 반환

 

Statement vs PreparedStatement 차이점

1. 캐시 사용 유무 (재사용성)

  • Statement는 첫 수행한 Query와 완전히 일치하는 Query를 요청하는 경우에만 캐싱한 데이터를 재활용할 수 있다.
  • 그에 비해, PreparedStatement는 placeHolder (?)를 이용하여 파라미터를 바인딩하기 때문에 기존에 컴파일된 Query를 재활용할 수 있다.
  • 따라서, PreparedStatement를 사용하면 상대적으로 DB부하를 낮추고 속도를 더 높일 수 있다.

2. 가독성

  • 파라미터를 Query문 안에 직접 넣어주는 Statement 방식과 달리, PreparedStatement는 파라미터 바인딩을 통해 가독성 및 유지보수가 쉽다.
1
2
3
4
5
6
//Statement 예시

Connection conn = DriverManager.getConnection(url, id, pwd);
Statement stmt = conn.createStatement();
stmt.executeUpdate("Update Member Set Name = " + "머스크햄" + "Where id = " + 10); //SQL 1번
stmt.executeUpdate("Update Member Set Name = " + "화성갈끄니까"+ "Where id = " + 11); //SQL 2번
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//PreparedStatement 예시

Connection conn = DriverManager.getConnection(url, id, pwd);
String sql = "Update Member Set Name = ? Where id = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);

//SQL 1번
pstmt.setString(1, "머스크햄");
pstmt.setInt(2, 10);
pstmt.executeUpdate();

//SQL 2번
pstmt.setString(1, "화성 갈끄니까");
pstmt.setInt(2, 11);
pstmt.executeUpdate();

3. SQL Injection 방지

  • PreparedStatment 방식은 사용자에게서 받은 파라미터 값이 악의적으로 조작되었다 하더라도 파라미터 바인딩을 통해 SQL Injection을 방지할 수 있다.
  • 정확히는 .setString() 메소드 내부에서 사용되는 javaEncode() 메소드가 SQL Injection을 방지해준다.

자세한 내용은 이 블로그 참고바란다.

JDBC API 와 Spring-JDBC-Template


JdbcTemplate은 JDBC Core Pakage의 중앙 클래스로 커넥션 획득, statement 준비 등 개발자가 JDBC를 직접 사용할 때의 반복 작업을 대신 처리해준다.

즉, JdbcTemplate은 JDBC의 사용을 단순화하고 일반적인 오류를 방지함으로써 개발자가 JDBC 기술을 쉽게 사용할 수 있도록 도와주는 클래스다. spring-jdbc 라이브러리에 포함되어 있다.

자세한 설명은 이 블로그에서 참고 바란다.

: Reference

위키백과 - JDBC
Oracle - java.sql Pakage 공식문서
나는연어다 - JDBC 프로그래밍에 사용하는 객체
shs2810 - JDBC란 무엇일까?
잇트루 - JDBC란 무엇인가?
victolee - JDBC 사용하기(MySQL)
pjok1122 - JDBC MySQL 연동하기
Dev_sHu - Statement보다 PreparedStatement를 사용해야 하는 이유
연구소장 J - [Spring] JdbcTemplate이란?
코동이 - JDBC, JdbcTemplate
Hudi - JDBC Driver와 HikariCP 설정

  1. JDK는 개발자가 자바 기반 애플리케이션 개발을 위해 다운로드하는 소프트웨어 패키지다. 컴파일러와 클래스 라이브러리(Class Library)가 포함되어 있다. 

This post is written by 서병범.

사용 중인 Port 찾아서 Kill하기

서브넷, 서브넷마스크