概要

業務システムの多くはデータベースとのやり取りを伴います。Java では JDBC(Java Database Connectivity)が標準 API として用意されており、外部ライブラリを追加しなくても基本的な DB 操作が可能です。フレームワークに任せる場面が増えた今でも、JDBC の基本を押さえておくことはトラブル時の原因調査や、フレームワークが生成する SQL の理解に直結します。この記事では、DriverManager による接続取得、Statement を使った SELECT・INSERT・UPDATE・DELETE、そして try-with-resources によるリソースの確実な解放までを整理します。保守案件や社内ツールで「素の JDBC」を触る場面に備え、動作する完結したコードで基本操作を確認します。

使いどころ

社内管理ツールから従業員マスタを参照・更新する画面の裏側を JDBC で実装する

バッチ処理で CSV から読み取ったデータを DB テーブルへ INSERT する

障害調査時にフレームワークを経由せず、直接 JDBC でクエリを実行して状態を確認する

コード例

JDBC で従業員テーブルの CRUD を実装する
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class JdbcBasicSample {

    record Employee(int id, String name, String dept, int salary) {}

    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(
            "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1", "sa", "");
    }

    public static void setup(Connection conn) throws SQLException {
        try (var 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)");
        }
    }

    public static List<Employee> findAll(Connection conn) throws SQLException {
        var results = new ArrayList<Employee>();
        var sql = "SELECT id, name, dept, salary FROM employees ORDER BY id";
        try (var stmt = conn.createStatement();
             var rs = stmt.executeQuery(sql)) {
            while (rs.next()) {
                results.add(new Employee(
                    rs.getInt("id"), rs.getString("name"),
                    rs.getString("dept"), rs.getInt("salary")));
            }
        }
        return results;
    }

    public static int updateSalary(Connection conn, int id, int newSalary)
            throws SQLException {
        var sql = "UPDATE employees SET salary = ? WHERE id = ?";
        try (var pstmt = conn.prepareStatement(sql)) {
            pstmt.setInt(1, newSalary);
            pstmt.setInt(2, id);
            return pstmt.executeUpdate();
        }
    }

    public static int delete(Connection conn, int id) throws SQLException {
        var sql = "DELETE FROM employees WHERE id = ?";
        try (var pstmt = conn.prepareStatement(sql)) {
            pstmt.setInt(1, id);
            return pstmt.executeUpdate();
        }
    }

    public static void main(String[] args) throws SQLException {
        try (var conn = getConnection()) {
            setup(conn);

            System.out.println("=== 全件取得 ===");
            for (var emp : findAll(conn)) {
                System.out.printf("id=%d name=%s dept=%s salary=%d%n",
                    emp.id(), emp.name(), emp.dept(), emp.salary());
            }

            System.out.println("\n=== UPDATE ===");
            updateSalary(conn, 1, 370000);
            System.out.println("田中の給与更新完了");

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

Java 8 / 17 / 21 の完全なサンプルコードは GitHub リポジトリ で確認できます。

Version Coverage

var による型推論とテキストブロック(""")で SQL を見やすく記述できる。record で行データを型安全に保持する設計が自然に書ける。

Java 17
// Java 17: var + record + テキストブロックで簡潔に
record Employee(int id, String name, String dept, int salary) {}
var sql = "SELECT id, name, dept, salary FROM employees ORDER BY id";
try (var stmt = conn.createStatement();
     var rs = stmt.executeQuery(sql)) {
    while (rs.next()) {
        var emp = new Employee(
            rs.getInt("id"), rs.getString("name"),
            rs.getString("dept"), rs.getInt("salary"));
    }
}

Library Comparison

Pure JDBC(標準 API)保守案件や小規模ツール、フレームワーク非依存の処理で使う。コード量は多いが依存ゼロで動く。SQL の制御が明示的。
Spring JdbcTemplateSpring Boot プロジェクトで定型的な CRUD を効率化したい場合。ボイラープレートが大幅に減るが、Spring への依存が前提になる。
MyBatisSQL を XML やアノテーションで管理し、マッピングを自動化したい場合。SQL の可読性は高いが、設定ファイルと学習コストが増える。

注意点

Statement に直接文字列連結で値を埋め込むと SQL インジェクションの原因になる。ユーザー入力を含む場合は必ず PreparedStatement を使うこと

Connection・Statement・ResultSet は try-with-resources で閉じる。finally で close() を呼ぶ旧式の書き方はリソースリークの温床になりやすい

DriverManager.getConnection の接続文字列はデータベース製品ごとに異なる。H2 のインメモリ DB はテスト用であり、本番では MySQL / PostgreSQL の URL に読み替えること

ResultSet のカラム取得で列名と型を間違えると実行時例外になる。getInt で VARCHAR 列を取ると ClassCastException 相当のエラーになるため、カラム定義と型メソッドの対応に注意する

FAQ

DriverManager と DataSource はどちらを使うべきですか?

本番ではコネクションプーリング付きの DataSource が推奨です。DriverManager は学習やツール用途に向いています。

ResultSet のカラムはインデックスと名前のどちらで取得すべきですか?

可読性と保守性の観点から列名指定(getString("name"))が安全です。列順の変更に影響されません。

H2 以外の DB で試すにはどうすればよいですか?

対象 DB の JDBC ドライバを CLASSPATH に追加し、接続文字列・ユーザー・パスワードを変更するだけです。

関連書籍

この記事のテーマをさらに深く学びたい方へ。

※ Amazon アソシエイトリンクを含みます