본문 바로가기
spring

[Spring] JDBC

by goblin- 2024. 11. 22.

1. JDBC란 무엇인가?

 

**JDBC(Java Database Connectivity)**는 자바 애플리케이션에서 관계형 데이터베이스에 연결하고 SQL 쿼리를 실행하며, 결과를 처리하기 위한 표준 API입니다. JDBC를 사용하면 다양한 데이터베이스 시스템(MySQL, PostgreSQL, Oracle 등)에 독립적으로 접근할 수 있습니다.

 

데이터베이스 독립성: JDBC는 데이터베이스 벤더에 종속되지 않는 표준 인터페이스를 제공합니다.

표준화된 API: 개발자가 일관된 방식으로 데이터베이스 작업을 수행할 수 있습니다.

 

2. JDBC의 아키텍처

 

JDBC 아키텍처는 다음과 같은 두 계층으로 구성됩니다.

 

1. JDBC API: 애플리케이션과 JDBC 드라이버를 연결하는 표준 인터페이스입니다.

2. JDBC 드라이버: 특정 데이터베이스에 대한 구현체로, JDBC API를 통해 전달된 호출을 실제 데이터베이스에 맞게 변환하여 실행합니다.

 

JDBC 드라이버 유형:

 

Type 1: JDBC-ODBC 브리지 드라이버 (현재는 거의 사용되지 않음)

Type 2: 네이티브 API 드라이버

Type 3: 네트워크 프로토콜 드라이버

Type 4: 순수 자바 드라이버 (가장 일반적으로 사용됨)

 

3. JDBC의 주요 구성 요소

 

3.1 DriverManager

 

역할: JDBC 드라이버를 관리하고, 데이터베이스 연결을 얻는 데 사용됩니다.

사용 방법: DriverManager.getConnection() 메서드를 통해 데이터베이스에 연결합니다.

특징:

애플리케이션이 시작될 때 JDBC 드라이버를 로드하면, 해당 드라이버는 DriverManager에 자신을 등록합니다.

DriverManager는 등록된 드라이버 목록을 관리하고, getConnection() 호출 시 적절한 드라이버를 선택하여 연결을 생성합니다.

 

3.2 Connection

 

역할: 데이터베이스와의 연결을 나타내는 객체입니다.

주요 기능:

트랜잭션 관리 (commit(), rollback())

SQL 문 실행을 위한 Statement, PreparedStatement, CallableStatement 객체 생성

 

3.3 Statement와 PreparedStatement

 

Statement:

SQL 문을 실행하기 위한 객체입니다.

동적 SQL이나 간단한 쿼리에 사용됩니다.

SQL 문이 매번 컴파일되므로, 반복 실행 시 성능이 저하될 수 있습니다.

PreparedStatement:

미리 컴파일된 SQL 문을 실행하기 위한 객체입니다.

바인딩 변수를 사용하여 SQL 인젝션을 방지하고 성능을 향상시킵니다.

동일한 SQL 문을 여러 번 실행할 때 유리합니다.

 

3.4 ResultSet

 

역할: SQL 쿼리의 결과를 저장하고, 결과를 순회하며 데이터를 추출할 수 있습니다.

주요 메서드:

next(): 다음 행(row)으로 이동

getInt(), getString() 등: 특정 컬럼의 값을 가져옴

특징:

ResultSet은 기본적으로 전방향(forward-only)이며, 읽기 전용입니다.

필요에 따라 스크롤 가능하거나 업데이트 가능한 ResultSet을 생성할 수 있습니다.

 

3.5 DataSource

 

역할: 데이터베이스 연결을 관리하기 위한 고수준의 인터페이스입니다.

사용 방법: DataSource를 통해 Connection 객체를 얻습니다.

특징:

**커넥션 풀링(Connection Pooling)**을 지원하여 성능을 향상시킵니다.

**JNDI(Java Naming and Directory Interface)**를 통해 등록 및 조회할 수 있습니다.

트랜잭션 관리분산 트랜잭션을 지원하는 고급 기능을 제공합니다.

이점:

DriverManager보다 유연하고 확장성이 높습니다.

애플리케이션 서버나 프레임워크(Such as Spring)에서 자주 사용됩니다.

 

4. JDBC의 동작 방식

 

1. JDBC 드라이버 로드:

Class.forName("com.mysql.cj.jdbc.Driver") 등을 사용하여 드라이버 클래스를 로드합니다.

최신 JDBC 버전에서는 자동 로딩이 지원되므로 생략할 수 있습니다.

2. 데이터베이스 연결:

DriverManager.getConnection() 또는 DataSource.getConnection()을 사용하여 데이터베이스에 연결하고 Connection 객체를 얻습니다.

3. SQL 문 실행:

Connection 객체에서 Statement 또는 PreparedStatement를 생성합니다.

SQL 문을 실행하고 결과를 ResultSet으로 받습니다.

4. 결과 처리:

ResultSet을 순회하며 데이터를 추출하고 처리합니다.

5. 자원 해제:

ResultSet, Statement, Connection 객체를 close() 메서드를 호출하여 자원을 해제합니다.

 

5. DriverManager와 DataSource의 비교

 

5.1 DriverManager(물리적 접속)

 

역할: JDBC 드라이버를 관리하고 데이터베이스 연결을 얻는 데 사용됩니다.

특징:

간단한 애플리케이션에서 사용하기 적합합니다.

커넥션 풀링이나 트랜잭션 관리 등의 고급 기능을 지원하지 않습니다.

매번 새로운 연결을 생성하므로, 다수의 연결이 필요한 경우 성능 저하가 발생할 수 있습니다.

 

5.2 DataSource(논리적 접속)

 

역할: 데이터베이스 연결을 관리하기 위한 고수준의 인터페이스입니다.

특징:

커넥션 풀링을 지원하여 데이터베이스 연결의 재사용을 가능하게 합니다.

트랜잭션 관리분산 트랜잭션을 지원합니다.

설정이 복잡할 수 있지만, 애플리케이션 서버나 프레임워크(Spring 등)에서 쉽게 설정할 수 있습니다.

장점:

성능 향상: 커넥션 풀링을 통해 연결 생성 및 해제 비용을 절감합니다.

확장성: 대규모 애플리케이션에서 효율적인 연결 관리가 가능합니다.

유연성: 설정 파일이나 JNDI를 통해 연결 정보를 관리할 수 있습니다.

 

5.3 언제 어떤 것을 사용해야 하는가

 

DriverManager:

간단한 애플리케이션이나 테스트 환경에서 사용하기 적합합니다.

커넥션 풀링이나 고급 기능이 필요하지 않은 경우에 사용합니다.

DataSource:

성능과 확장성이 중요한 애플리케이션에서 사용해야 합니다.

Spring이나 애플리케이션 서버를 사용하는 경우 자연스럽게 DataSource를 사용하게 됩니다.

 

6. JDBC를 사용한 데이터베이스 연결 예제

 

6.1 DriverManager를 사용한 예제

import java.sql.*;

public class JdbcExample {
    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/your_database";
        String username = "your_username";
        String password = "your_password";

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

        try {
            // 1. JDBC 드라이버 로드
            Class.forName("com.mysql.cj.jdbc.Driver");

            // 2. 데이터베이스 연결
            conn = DriverManager.getConnection(url, username, password);

            // 3. SQL 문 준비
            String sql = "SELECT * FROM users WHERE age > ?";
            pstmt = conn.prepareStatement(sql);
            pstmt.setInt(1, 20);

            // 4. SQL 문 실행
            rs = pstmt.executeQuery();

            // 5. 결과 처리
            while (rs.next()) {
                int id = rs.getInt("id");
                String name = rs.getString("name");
                int age = rs.getInt("age");

                System.out.println("ID: " + id + ", 이름: " + name + ", 나이: " + age);
            }
        } catch (ClassNotFoundException e) {
            System.err.println("드라이버 로드 실패: " + e.getMessage());
        } catch (SQLException e) {
            System.err.println("SQL 오류: " + e.getMessage());
        } finally {
            // 6. 자원 해제
            try { if (rs != null) rs.close(); } catch (SQLException e) { /* 무시 */ }
            try { if (pstmt != null) pstmt.close(); } catch (SQLException e) { /* 무시 */ }
            try { if (conn != null) conn.close(); } catch (SQLException e) { /* 무시 */ }
        }
    }
}

설명:

 

드라이버 로드: Class.forName()을 통해 JDBC 드라이버를 로드합니다.

데이터베이스 연결: DriverManager.getConnection()을 사용하여 Connection 객체를 얻습니다.

SQL 실행 및 결과 처리: PreparedStatementResultSet을 사용하여 SQL 문을 실행하고 결과를 처리합니다.

자원 해제: 사용한 자원(ResultSet, PreparedStatement, Connection)을 명시적으로 닫아줍니다.

 

6.2 DataSource를 사용한 예제

import javax.sql.DataSource;
import java.sql.*;

public class DataSourceExample {
    public static void main(String[] args) {
        DataSource dataSource = getDataSource(); // DataSource 설정 메서드 호출
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        try {
            // 1. 데이터베이스 연결
            conn = dataSource.getConnection();

            // 2. SQL 문 준비
            String sql = "SELECT * FROM users WHERE age > ?";
            pstmt = conn.prepareStatement(sql);
            pstmt.setInt(1, 20);

            // 3. SQL 문 실행
            rs = pstmt.executeQuery();

            // 4. 결과 처리
            while (rs.next()) {
                int id = rs.getInt("id");
                String name = rs.getString("name");
                int age = rs.getInt("age");

                System.out.println("ID: " + id + ", 이름: " + name + ", 나이: " + age);
            }
        } catch (SQLException e) {
            System.err.println("SQL 오류: " + e.getMessage());
        } finally {
            // 5. 자원 해제
            try { if (rs != null) rs.close(); } catch (SQLException e) { /* 무시 */ }
            try { if (pstmt != null) pstmt.close(); } catch (SQLException e) { /* 무시 */ }
            try { if (conn != null) conn.close(); } catch (SQLException e) { /* 무시 */ }
        }
    }

    private static DataSource getDataSource() {
        // DataSource 설정 코드 (예: HikariCP 사용)
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:mysql://localhost:3306/your_database");
        config.setUsername("your_username");
        config.setPassword("your_password");
        config.setDriverClassName("com.mysql.cj.jdbc.Driver");
        return new HikariDataSource(config);
    }
}

설명:

 

DataSource 설정: getDataSource() 메서드에서 HikariCP 등을 사용하여 DataSource를 설정합니다.

데이터베이스 연결: dataSource.getConnection()을 통해 연결을 얻습니다.

나머지 부분DriverManager를 사용하는 경우와 동일합니다.

 

7. JDBC의 장단점

 

장점

 

데이터베이스 독립성: 다양한 데이터베이스에 동일한 방식으로 접근할 수 있습니다.

직접적인 제어: SQL 문을 직접 작성하고 실행하므로, 데이터베이스 작업을 세밀하게 제어할 수 있습니다.

표준 API: 표준화된 인터페이스를 제공하여 일관성 있는 개발이 가능합니다.

 

단점

 

반복적인 코드: 연결, 자원 해제, 예외 처리 등 반복적인 코드가 많이 발생합니다.

자원 관리의 복잡성: 연결 누수(Connection Leak)를 방지하기 위해 자원을 적절히 해제해야 합니다.

예외 처리의 복잡성: SQL 예외 처리가 복잡할 수 있으며, 코드의 가독성을 떨어뜨립니다.

생산성 저하: 반복적인 작업으로 인해 개발 생산성이 낮아질 수 있습니다.

 

 

8. 마치며

 

JDBC는 자바 애플리케이션에서 데이터베이스와 상호 작용하기 위한 기본적인 방법을 제공합니다. 직접 SQL을 제어할 수 있어 세밀한 데이터베이스 작업이 가능하지만, 반복적인 코드와 자원 관리의 복잡성으로 인해 생산성이 떨어질 수 있습니다.

 

DriverManager는 간단한 애플리케이션에서 사용하기 적합하지만, 커넥션 풀링과 같은 고급 기능을 지원하지 않습니다. 반면, DataSource는 커넥션 풀링과 트랜잭션 관리 등 고급 기능을 제공하여 대규모 애플리케이션에서 효율적인 연결 관리를 가능하게 합니다.

 

프로젝트의 요구사항과 규모에 따라 DriverManagerDataSource 중 적절한 방식을 선택하시기 바랍니다. 일반적으로 Spring 등의 프레임워크를 사용하면 DataSource를 활용하여 커넥션 풀링과 트랜잭션 관리를 자동화할 수 있습니다.