Javaでのデータベース操作とPreparedStatementの基礎

1. 基本:データベース接続とSQL文の準備

Javaでデータベース(ここではOracle)を操作するには、まずデータベースに接続する必要があります。そして、接続後にSQL文を準備し、実行します。以下はデータベース接続と基本的なクエリの流れです。

下記のプログラムはJavaからOracleデータベースに接続して、ユーザーが入力した商品名をもとに例としてITEM_TABLEから該当するデータを検索し、その結果を表示するプログラムです。

package dbsample; // このクラスが所属するパッケージ名

import java.sql.Connection;            // データベース接続を表すクラス
import java.sql.DriverManager;         // データベース接続を管理するクラス
import java.sql.PreparedStatement;     // プリコンパイル済みのSQL文を実行するクラス
import java.sql.ResultSet;             // SQLの実行結果を格納するクラス
import java.sql.SQLException;          // SQL関連のエラー処理を行うクラス
import java.util.Scanner;              // ユーザー入力を取得するためのクラス

public class DBSample { // データベースサンプルプログラムの開始

    public static void main(String[] args) { // プログラムのメインメソッド
        // データベース接続用変数
        Connection conn = null; // データベース接続を保持する変数を初期化
        
        // ステートメント実行用変数
        PreparedStatement stmt = null; // プリコンパイル済みSQL文の実行変数を初期化
        
        try {
            // Oracle JDBCドライバのロード(JDBCドライバを使用可能にする)
            Class.forName("oracle.jdbc.driver.OracleDriver"); // JDBCドライバを読み込み
            
            // DBに接続(データベースURL、ユーザー名、パスワードを指定)
            conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:orcl", "USER_NAME", "PASSWORD"); // データベースに接続
            
            // ユーザー入力を取得するための準備
            Scanner sc = new Scanner(System.in); // ユーザー入力を受け取るスキャナーを作成
            System.out.print("検索したい商品名を入力してください:"); // ユーザーに商品名を入力させるプロンプトを表示
            String name = sc.next(); // ユーザーが入力した商品名を変数nameに格納
            
            // プレースホルダーを使ったSQL文を準備
            String sql = "SELECT * FROM ITEM_TABLE WHERE ITEM_NAME = ? ORDER BY ITEM_NO"; // 商品名を条件にしてデータを取得するSQL文
            
            // SQLをプリコンパイル(事前に準備しておく)
            stmt = conn.prepareStatement(sql); // SQL文をプリコンパイルして準備
            stmt.setString(1, name); // SQL文の最初の?に、ユーザーが入力した商品名を設定
            
            // SQLの実行
            ResultSet rs = stmt.executeQuery(); // SQLを実行して結果セットを取得
            
            // 結果の取得と表示
            while (rs.next()) { // 取得した結果を1行ずつ処理
                System.out.println(rs.getString("ITEM_NAME")); // "ITEM_NAME"列の値を取得して表示
            }
            
            // 結果セットをクローズ(リソース解放)
            rs.close(); // 結果セットを閉じ、リソースを解放
        } catch (SQLException | ClassNotFoundException e) { // SQL実行やクラスロードでエラーが発生した場合
            e.printStackTrace(); // エラーの詳細を表示
        } finally { // エラーの有無に関わらず、最後に実行されるブロック
            // リソースの解放
            try {
                // ステートメントがある場合はクローズ
                if (stmt != null) { // ステートメントが開かれている場合
                    stmt.close(); // ステートメントを閉じ、リソースを解放
                }
                // データベース接続がある場合はクローズ
                if (conn != null) { // データベース接続が確立されている場合
                    conn.close(); // データベース接続を閉じ、リソースを解放
                }
            } catch (SQLException e) { // リソース解放中にエラーが発生した場合
                e.printStackTrace(); // エラーの詳細を表示
            }
        }
    }
}

コードの解説

  1. データベース接続の準備
    • Connection connPreparedStatement stmt を宣言して、データベース接続とSQL文の実行に使います。
  2. JDBCドライバのロードjavaコードをコピーするClass.forName("oracle.jdbc.driver.OracleDriver");
    • JDBCドライバをロードして、Oracleデータベースに接続できるようにしています。
  3. データベース接続javaコードをコピーするconn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:orcl", "USER_NAME", "PASSWORD");
    • データベースURL、ユーザー名、パスワードを指定して接続しています(USER_NAMEPASSWORDは実際のアカウント情報を使ってください)。
  4. ユーザー入力の取得javaコードをコピーするScanner sc = new Scanner(System.in); String name = sc.next();
    • ユーザーに検索したい商品名を入力してもらい、その入力を変数nameに保存しています。
  5. SQL文の準備とプレースホルダーjavaコードをコピーするString sql = "SELECT * FROM ITEM_TABLE WHERE ITEM_NAME = ? ORDER BY ITEM_NO"; stmt = conn.prepareStatement(sql); stmt.setString(1, name);
    • SQL文の中に「?」を使い、ユーザーが入力した商品名を後から設定しています。これにより、SQLインジェクションのリスクを減らします。
  6. SQLの実行と結果の取得javaコードをコピーするResultSet rs = stmt.executeQuery();
    • SQL文を実行し、検索結果をResultSetに格納します。
  7. 結果の表示javaコードをコピーするwhile (rs.next()) { System.out.println(rs.getString("ITEM_NAME")); }
    • 検索結果を1行ずつ読み取り、ITEM_NAME列の値を表示しています。
  8. リソースの解放
    • データベース接続やステートメントなどのリソースはfinallyブロックで閉じることで、使い終わった後に自動的に解放されます。

2. PreparedStatementとは

PreparedStatement は、SQL文を効率的かつ安全に実行するためのオブジェクトです。SQL文に「?」を使うことで後から値を指定でき、次のようなメリットがあります。

  • SQLインジェクション対策:ユーザーの入力がSQLの一部として実行されることを防ぎます。
  • 効率的:SQL文を事前に準備(コンパイル)するため、同じSQLを繰り返し実行する場合にパフォーマンスが向上します。
  • コードが見やすくなる:SQL文とパラメータの指定が分かれて書かれるため、見通しが良くなります。

3. プレースホルダーと値の設定方法

SQL文で使う「?」はプレースホルダーと呼ばれ、動的に値を設定できます。たとえば、「特定の商品名で検索するSQL」を準備するときは、以下のように「?」を使います。

String sql = "SELECT * FROM ITEM_TABLE WHERE ITEM_NAME = ?";<br>PreparedStatement stmt = conn.prepareStatement(sql);<br>stmt.setString(1, "Apple"); // 1つ目の ? に "Apple" を設定

  • stmt.setString(1, "Apple");1 は、SQL文内で最初の?を指しており、これに"Apple"を代入しています。
  • 複数の?がある場合も、順番に指定して埋め込みます。

4. try-with-resources構文での接続管理

try (Connection conn = ...) のように try の後にカッコでリソースを指定すると、自動で close() が呼ばれます。この構文は try-with-resources構文 と呼ばれ、リソースリークを防ぎつつコードを簡潔に書けます。

5. SQLインジェクションのリスクと防止

SQLインジェクションとは、悪意のある入力をSQL文に組み込ませる攻撃手法です。PreparedStatementを使わないで、文字列連結でSQLを組み立てると、次のような問題が起こり得ます。

String userInput = "Apple' OR '1'='1";<br>String sql = "SELECT * FROM ITEM_TABLE WHERE ITEM_NAME = '" + userInput + "'";<br>Statement stmt = conn.createStatement();<br>ResultSet rs = stmt.executeQuery(sql); // SQLインジェクションのリスクがある

この例では、「' OR '1'='1」のように条件を操作されてしまい、本来表示されないデータまで表示されるリスクがあります。しかし、PreparedStatementを使えばこのリスクを回避できます。

まとめ

  • データベース接続ConnectionDriverManager.getConnection()で行い、try-with-resources構文で自動クローズします。
  • PreparedStatementの使用:プレースホルダーでSQL文を準備し、setString()setInt()で動的に値を埋め込むことで、SQLインジェクションのリスクを減らし、効率的にSQLを実行できます。

PreparedStatementは、Javaでデータベースを操作する際に安全で効率的な方法です。データベースを使うプログラムでは積極的に活用しましょう。

良かったらフォローお願いします

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です