0Pricing
Java Academy · レッスン

PreparedStatement

安全なパラメータ化クエリです。

「PreparedStatement」はCoddyKit上の無料Java Academyレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはJava Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Java Academyコースには全4レッスンが含まれています。

なぜPreparedStatementを使うのですか?

ユーザー入力を連結してSQLを組み立てると、危険であるだけでなく処理も遅くなります。

PreparedStatementは、次の2つの問題を解決します。

  • 安全性 — 値がSQLとは別に送信されるため、SQLインジェクションを防止できます。
  • パフォーマンス — データベースがクエリを一度だけ解析して実行計画を作成し、その後再利用できます。

SQLインジェクションの問題

文字列連結でクエリを組み立てる場合を考えてみましょう。ユーザーが名前として'; DROP TABLE users; --と入力すると、連結されたSQLはそれをコマンドとして実行します。

これはSQLインジェクションと呼ばれ、最も古く、被害の大きいWeb脆弱性の1つです。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は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の後には、自動生成されたIDが必要になることがよくあります。

準備時に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());
        }
    }
}

バッチ挿入

何千行も1つずつステートメントで挿入すると、処理が遅くなります。バッチ処理を使用してください。

  • パラメーターをバインドしてから、ps.addBatch()を呼び出します。
  • 各行について繰り返します。
  • 最後にps.executeBatch()を1回呼び出します。

これにより行がまとめて送信され、データベースとの往復回数を大幅に減らせます。

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());
        }
    }
}

ステートメントを再利用する

1つのPreparedStatementを、異なるパラメーターで何度も実行できます。

プレースホルダーに値を再度バインドして、もう一度executeを呼び出します。データベースはキャッシュされた実行計画を再利用するため、繰り返しクエリではプリペアドステートメントの方が高速になります。

連結しない

鉄則は、変化する値はすべて?パラメーターにすることです。

連結してよいのは、固定されたコード管理下の識別子(許可リストに登録された列名など)だけです。その場合でも、許可リストに対して検証してください。ユーザーから提供されたデータは、必ずバインドし、決して連結しないでください。

クローズと再利用のスコープ

PreparedStatementはConnectionに関連付けられているため、処理が終わったらクローズする必要があります。

try-with-resources内では、ステートメントが接続より先にクローズされるように宣言してください。ループ内でステートメントを何度も再利用する場合は、ループの外で1回だけ準備し、ループ内ではパラメーターだけを再バインドします。反復ごとに準備し直すと、キャッシュされた実行計画が破棄されてしまいます。

クイックチェック

なぜ PreparedStatement はSQLインジェクションを防げるのでしょうか?

まとめ

安全で高速なクエリの書き方を学びました。

  • ? プレースホルダーと型付きセッターを使用します(インデックスは1始まりです)。
  • setString、setInt、setNull などで値をバインドします。
  • RETURN_GENERATED_KEYS と getGeneratedKeys で自動生成キーを取得します。
  • addBatch + executeBatch で多数の書き込みをバッチ処理します。
  • 決してユーザー入力をSQLに連結してはいけません。

よくある質問

「PreparedStatement」レッスンは無料ですか?

はい。「PreparedStatement」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Java Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Java Academyコースには全4レッスンが含まれています。

「PreparedStatement」で何を学びますか?

安全なパラメータ化クエリです。 ブラウザで直接実行するハンズオンコードでJava Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Java Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのJava Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。

「PreparedStatement」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このJava Academyレッスンでコードを書いて実行できますか?

はい。すべてのJava Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. JDBC の基礎
  2. PreparedStatement
  3. トランザクション
  4. HikariCP によるコネクションプール
← Java Academyに戻る