0Pricing
Java Academy · 강의

PreparedStatement

안전한 매개변수화 쿼리를 작성합니다

PreparedStatement은(는) CoddyKit의 무료 Java Academy 강의입니다. 이것은 4개 중 2번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Java Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Java Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

PreparedStatement를 사용하는 이유

사용자 입력을 문자열로 연결하여 SQL을 만드는 것은 위험하고 느립니다.

PreparedStatement는 두 문제를 모두 해결합니다:

  • 안전성 — 값이 SQL과 분리되어 전송되므로 SQL 주입을 방지합니다.
  • 성능 — 데이터베이스가 쿼리를 한 번만 구문 분석하고 실행 계획을 세운 뒤 재사용할 수 있습니다.

SQL 주입 문제

문자열을 연결하여 쿼리를 만든다고 생각해 보십시오. 사용자가 이름으로 '; DROP TABLE users; --를 입력하면 연결된 SQL이 이를 명령으로 실행합니다.

이것이 SQL 주입이며, 가장 오래되고 피해가 큰 웹 취약점 중 하나입니다. PreparedStatement는 설계상 이를 불가능하게 만듭니다.

public class Main {
    public static void main(String[] args) {
        String userInput = "'; DROP TABLE users; --";
        // Dangerous: never do this
        String unsafe = "SELECT * FROM users WHERE name = '" + userInput + "'";
        System.out.println("Injected SQL becomes:");
        System.out.println(unsafe);
    }
}

자리 표시자

PreparedStatement에서는 나중에 제공할 각 값의 자리에 ?를 하나씩 작성합니다.

그런 다음 타입별 설정 메서드로 값을 바인딩합니다:

  • ps.setString(1, name)
  • ps.setInt(2, age)

매개변수 인덱스는 1부터 시작하며 ? 표시의 순서와 일치합니다.

안전한 쿼리

prepareStatement로 문을 만들고 매개변수를 바인딩한 다음 executeQuery를 호출합니다.

값이 SQL 텍스트에 전혀 포함되지 않으므로 사용자가 무엇을 입력하더라도 주입이 발생할 수 없습니다.

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class Main {
    public static void main(String[] args) {
        String url = "jdbc:postgresql://localhost:5432/shop";
        String sql = "SELECT id FROM users WHERE name = ?";
        try (Connection conn = DriverManager.getConnection(url, "app", "secret");
             PreparedStatement ps = conn.prepareStatement(sql)) {
            ps.setString(1, "Ada");
            try (ResultSet rs = ps.executeQuery()) {
                while (rs.next()) System.out.println(rs.getInt("id"));
            }
        } catch (SQLException e) {
            System.out.println(e.getMessage());
        }
    }
}

여러 매개변수 바인딩

각 ?에는 고유한 설정 메서드 호출이 필요합니다. 순서가 중요합니다. 매개변수 1은 첫 번째 ?, 매개변수 2는 두 번째 ?에 해당하며 이후에도 같은 방식입니다.

열의 형식에 맞는 설정 메서드를 사용하십시오. 예를 들어 setInt, setString, setDouble, setBoolean 등이 있습니다.

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class Main {
    public static void main(String[] args) {
        String url = "jdbc:postgresql://localhost:5432/shop";
        String sql = "INSERT INTO users(name, age, active) VALUES (?, ?, ?)";
        try (Connection conn = DriverManager.getConnection(url, "app", "secret");
             PreparedStatement ps = conn.prepareStatement(sql)) {
            ps.setString(1, "Grace");
            ps.setInt(2, 42);
            ps.setBoolean(3, true);
            int rows = ps.executeUpdate();
            System.out.println("Inserted " + rows + " row");
        } catch (SQLException e) {
            System.out.println(e.getMessage());
        }
    }
}

NULL 처리

SQL NULL을 바인딩하려면 java.sql.Types 상수와 함께 setNull(index, type)을 사용합니다.

Java의 null을 setString에 전달하고 제대로 처리되기를 기대하지 마십시오. 명시적으로 지정해야 합니다:

  • ps.setNull(2, java.sql.Types.INTEGER)

생성된 키 가져오기

INSERT 후에는 자동으로 생성된 식별자가 필요한 경우가 많습니다.

준비할 때 Statement.RETURN_GENERATED_KEYS를 전달한 다음 ps.getGeneratedKeys()를 읽습니다.

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class Main {
    public static void main(String[] args) {
        String url = "jdbc:postgresql://localhost:5432/shop";
        String sql = "INSERT INTO users(name) VALUES (?)";
        try (Connection conn = DriverManager.getConnection(url, "app", "secret");
             PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
            ps.setString(1, "Linus");
            ps.executeUpdate();
            try (ResultSet keys = ps.getGeneratedKeys()) {
                if (keys.next()) System.out.println("New id: " + keys.getLong(1));
            }
        } catch (SQLException e) {
            System.out.println(e.getMessage());
        }
    }
}

일괄 삽입

수천 개의 행을 한 번에 하나씩 문으로 삽입하면 느립니다. 일괄 처리를 사용하십시오:

  • 매개변수를 바인딩한 다음 ps.addBatch()를 호출합니다.
  • 각 행에 대해 반복합니다.
  • ps.executeBatch()를 한 번 호출합니다.

이렇게 하면 행을 함께 전송하여 왕복 횟수를 크게 줄일 수 있습니다.

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class Main {
    public static void main(String[] args) {
        String url = "jdbc:postgresql://localhost:5432/shop";
        String sql = "INSERT INTO tags(label) VALUES (?)";
        String[] labels = {"java", "jdbc", "sql"};
        try (Connection conn = DriverManager.getConnection(url, "app", "secret");
             PreparedStatement ps = conn.prepareStatement(sql)) {
            for (String label : labels) {
                ps.setString(1, label);
                ps.addBatch();
            }
            int[] counts = ps.executeBatch();
            System.out.println("Batched " + counts.length + " inserts");
        } catch (SQLException e) {
            System.out.println(e.getMessage());
        }
    }
}

문 재사용

하나의 PreparedStatement를 서로 다른 매개변수로 여러 번 실행할 수 있습니다.

자리 표시자에 값을 다시 바인딩하고 execute를 다시 호출하십시오. 데이터베이스가 캐시된 실행 계획을 재사용하므로 준비된 문은 반복 쿼리에서 더 빠릅니다.

절대 연결하지 않기

핵심 원칙은 다음과 같습니다. 변경될 수 있는 모든 값은 ? 매개변수여야 합니다.

고정되어 있고 코드가 제어하는 식별자(허용 목록에 있는 열 이름 등)만 연결할 수 있습니다. 이 경우에도 허용 목록과 대조하여 검증하십시오. 사용자가 제공한 데이터는 항상 바인딩하고 절대 연결하지 않습니다.

닫기 및 재사용 범위

PreparedStatement는 해당 Connection에 연결되어 있으므로 사용이 끝나면 닫아야 합니다.

리소스 자동 관리 블록 안에서는 연결보다 문이 먼저 닫히도록 문을 선언하십시오. 문을 반복문에서 여러 번 재사용한다면 반복문 밖에서 한 번만 준비하고 반복문 안에서는 매개변수만 다시 바인딩하십시오. 반복할 때마다 다시 준비하면 캐시된 실행 계획을 버리게 됩니다.

빠른 확인

PreparedStatement는 왜 SQL 인젝션을 방지하나요?

복습

안전하고 빠른 쿼리를 작성하는 방법을 배웠습니다.

  • ? 자리 표시자와 자료형에 맞는 설정 메서드를 사용합니다(인덱스는 1부터 시작).
  • setString, setInt, setNull 등으로 값을 바인딩합니다.
  • RETURN_GENERATED_KEYS와 getGeneratedKeys로 자동 생성 키를 가져옵니다.
  • addBatch와 executeBatch를 함께 사용해 여러 쓰기 작업을 일괄 처리합니다.
  • 절대로 사용자 입력을 SQL에 결합하지 않습니다.

자주 묻는 질문

“PreparedStatement” 강의는 무료인가요?

네 — “PreparedStatement” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Java Academy 강의 전체를 잠금 해제할 수 있습니다. Java Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

“PreparedStatement”에서 뭘 배우나요?

안전한 매개변수화 쿼리를 작성합니다 브라우저에서 직접 실행하는 실습 코드로 Java Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

Java Academy을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 Java Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 2번째 강의입니다.

“PreparedStatement” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 Java Academy 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 Java Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. JDBC 기초
  2. PreparedStatement
  3. 트랜잭션
  4. HikariCP를 사용한 연결 풀링
← Java Academy(으)로 돌아가기