JDBCの基本操作と実用的な応用テクニック

JDBC(Java Database Connectivity)は、Javaアプリケーションがリレーショナルデータベースと連携するための標準APIです。各データベースベンダーが独自のドライバーを提供し、JDBC仕様に準拠して実装します。

主な利点

  • 統一されたインターフェースで、異なるDBへの対応が容易
  • 学習コストが低く、初学者にも扱いやすい
  • パフォーマンスが高く、大規模データ処理にも対応可能

基本的な接続手順

  1. ドライバークラスを動的にロード
  2. データベースとの物理接続を確立
  3. SQL実行用オブジェクトを生成
  4. 結果セットを走査してデータ取得
  5. リソースを明示的に解放

Mavenプロジェクトでは、依存関係としてドライバーを追加するのが一般的です。手動でJARをlibフォルダに配置する場合は、ビルドパスに含める必要があります。

接続コード例

public class DatabaseConnector {
    public static void main(String[] args) {
        try (Connection conn = DriverManager.getConnection(
                "jdbc:mysql://localhost:5001/company?useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai",
                "admin", "password123")) {

            try (Statement stmt = conn.createStatement();
                 ResultSet rs = stmt.executeQuery("SELECT id, name, dept FROM staff WHERE dept = '開発部'")) {

                while (rs.next()) {
                    System.out.printf("%s - %d - %s%n", 
                        rs.getString("dept"),
                        rs.getInt("id"),
                        rs.getString("name"));
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

再利用可能なユーティリティクラス

public class ConnectionHelper {
    private static final String DRIVER = "com.mysql.cj.jdbc.Driver";
    private static final String URL = "jdbc:mysql://localhost:5001/company?useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai";

    static {
        try {
            Class.forName(DRIVER);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("ドライバーの読み込みに失敗しました", e);
        }
    }

    public static Connection establishConnection() throws SQLException {
        return DriverManager.getConnection(URL, "admin", "password123");
    }

    public static void releaseResources(ResultSet rs, Statement stmt, Connection conn) {
        if (rs != null) try { rs.close(); } catch (SQLException ignored) {}
        if (stmt != null) try { stmt.close(); } catch (SQLException ignored) {}
        if (conn != null) try { conn.close(); } catch (SQLException ignored) {}
    }
}

SQLインジェクション対策

ユーザー入力をそのままSQL文に埋め込むと、悪意のあるSQLコードが実行される危険があります。これを防ぐにはPreparedStatementを使用し、パラメータバインディングを行います。

トランザクション制御

複数の操作を論理的な単位として扱うには、手動コミットモードを有効にします。失敗時はrollback()で状態を復元できます。

try (Connection conn = ConnectionHelper.establishConnection()) {
    conn.setAutoCommit(false);
    String sql = "INSERT INTO staff (id, name, salary, dept) VALUES (?, ?, ?, ?)";
    
    for (int i = 5001; i <= 6000; i++) {
        try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
            pstmt.setInt(1, i);
            pstmt.setString(2, "スタッフ" + i);
            pstmt.setFloat(3, 4500.0f);
            pstmt.setString(4, "営業部");
            pstmt.executeUpdate();
        }
    }
    conn.commit();
} catch (Exception e) {
    if (conn != null && !conn.isClosed()) {
        conn.rollback();
    }
    throw e;
}

エンティティマッピングとページネーション

検索結果をJavaオブジェクトに変換することで、型安全な操作が可能になります。LIMIT句とオフセット計算でページ分割も容易です。

public class Staff {
    private Integer id;
    private String name;
    private Float salary;
    private String department;
    private java.sql.Date joinDate;

    // getter/setter省略
}

// ページ取得処理
String query = "SELECT * FROM staff LIMIT ?, 20";
try (PreparedStatement pstmt = conn.prepareStatement(query)) {
    pstmt.setInt(1, (pageNumber - 1) * 20);
    ResultSet rs = pstmt.executeQuery();
    List<Staff> page = new ArrayList<>();
    
    while (rs.next()) {
        Staff s = new Staff();
        s.setId(rs.getInt("id"));
        s.setName(rs.getString("name"));
        s.setSalary(rs.getFloat("salary"));
        s.setDepartment(rs.getString("dept"));
        s.setJoinDate(rs.getDate("join_date"));
        page.add(s);
    }
}

日付型の取り扱い

文字列形式の日付をデータベースに格納する際は、java.util.Datejava.sql.Dateへの変換が必要です。

SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-dd");
java.util.Date parsed = parser.parse(inputString);
java.sql.Date sqlDate = new java.sql.Date(parsed.getTime());

pstmt.setDate(5, sqlDate); // PreparedStatementに設定

バッチ処理による性能向上

大量のINSERT/UPDATEを個別に実行するとオーバーヘッドが大きくなります。addBatch()executeBatch()を組み合わせることで、ネットワークラウンドトリップを削減できます。

try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
    conn.setAutoCommit(false);
    for (int i = 10000; i < 20000; i++) {
        pstmt.setInt(1, i);
        pstmt.setString(2, "バッチ" + i);
        pstmt.setFloat(3, 3800.0f);
        pstmt.setString(4, "物流部");
        pstmt.addBatch();
    }
    pstmt.executeBatch();
    conn.commit();
}

コネクションプールの導入

DruidやHikariCPなどのプール管理ライブラリを使うことで、接続の再利用と負荷分散が可能になります。初期接続数と最大接続数は、アプリケーションの同時アクセス数に基づいて調整します。

Apache Commons DBUtilsの活用

定型的なJDBCコードを簡略化するためのユーティリティです。Beanマッピングや自動リソース管理機能により、生産性が向上します。

QueryRunner runner = new QueryRunner(dataSource);
List<Staff> results = runner.query(
    "SELECT * FROM staff WHERE dept = ?", 
    new BeanListHandler<>(Staff.class), 
    "人事部"
);

タグ: JDBC MySQL PreparedStatement トランザクション コネクションプール

6月25日 17:21 投稿