JDBC 동작 방식

이명범·2022년 3월 23일
0

1. 개요

최근에 싸피에서 JDBC라는 DB 접근 기술에 대해서 배웠다. JDBC는 자바에서 제공하는 DB 접근을 위한 인터페이스로 드라이버라고 불리는 구현체를 사용해서 자바에서 DB에 연결할 수 있다! 자바에서 이용하는 모든 DB 접근 기술(MyBatis, JPA 등)은 모두 JDBC를 기반으로 동작하므로 JDBC는 더 확실히 공부해보고자 이번에 따로 정리해보았다!!!!

2. JDBC 구조

JDBC 구조

JDBC를 이용해서 DB에 연결하기까지의 흐름은 다음과 같다. 사용자가 자바 어플리케이션에서 JDBC API를 이용해 DB에 접근하고자 한다. 하지만 JDBC API 자체는 인터페이스이므로 실제로 메서드들을 사용하기 위해서는 구현체가 필요하다. 그 구현체를 가져오는 역할은 DriverManager라는 클래스가 맡는다. 이 클래스가 Driver를 가져오면 이제 자바와 데이터베이스를 연결할 준비는 끝난다.

3. JDBC 동작 방식

3.1 클래스 구조

├─jdbc
│  ├─util
│  │  ├─DBConnection.java
│  │  ├─DBClose.java
│  ├─ProductDao.java
│  ├─ProductDaoImpl.java
│  ├─ProductDto.java
│  ├─ProductMain.java

클래스 구조는 위와 같다. 싸피에서 JDBC를 처음 배운 시점에 사용한 구조이다. 각각의 클래스 역할은 다음과 같다.

  • jdbc.util.DBConnection → MySQL Driver를 통해 실제 DBMS 서버와 연결하는 역할
  • jdbc.util.DBClose → DB와 연결하기 위해 사용한 객체들의 연결을 끊어주는 역할.
  • jdbc.ProductDao → 실제 데이터베이스에 접근하는 역할. 인터페이스임.
  • jdbc.ProductDaoImpl → ProductDao의 구현체
  • jdbc.ProductDto → 클라이언트와 서버간 데이터 전달을 위해 사용하는 역할.
  • jdbc.ProductMain → 실제 비즈니스 로직이 실행되는 위치

3.2 동작방식

우리는 ProductMain 클래스에 searchAll() 메서드를 구현하여 사용하였기 때문에 이 메서드의 동작 방식으로 JDBC의 흐름을 알아보고자 한다.

ProductDaoImpl 클래스에서 searchAll() 메서드를 한번 보자.

// 1번 코드 - ProductDaoImpl 클래스의 searchAll() 메서드//

@Override
public List<ProductDto> searchAll() {
	List<ProductDto> list = new ArrayList<>();

	try (Connection conn = DBConnection.getConnection()) {

		String sql = "select product_id, product_name, product_price, product_desc, ";
		sql += "date_format(register_date, '%y.%m.%d') register_date \n";
		sql += "from product";
		
		try (PreparedStatement pstmt = conn.prepareStatement(sql); 
				 ResultSet rs = pstmt.executeQuery()) {
			while (rs.next()) {
				ProductDto productDto = new ProductDto();
				productDto.setProductId(rs.getString("product_id"));
				productDto.setProductName(rs.getString("product_name"));
				productDto.setProductPrice(rs.getInt("product_price"));
				productDto.setProductDesc(rs.getString("product_desc"));
				productDto.setRegisterDate(rs.getString("register_date"));

				list.add(productDto);
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
	} catch (SQLException e) {
		e.printStackTrace();
	}
	return list;
}

이 코드를 보면 우선 처음에 Connection 객체를 가져오고, conn 객체에서 preparedStatement 객체를 가져오고, 또 pstmt 객체로 ResultSet 객체를 가져온다.

그럼 여기 나온 각각의 클래스는 어떤 역할을 하길래 이러한 순서로 가져오는지 알아보자.

3.2.1 Connection 클래스

// 2번 코드 - Connection 객체를 리턴하는 메서드//

private static final String URL = "jdbc:mysql://127.0.0.1:3306/ssafydb?serverTimezone=UTC&useUniCode=yes&characterEncoding=UTF-8";
private static final String DB_ID = "ssafy";
private static final String DB_PASS = "1234";

public static Connection getConnection() throws SQLException {
		return DriverManager.getConnection(URL, DB_ID, DB_PASS);
}

Connection 객체는 java.sql API가 제공하는 DriverManager 클래스에서 내부 로직을 거친 후 반환받아 사용한다.

하지만 이 코드만으로 코드를 실행하면 다음과 같은 Exception이 뜬다ㅠ

SQLException

DriverManager 내부에서는 ClassLoader를 통해 JDBC Driver를 읽어 Driver 구현체를 등록하는데, 위의 코드같은 경우에는 Driver 구현체를 등록하는 메서드가 없기 때문에 DriverManager가 드라이버를 찾지 못했다는 예외를 던진 것이다.

쉽게 말해 드라이버의 위치를 찾지 못하는 것이다!!

따라서 아래와 같은 코드를 추가해주어야 한다.

// 3번 코드 - 드라이버를 등록하는 부분을 추가했다.//

private static final String DRIVER = "com.mysql.cj.jdbc.Driver";
private static final String URL = "jdbc:mysql://127.0.0.1:3306/ssafydb?serverTimezone=UTC&useUniCode=yes&characterEncoding=UTF-8";
private static final String DB_ID = "ssafy";
private static final String DB_PASS = "1234";
	
static {
	try {
		Class.forName(DRIVER);
		System.out.println("성공");
	} catch (ClassNotFoundException e) {
		// TODO Auto-generated catch block
		System.out.println("실패");
		e.printStackTrace();
	}
}
	
public static Connection getConnection() throws SQLException {
	return DriverManager.getConnection(URL, DB_ID, DB_PASS);
}

여기서 DRIVER는 MySQL Driver 구현체의 위치이다. Class.forName() 메서드를 통해서 JVM에 등록되고 DriverManager가 읽을 수 있다. → Class.forName()의 자세한 설명

💡 그런데 사실 JDBC 4.0부터는 자동으로 드라이버를 초기화해주기 때문에 (코드 2)만으로도 실행 가능하다고 합니다!! 💡

자동 서비스 검색 문제 는 Java 1.6 및 서비스 공급자 메커니즘 으로 해결되었습니다 . 이를 통해 서비스 제공자는 서비스 를 포함하는 JAR 파일 내의 META-INF/services 아래에 서비스를 배치하여 서비스를 선언할 수 있습니다.
이 메커니즘은 드라이버를 자동으로 등록하므로 클래스를 로드하는 수동 단계가 더 이상 필요하지 않습니다. 그러나 서비스 제공자가 제자리에 있더라도 수동 클래스 로딩은 실패를 일으키지 않습니다. 최근 JVM 및 JDBC 4 드라이버를 사용하여 명시적으로 드라이버 로드를 호출하는 것은 완전히 합법적입니다.
서비스 제공자 사양은 수동 클래스 로딩을 선언적 접근 방식으로 간단히 대체합니다. 예를 들어 PostgreSQL JDBC 드라이버에는 META-INF/services/ 아래에 단일 파일이 있습니다. 파일 이름은 java.sql.Driver (JDBC 드라이버에 대해 잘 정립된 규칙)입니다. 여기에는 JDBC 드라이버의 정규화된 클래스 이름이 포함되며 이 경우 org.postgresql.Driver 입니다.

위 내용은 https://www.baeldung.com/java-jdbc-loading-drivers#jdbc-4-approach 여기서 확인한 내용입니다!!!

3.2.2 PreparedStatement 클래스

이제 Connection 객체를 성공적으로 불러오면서 DBMS 서버와 연결하는 데 성공했다. 이제 자바에서 원하는 쿼리를 날려 그 결과값을 자바로 받아오는 과정이 남았다. 우선 자바에서 DBMS로 쿼리를 날리기 위해서는 PreparedStatement 클래스가 필요하다.

아까 (코드 1)에 보면 아래와 같은 코드가 있었다.

// 4번 코드 - PreparedStatement 객체 선언부//

try (PreparedStatement pstmt = conn.prepareStatement(sql); 
		 ResultSet rs = pstmt.executeQuery())

여기서 보면 PreparedStatement는 Connection 객체에서 내부 로직을 거친 후 반환받아 사용한다. PreparedStatement는 AutoCloseable 인터페이스를 상속받아 사용하기 때문에 try 문의 소괄호 안에서 선언하였다. (사용 완료 후 자동 close 가능)

prepareStatement()에서 파라미터로 받은 sql은 사용할 쿼리문에 대한 정보가 담겨있다.

3.2.3 ResultSet 클래스

PrepareStatement 객체를 받은 후 executeQuery() 메서드를 실행하면 SELECT 문을 실행한 결과가 ResultSet 객체에 담겨서 반환받을 수 있다.

// 코드 5 - ResultSet 객체로 받은 테이블 정보를 DTO에 저장하는 코드 //

while (rs.next()) {
	ProductDto productDto = new ProductDto();
	productDto.setProductId(rs.getString("product_id"));
	productDto.setProductName(rs.getString("product_name"));
	productDto.setProductPrice(rs.getInt("product_price"));
	productDto.setProductDesc(rs.getString("product_desc"));
	productDto.setRegisterDate(rs.getString("register_date"));
	
	list.add(productDto);
}

ResultSet은 초기화되었을 때 받아온 테이블의 첫 번째 행을 가리키고 있으며 next()를 실행할 때마다 다음 행으로 갈 수 있다.

그리고 get~~() 메서드의 경우에는 열의 인덱스(정수형)을 파라미터로 받아도 되고, 아니면 행의 라벨 이름을 받아도 된다. (코드 5)는 라벨 이름을 통해서 현재 행의 value를 받아온 것이다.

이 3개의 클래스를 사용하여 selectAll()의 로직을 구현하는데 완성했다. 만들어진 리스트를 클라이언트에 반환한다.


// 코드 6 - 클라이언트가 리스트를 반환 받아 사용하는 모습//

private void searchAll() {
		List<ProductDto> list = ProductDaoImpl.getProductDao().searchAll();
		showList(list);
}

만들어진 리스트로 다음과 같이 View를 만들어 출력하였다.

4. 고찰

JDBC는 모든 DB 기술의 근간이 되는 기술이기 때문에 원래 공부를 할 생각이 있었던 기술 중 하나였는데 맨날 미루다가 드디어 이번에 싸피 덕분에 조금이나마 접근할 수 있게 되었다. JDBC의 동작 방식을 어느 정도 이해했으니 나중에 MyBatis나 JPA를 공부할 때 어떤 연관이 있는지도 한번 확인해보면 좋을 것 같다.

profile
백엔드 개발자가 될거야

0개의 댓글