java-recipes

ホーム データベース(JDBC) › DB-01

DB-01: JDBC基本パターン(接続・クエリ・結果処理)

JDBC(Java Database Connectivity)は Java 標準の DB アクセス API です。 MySQL・PostgreSQL・Oracle・H2 など様々な DB をドライバを切り替えるだけで使える統一インターフェースを解説します。

いつ使うか

  • Java アプリから MySQL・PostgreSQL・Oracle などのリレーショナル DB にアクセスするとき
  • Spring や MyBatis などの ORM フレームワークを使わず、SQL を直接書いて処理したいとき
  • 外部ライブラリが使えない環境(組み込みシステム・レガシー環境)での DB アクセス
  • バッチ処理で大量データを効率よく読み書きするとき

主な JDBC インターフェース

クラス/インターフェース役割
ConnectionDB との接続を表す。使い終わったら必ずクローズする
StatementSQL を実行する。パラメータなしの場合に使用
PreparedStatementパラメータ付き SQL を安全に実行(DB-02 で詳説)
ResultSetSELECT の結果セット。next() でカーソルを1行ずつ進める

サンプルコード

JdbcBasicSample.java
import java.sql.*;
import java.util.*;

public class JdbcBasicSample {

    // DB接続を確立する
    // H2 インメモリDB(実務では MySQL/PostgreSQL の接続文字列に変更する)
    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(
            "jdbc:h2:mem:jdbcbasic;DB_CLOSE_DELAY=-1", "sa", "");
    }

    // テーブル作成とサンプルデータ挿入
    public static void setup(Connection conn) throws SQLException {
        try (Statement stmt = conn.createStatement()) {
            stmt.execute(
                "CREATE TABLE IF NOT EXISTS employees (" +
                "  id     INT PRIMARY KEY," +
                "  name   VARCHAR(50)," +
                "  dept   VARCHAR(30)," +
                "  salary INT" +
                ")"
            );
            stmt.execute("DELETE FROM employees"); // 冪等のためクリア
            stmt.execute("INSERT INTO employees VALUES (1, '田中太郎', '営業', 350000)");
            stmt.execute("INSERT INTO employees VALUES (2, '鈴木花子', '開発', 420000)");
            stmt.execute("INSERT INTO employees VALUES (3, '佐藤次郎', '開発', 380000)");
        }
    }

    // SELECT: 全件取得
    public static List<String> findAll(Connection conn) throws SQLException {
        List<String> results = new ArrayList<>();
        String sql = "SELECT id, name, dept, salary FROM employees ORDER BY id";
        try (Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery(sql)) {
            while (rs.next()) {
                results.add(String.format("id=%d name=%s dept=%s salary=%d",
                    rs.getInt("id"), rs.getString("name"),
                    rs.getString("dept"), rs.getInt("salary")));
            }
        }
        return results;
    }

    // INSERT: 1件追加
    // ⚠️ 本番では PreparedStatement を使うこと(SQL インジェクション対策)
    public static int insert(Connection conn, int id, String name, String dept, int salary)
            throws SQLException {
        try (Statement stmt = conn.createStatement()) {
            return stmt.executeUpdate(
                "INSERT INTO employees VALUES (" + id + ", '" + name + "', '" + dept + "', " + salary + ")"
            );
        }
    }

    // UPDATE: 給与変更
    public static int updateSalary(Connection conn, int id, int newSalary) throws SQLException {
        try (Statement stmt = conn.createStatement()) {
            return stmt.executeUpdate(
                "UPDATE employees SET salary = " + newSalary + " WHERE id = " + id);
        }
    }

    // DELETE: 1件削除
    public static int delete(Connection conn, int id) throws SQLException {
        try (Statement stmt = conn.createStatement()) {
            return stmt.executeUpdate("DELETE FROM employees WHERE id = " + id);
        }
    }

    public static void main(String[] args) throws SQLException {
        // try-with-resources で Connection を確実にクローズする
        try (Connection conn = getConnection()) {
            setup(conn);

            System.out.println("=== SELECT 全件 ===");
            for (String row : findAll(conn)) {
                System.out.println(row);
            }

            System.out.println("\n=== INSERT ===");
            insert(conn, 4, "山田美咲", "総務", 310000);
            System.out.println("追加後: " + findAll(conn).size() + " 件");

            System.out.println("\n=== UPDATE ===");
            int updated = updateSalary(conn, 1, 370000);
            System.out.println("更新件数: " + updated);

            System.out.println("\n=== DELETE ===");
            int deleted = delete(conn, 4);
            System.out.println("削除件数: " + deleted);
            System.out.println("削除後: " + findAll(conn).size() + " 件");
        }
    }
}

Java 8 では Connection・Statement・ResultSet を try-with-resources で管理します。Statement で SQL を組み立てる場合は SQL インジェクション対策として DB-02 の PreparedStatement を使うことを検討してください。

よくあるミス・注意点

⚠️ Connection・Statement・ResultSet は必ず try-with-resources でクローズする

クローズし忘れると DB コネクションが解放されず、コネクションプールが枯渇してアプリケーション全体が止まります。try-with-resources を使えば 例外発生時も自動でクローズされます。

⚠️ Statement で文字列結合した SQL は SQL インジェクションの温床

このサンプルの insert() メソッドは説明用としてStatement を使っていますが、 本番コードでは必ず PreparedStatement を使ってください(DB-02 参照)。

⚠️ rs.getString() は null を返す場合がある

DB の列が NULL の場合、rs.getString("col")null を返します。 null チェックを怠ると NullPointerException が発生します。rs.wasNull() またはOptional.ofNullable() で対処しましょう。

⚠️ 本番では DriverManager でなくコネクションプールを使う

DriverManager.getConnection() は 呼び出すたびに新規接続を作成するため低速です。本番では HikariCP や c3p0 などのコネクションプールを使って接続を再利用します。

テストする観点

  • SELECT で初期データが全件取得できること
  • INSERT 後に件数が増えること(境界値: 0件・1件・複数件)
  • UPDATE 後に対象レコードの値が変わっていること
  • DELETE 後に対象レコードが消えること
  • 存在しない ID を UPDATE/DELETE しても例外が発生せず、更新件数が 0 になること
  • Connection を閉じた後に SQL を実行すると SQLException になること

GitHub でソースコードを見る →