こんにちは、かつコーチです。
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());
}
executeQueryはResultSetを返し、rs.next()がtrueを返す間、1行ずつカーソルを進めながらgetInt・getStringで列の値を取り出します。
よくあるつまずきポイント・エラー対処
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に;区切りで複数のリソースを並べると、Connection・PreparedStatement・ResultSetをまとめて自動クローズでき、閉じ忘れによるリソースリークを防げます。
まとめ
この記事のポイント
- JDBCは
Connection・Statement・ResultSetの3つを軸にDBを操作する共通API - H2データベースはインメモリで動くため、インストール不要でJDBCの学習に向いている
- 外部からの入力を含むSQLは必ず
PreparedStatementのプレースホルダで組み立てる try-with-resourcesで複数のリソースをまとめて安全にクローズできる
次に読むべき記事
- ファイル入出力(java.nio.file)
- シリアライズの基本
タグ: Java, 中級者向け, 標準ライブラリ