【Java】JavaのJDBCでデータベースに接続する基本

Java

こんにちは、かつコーチです。
Spring Bootのような便利なフレームワークを使う前に、「Javaは素の状態でどうやってデータベースにつながっているのか」を知りたくなったことはありませんか。
今回はJavaの標準APIJDBC(Java Database Connectivityの略で、データベース接続の共通仕様)を使い、軽量なH2データベース(Javaだけで動くインメモリ・組み込みデータベース)を例に接続の基本を解説します。

JDBCとは?

JDBCの役割

JDBCは「JavaプログラムとDBを接続するための共通ルール(インターフェース)」です。
MySQLでもPostgreSQLでもH2でも、JDBCのルールに沿ったドライバ(DB製品ごとの接続実装)さえあれば、Java側のコードはほぼ同じ書き方で扱えます。

  • Connection(DBとの接続そのものを表すオブジエクト)
  • Statement / PreparedStatement(SQLを実行するためのオブジェクト)
  • ResultSet(SELECTの実行結果を1行ずつ取り出せるオブジェクト)

この3つがJDBCの基本セットです。

なぜこの記事ではH2を使うのか

MySQLなどは別途サーバーのインストールが必要ですが、H2データベースはJarファイル1つで動く軽量なDBで、インメモリモード(メモリ上だけで動作し、プログラム終了で消える)を使えばインストール不要で試せます。
JDBCの基本を学ぶには最適な選択肢です。

基本の書き方

H2への接続とテーブル作成

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

public class JdbcSample {
    public static void main(String[] args) {
        String url = "jdbc:h2:mem:testdb"; // メモリ上に一時的なDBを作る

        try (Connection conn = DriverManager.getConnection(url, "sa", "")) {
            try (Statement stmt = conn.createStatement()) {
                stmt.execute("CREATE TABLE members (id INT PRIMARY KEY, name VARCHAR(50), age INT)");
                stmt.execute("INSERT INTO members VALUES (1, 'かつコーチ', 35)");
                stmt.execute("INSERT INTO members VALUES (2, 'Aさん', 22)");
                System.out.println("テーブル作成とデータ登録が完了しました。");
            }
        } catch (SQLException e) {
            System.out.println("DB操作でエラーが発生しました: " + e.getMessage());
        }
    }
}

DriverManager.getConnectionにJDBC URL(jdbc:h2:mem:testdb)とユーザー名・パスワードを渡すとConnectionが得られます。
try-with-resourcesを使うことで、処理が終わったときに自動的に接続をクローズできます。

SELECTでデータを取得する(ResultSet)

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

String url = "jdbc:h2:mem:testdb";

try (Connection conn = DriverManager.getConnection(url, "sa", "");
     Statement stmt = conn.createStatement();
     ResultSet rs = stmt.executeQuery("SELECT id, name, age FROM members")) {

    while (rs.next()) {
        int id = rs.getInt("id");
        String name = rs.getString("name");
        int age = rs.getInt("age");
        System.out.println(id + ": " + name + "(" + age + "歳)");
    }
} catch (SQLException e) {
    System.out.println("検索でエラーが発生しました: " + e.getMessage());
}

executeQueryResultSetを返し、rs.next()trueを返す間、1行ずつカーソルを進めながらgetIntgetStringで列の値を取り出します。

よくあるつまずきポイント・エラー対処

Statementに変数を埋め込んでSQLインジェクションの危険を作ってしまう

実際に私が初めてJDBCを触ったとき、文字列連結でSQLを組み立ててしまい、後からSQLインジェクション(悪意ある入力でSQL文を改ざんされる脆弱性)の危険があると指摘された経験があります。

// ❌Before:ユーザー入力をそのまま文字列連結してしまう
String inputName = "'; DROP TABLE members; --"; // 悪意あるユーザーの入力例
String sql = "SELECT * FROM members WHERE name = '" + inputName + "'";
stmt.executeQuery(sql); // テーブルが削除されてしまう危険がある

このコードはinputNameにSQLとして解釈される文字列が入り込むと、意図しないSQLが実行されるSQLインジェクションの脆弱性を生みます。
Statementは文字列をそのままSQLとして送るため、外部からの入力を直接埋め込むのは危険です。

// ✅After:PreparedStatementでプレースホルダを使う
import java.sql.PreparedStatement;
import java.sql.ResultSet;

String inputName = "'; DROP TABLE members; --";
String sql = "SELECT * FROM members WHERE name = ?";

try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
    pstmt.setString(1, inputName); // 値として安全に扱われる
    try (ResultSet rs = pstmt.executeQuery()) {
        while (rs.next()) {
            System.out.println(rs.getString("name"));
        }
    }
}

PreparedStatement(プレースホルダ?にパラメータを安全に渡せるStatementの拡張)を使えば、値はSQL文の構文として解釈されず単なるデータとして扱われるため、SQLインジェクションを防げます。
JDBCを使うときは、原則StatementではなくPreparedStatementを使うのが実務のルールです。

応用・一歩先の使い方

INSERT・UPDATEもPreparedStatementで統一する

String sql = "INSERT INTO members (id, name, age) VALUES (?, ?, ?)";

try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
    pstmt.setInt(1, 3);
    pstmt.setString(2, "Bさん");
    pstmt.setInt(3, 28);
    pstmt.executeUpdate();
}

SELECT以外の更新系SQLも同じPreparedStatementで統一すると、コードの書き方に一貫性が生まれます。

try-with-resourcesで複数リソースをまとめてクローズする

try (Connection conn = DriverManager.getConnection(url, "sa", "");
     PreparedStatement pstmt = conn.prepareStatement("SELECT * FROM members");
     ResultSet rs = pstmt.executeQuery()) {

    while (rs.next()) {
        System.out.println(rs.getString("name"));
    }
} catch (SQLException e) {
    System.out.println("エラー: " + e.getMessage());
}

try-with-resources;区切りで複数のリソースを並べると、ConnectionPreparedStatementResultSetをまとめて自動クローズでき、閉じ忘れによるリソースリークを防げます。

まとめ

この記事のポイント

  • JDBCはConnectionStatementResultSetの3つを軸にDBを操作する共通API
  • H2データベースはインメモリで動くため、インストール不要でJDBCの学習に向いている
  • 外部からの入力を含むSQLは必ずPreparedStatementのプレースホルダで組み立てる
  • try-with-resourcesで複数のリソースをまとめて安全にクローズできる

次に読むべき記事

  • ファイル入出力(java.nio.file)
  • シリアライズの基本

タグ: Java, 中級者向け, 標準ライブラリ

タイトルとURLをコピーしました