JDBC

김채운·2026년 2월 24일

1. JDBC란 무엇인가?

JDBC(Java Database Connectivity)는 자바 프로그램 내에서 SQL 문을 실행하기 위한 함수 호출 인터페이스다.

과거에는 데이터베이스마다 접속 방식과 사용 함수가 달라 DBMS가 바뀔 때마다 코드를 수정해야 하는 번거로움이 있었다. 그러나 JDBC의 등장으로 자바 개발자는 데이터베이스의 종류에 상관없이 동일한 API를 사용하여 프로그래밍할 수 있게 되었다. 이는 JDBC가 인터페이스로 구성되어 있고, 각 DB 벤더(MySQL, Oracle 등)가 이를 구현한 드라이버(Driver)를 제공하기 때문이다.


2. JDBC의 주요 구성 요소

JDBC의 동작 원리를 이해하기 위해서는 다음의 핵심 클래스와 인터페이스를 숙지해야 한다.

  • DriverManager: JDBC에서 제공하는 구현체(Concrete) 클래스로, 각 DB에 맞는 드라이버를 관리하고 연결을 조율한다.

  • Driver: 모든 데이터베이스 제조사가 반드시 구현해야 하는 인터페이스다.

  • Connection: 데이터베이스와의 연결을 담당하는 객체다. DriverManager를 통해 생성된다.

  • PreparedStatement (Interface): 동일한 SQL 문장이 여러 번 반복 수행될 때 성능과 보안을 위해 사용된다.

    • executeQuery(): SELECT 문 실행 시 사용.

    • executeUpdate(): INSERT, UPDATE, DELETE와 같은 DML 실행 시 사용.

  • CallableStatement (Interface): 데이터베이스 내의 Stored Procedure를 호출할 때 사용한다.

  • ResultSet: SELECT 문의 실행 결과를 담는 객체다. 내부적으로 '커서(Cursor)'라는 포인터가 존재하며, 초기값은 첫 번째 레코드 직전을 가르킨다. next() 메서드를 통해 다음 레코드로 이동하며 데이터를 읽는다.


3. SQL Injection과 Statement의 퇴출

JDBC에는 Statement라는 객체도 존재하지만, 실무에서는 사용을 지양한다. 그 이유는 SQL Injection 공격에 취약하기 때문이다.

  • SQL Injection 예시:
    • SELECT * FROM userInfo WHERE username='admin' and password=' ' OR '1' = '1';

Statement는 SQL 문장을 단순 문자열로 처리하여 전달한다. 위와 같은 쿼리가 전달될 경우, 조건절이 항상 참('1'='1')이 되어 비밀번호를 몰라도 어드민 권한을 획득할 수 있는 치명적인 보안 허점이 발생한다. 따라서 현재는 파라미터를 안전하게 바인딩하는 PreparedStatement를 사용하는 것이 표준이다.


4. 실전 코드 분석

이제 제공된 코드를 통해 실제 JDBC가 어떻게 동작하는지 살펴보자.

4-1. SELECT 문 실행과 데이터 조회 (PreparedStatement)

이 코드는 DB에 접속하여 사용자의 ID와 비밀번호를 조회하는 기본적인 로직을 담고 있다

package com.ssafy.jdbc;

import java.sql.*;

public class JDBC2PreparedStatementTest {
    static final String DRIVER = "com.mysql.cj.jdbc.Driver";
    static final String ID = "ssafy";
    static final String PASSWORD = "ssafy";
    
    public static void main(String[] args) {
        // 1. 드라이버 로딩
        try {
            Class.forName(DRIVER);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        
        Connection con = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        
        try {
            // 2. Connection 생성
            con = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/mydb?useUniCode=yes&characterEncoding=UTF-8",
                    ID, PASSWORD);
            
            // 3. SQL 작성 및 실행 준비
            String sql = "select id, password from user;";
            pstmt = con.prepareStatement(sql);
            
            // 4. SQL 실행 (ResultSet 응답)
            rs = pstmt.executeQuery();
            
            // 5. 결과 처리 (Cursor 이동)
            while(rs.next()) {
                String id = rs.getString(1); 
                String pass = rs.getString("password");
                System.out.println(id + ", " + pass);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            // 6. 자원 해제 (중요)
            try {
                if(rs != null) rs.close();
                if(pstmt != null) pstmt.close();
                if(con != null) con.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

4-2. CRUD (Create, Read, Update, Delete) 통합 구현

다음은 try-with-resources 구문을 사용하여 자원 해제를 효율적으로 처리하고, CRUD 기능을 메서드별로 분리한 예제다.

package com.ssafy.jdbc;

import java.sql.*;

/**
 * [Database Schema]
 * CREATE SCHEMA IF NOT EXISTS `mydb` DEFAULT CHARACTER SET utf8 ;
 * USE `mydb` ;
 * CREATE TABLE IF NOT EXISTS `user` (
 * `id`  VARCHAR(45) NOT NULL,
 * `password` VARCHAR(45) NOT NULL,
 * PRIMARY KEY (`id`));
 */

public class JDBC3CUDTest {
    static final String DRIVER = "com.mysql.cj.jdbc.Driver";
    static final String ID = "ssafy";
    static final String PASSWORD = "ssafy";

    public static void main(String[] args) {
        JDBC3CUDTest test = new JDBC3CUDTest();

        try {
            Class.forName(DRIVER);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        // 실행 테스트
        test.deleteUser("a03");
        test.selectAllUser();
        System.out.println("========================");
    }

    private Connection getConnection() {
        Connection con = null;
        try {
            con = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/mydb?useUniCode=yes&characterEncoding=UTF-8", ID, PASSWORD);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return con;
    }

    // 모든 사용자 조회
    private void selectAllUser() {
        String sql = "select id, password from user";
        try(Connection con = getConnection();
            PreparedStatement pstmt = con.prepareStatement(sql);
            ResultSet rs = pstmt.executeQuery();) {
            while(rs.next()) {
                System.out.println(rs.getString(1) + ", " + rs.getString(2));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    // 사용자 추가 (Insert)
    private int insertUser(String id, String password) {
        int result = -1;
        String sql = "insert into user(id, password) values(?, ?)";
        try(Connection con = getConnection();
            PreparedStatement pstmt = con.prepareStatement(sql);) {
            pstmt.setString(1, id);
            pstmt.setString(2, password);
            result = pstmt.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return result;
    }

    // 특정 사용자 조회 (Select)
    private void selectUser(String id) {
        String sql = "select id, password from user where id = ?";
        try(Connection con = getConnection();
            PreparedStatement pstmt = con.prepareStatement(sql);) {
            pstmt.setString(1, id);
            try(ResultSet rs = pstmt.executeQuery();) {
                while(rs.next()) {
                    System.out.println(rs.getString(1) + ", " + rs.getString(2));
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    // 사용자 정보 수정 (Update)
    private int updateUser(String id, String password) {
        int result = -1;
        String sql = "update user set password = ? where id = ?";
        try(Connection con = getConnection();
            PreparedStatement pstmt = con.prepareStatement(sql);) {
            pstmt.setString(1, password);
            pstmt.setString(2, id);
            result = pstmt.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return result;
    }

    // 사용자 삭제 (Delete)
    private int deleteUser(String id) {
        int result = -1;
        String sql = "delete from user where id = ?";
        try(Connection con = getConnection();
            PreparedStatement pstmt = con.prepareStatement(sql);) {
            pstmt.setString(1, id);
            result = pstmt.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return result;
    }
}

5. 마치며

JDBC는 자바 백엔드 기술의 근간이 되는 중요한 기술이다. 최근에는 MyBatis나 JPA와 같은 프레임워크를 많이 사용하지만, 이 모든 도구의 하부에는 결국 JDBC가 동작하고 있다. 따라서 JDBC의 동작 원리와 PreparedStatement를 활용한 안전한 쿼리 전송 방식을 이해하는 것은 매우 중요하다.

profile
개발 지식과 금융 트렌드를 포스팅하고 있습니다.

0개의 댓글