はじめに
株式会社Good Labでエンジニアをしている コータロー です。
日々、Java・SQL・Gitなどの技術情報や、新人エンジニア向けの学習ノウハウ、
AI活用についての情報を発信しています。
Good Labについて気になった方は、コーポレートサイトもぜひご覧ください。
▶コーポレートサイト
「Java用語、結局なんだっけ?」シリーズの第2回です。
| 回 | テーマ |
|---|---|
| #1 | 環境・基盤編 |
| #2(本記事) | DB接続編 |
| #3 | Web/サーバー編 |
| #4 | 例外・スレッド編 |
| #5 | モダンJava編 |
| #6 | フレームワーク編 |
| #7 | ビルド・運用編 |
今回は DB接続編 です。「JDBC」「ドライバー」「コネクション」「コネクションプール」「データソース」「トランザクション」を一気に整理します。
この記事のゴール
- JDBC が 仕様 であり、ドライバー が実装である関係を説明できる
- コネクション・コネクションプール・データソースの違いを説明できる
- トランザクションの基本(コミット・ロールバック)を説明できる
全体像:JavaアプリがDBに繋がるまで
[Javaアプリ]
↓ JDBCのAPIを呼ぶ(仕様)
[JDBC API] ← java.sql パッケージ
↓
[JDBCドライバー] ← MySQL Connector/J、PostgreSQL JDBC Driver等(実装)
↓ DBプロトコルで通信
[DBサーバー] ← MySQL、PostgreSQL、Oracle等
そして実務では、コネクションプール や データソース が間に挟まります。
[Javaアプリ]
↓
[データソース(DataSource)] ← 接続管理のインターフェース
↓
[コネクションプール] ← 既存接続を再利用
↓
[JDBCドライバー]
↓
[DBサーバー]
これらの用語を6つに分けて解説します。
① JDBC ― 「Java からDBを操作する標準仕様」
一言で言うと
Java から DB を操作するための標準APIの仕様です。
Connection、Statement、ResultSet などのインターフェースが定義されています。
正式名称は Java Database Connectivity。
重要なポイント
JDBCは 「仕様」であって「実装」ではありません。
JDBC(仕様) ← java.sql.Connection, java.sql.PreparedStatement など
↓
JDBCドライバー(実装) ← MySQL用、PostgreSQL用、Oracle用 など別々
「Javaから何のDBにでも接続できる」のは、各DBベンダーが JDBC仕様に従ったドライバー を提供しているからです。
コード例
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class JdbcDemo {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/sampledb";
String user = "root";
String password = "password";
try (Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement stmt = conn.prepareStatement("SELECT id, name FROM users WHERE id = ?")) {
stmt.setInt(1, 1);
try (ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
System.out.println("ID=" + id + " name=" + name);
}
}
} catch (SQLException e) {
System.err.println("DB操作失敗: " + e.getMessage());
}
}
}
import文を見れば分かるように、Connection も PreparedStatement も ResultSet も java.sql パッケージ(JDBC仕様)に属しています。
よくある誤解
- 「JDBC は MySQL のためのもの」:JDBCは仕様で、MySQLでもPostgreSQLでもOracleでも使える共通API。
- 「JDBCドライバーをインストールすればDB操作ができる」:ドライバーは「接続部品」。実際にDBへの読み書きをするのはDBサーバー側。
② JDBCドライバー ― 「JDBC仕様の実装」
一言で言うと
**JDBC仕様を各DBベンダーが実装した「接続部品」**です。
通常はJARファイルとして提供されます。
種類
| DB | ドライバー名 | Maven依存例 |
|---|---|---|
| MySQL | MySQL Connector/J | mysql:mysql-connector-java |
| PostgreSQL | PostgreSQL JDBC Driver | org.postgresql:postgresql |
| Oracle | Oracle JDBC Driver | com.oracle.database.jdbc:ojdbc11 |
| SQL Server | Microsoft JDBC Driver | com.microsoft.sqlserver:mssql-jdbc |
| H2(インメモリ) | H2 Database | com.h2database:h2 |
何をしている?
- JDBC仕様の
Connection、PreparedStatement等を 具象クラスとして実装 - DB独自のネットワークプロトコル(例:MySQLバイナリプロトコル)を扱う
- JavaのオブジェクトとDBのデータ型を変換(
java.sql.Date↔DATETIME等)
ドライバーの読み込み(昔と今)
Java 6以前:明示的に Class.forName でドライバーを読み込む必要があった
// 昔のコード
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection(url, user, password);
Java 6以降:JARをクラスパスに置けば、DriverManager が自動で見つけてくれる(SPI機構)
// 現代のコード(Class.forName 不要)
Connection conn = DriverManager.getConnection(url, user, password);
たまに「Class.forName を書け」と古い記事に書かれていますが、現代では基本不要です。
よくある誤解
- 「JDBCドライバーはJDKに含まれている」:含まれていない。別途インストール(pom.xmlやbuild.gradleに記述)が必要。
- 「ドライバーを変えればDBが変わる」:URLとドライバーを差し替えれば、コード本体はほぼそのまま動く(これがJDBCの利点)。
③ コネクション(Connection) ― 「DBへの1本の通信回線」
一言で言うと
**Javaアプリ ⇔ DBサーバー間の1本の「電話線」**のようなものです。
このコネクションを使ってクエリを送り、結果を受け取ります。
特徴
- 重い:コネクションを作るには TCP接続+認証+セッション確立 が必要(数十〜数百ミリ秒)
- 限りがある:DBサーバー側に「同時接続数の上限」がある(MySQLデフォルト151)
- 状態を持つ:トランザクション、文字コード、自動コミット設定など
コネクションのライフサイクル
1. DriverManager.getConnection(...) ← 接続を開く(重い処理)
2. クエリ実行(SELECT / INSERT / UPDATE / DELETE)
3. conn.close() ← 接続を閉じる
毎回 getConnection ⇔ close を繰り返すと、接続コストが膨大 になります。
これを解決するのが次に説明する コネクションプール です。
よくある誤解
- 「コネクションは何個でも作っていい」:DBサーバーの上限に達すると 「Too many connections」エラーで本番障害が起きる。
- 「conn.close() しなくても、しばらくしたら勝手に閉じる」:閉じない。確実に閉じる必要 がある(try-with-resourcesが推奨)。
④ コネクションプール ― 「使い回しのプール」
一言で言うと
事前に複数のコネクションを作っておき、必要な時に貸し出す仕組みです。
プール=水泳プールではなく、「貯水池」のイメージ。
なぜ必要か
毎回コネクションを作ると遅すぎる。だから 「最初にまとめて作って、使い終わったら返してもらう」 方式にする。
[コネクションプール]
┌──────┬──────┬──────┬──────┐
│ conn1 │ conn2 │ conn3 │ conn4 │ ← 起動時に4本作成
└──────┴──────┴──────┴──────┘
↑
リクエストAが借りる
処理後、プールに返却
次のリクエストBが再利用
主なコネクションプール実装
| 名前 | 概要 |
|---|---|
| HikariCP | 業界標準。高速・軽量。Spring Bootのデフォルト |
| Apache DBCP | Apacheの歴史ある実装。Tomcatに同梱 |
| C3P0 | 古くからあるOSS。最近は採用減 |
新規案件でコネクションプールを選ぶなら、HikariCPの一択です。
設定例(Spring Boot)
spring.datasource.url=jdbc:mysql://localhost:3306/sampledb
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.connection-timeout=30000
maximum-pool-size=10 で「同時に10本までコネクションを保持」という意味です。
よくある誤解
- 「プールサイズは大きいほど高速」:違う。DB側の上限を超えると逆に詰まる。CPU数や同時接続要件から適切なサイズを計算する。
-
「コネクションプールを使えば conn.close() しなくていい」:違う。
close()は呼ぶ。ただし 実際には閉じずに、プールに返却 されるという挙動。
⑤ データソース(DataSource) ― 「接続管理の抽象インターフェース」
一言で言うと
Connection を取得するための標準インターフェースです。
javax.sql.DataSource として定義されています(javax.sql は Java SE 側のAPIのため、Jakarta EE の名前空間変更の対象外。今でも javax.sql.DataSource のままです)。
DriverManager との違い
| 観点 | DriverManager |
DataSource |
|---|---|---|
| 接続取得 | 毎回新規作成 | プールから貸し出し可能 |
| 設定 | コード内にURL等を埋め込む | 外部設定(プロパティ等)で管理 |
| 透過性 | DB依存が見える | 利用側はDB種類を意識しない |
| 推奨度 | レガシー・サンプル用 | 業務コードの標準 |
コード例
// 推奨:DataSourceを使う
@Autowired
private DataSource dataSource;
public void findUser(long id) {
try (Connection conn = dataSource.getConnection();
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users WHERE id = ?")) {
// ...
}
}
Spring Boot を使っていると、DataSource の実体(HikariCP等)は 自動で組み立てられて注入されます。
開発者は 接続URLや認証情報を application.properties に書くだけ でOK。
よくある誤解
- 「DataSource = データベース」:違う。接続を提供する仕組み。DBそのものではない。
-
「
DriverManagerで十分」:単体テストやサンプルでは十分。だが業務コードではプール対応のDataSourceが必須。
⑥ トランザクション ― 「複数SQLをまとめて成功/失敗させる単位」
一言で言うと
「全部成功するか、全部失敗するか」を保証する処理の単位です。
銀行送金(口座A→口座B)のように、途中で止まると不整合が出る処理 に必要。
例:送金処理
1. 口座Aから1万円引く
2. 口座Bに1万円足す
もし手順1だけ成功して手順2が失敗したら、1万円が消滅 します。
トランザクションを使えば、「両方成功したら確定」「途中で失敗したら全部巻き戻し」を実現できます。
コード例
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class TransactionDemo {
public static void transferMoney(long fromId, long toId, int amount) {
String url = "jdbc:mysql://localhost:3306/bank";
try (Connection conn = DriverManager.getConnection(url, "user", "pass")) {
conn.setAutoCommit(false); // 自動コミットOFF
try (PreparedStatement withdraw = conn.prepareStatement(
"UPDATE accounts SET balance = balance - ? WHERE id = ?");
PreparedStatement deposit = conn.prepareStatement(
"UPDATE accounts SET balance = balance + ? WHERE id = ?")) {
withdraw.setInt(1, amount);
withdraw.setLong(2, fromId);
withdraw.executeUpdate();
deposit.setInt(1, amount);
deposit.setLong(2, toId);
deposit.executeUpdate();
conn.commit(); // 両方成功した場合のみ確定
} catch (SQLException e) {
conn.rollback(); // どちらかが失敗したら全部巻き戻し
throw e;
}
} catch (SQLException e) {
System.err.println("送金失敗: " + e.getMessage());
}
}
}
ポイント:
-
setAutoCommit(false):自動コミットを止める(明示的に commit するまで確定しない) -
commit():すべて成功した時点で確定 -
rollback():失敗時に全部巻き戻し
Spring Boot ではアノテーション1個で済む
業務コードでJDBCを直接書くことはほぼなく、@Transactional アノテーション1個でトランザクション制御できます。
@Transactional
public void transferMoney(long fromId, long toId, int amount) {
accountRepository.withdraw(fromId, amount);
accountRepository.deposit(toId, amount);
}
メソッドが正常終了したら commit、例外が発生したら rollback が自動で実行されます。
ACIDの4特性(用語として知っておく)
| 特性 | 一言で |
|---|---|
| Atomicity(原子性) | 「全部成功 or 全部失敗」 |
| Consistency(一貫性) | 整合性が保たれる |
| Isolation(独立性) | 並行実行が互いに干渉しない |
| Durability(永続性) | コミットしたら消えない |
よくある誤解
- 「トランザクションは必ず必要」:違う。1個のSQLしか実行しない ならトランザクションは不要(自動コミットで十分)。
- 「rollback したらアプリも止まる」:違う。DBの変更が巻き戻るだけ。Javaコード上の処理は普通に続く。
用語まとめ早見表
| 用語 | 一言で | 関係性 |
|---|---|---|
| JDBC | DB接続の仕様 |
java.sql パッケージ |
| JDBCドライバー | JDBC仕様の実装(DBベンダー製) | JARで配布、クラスパスに追加 |
| Connection | DBへの1本の接続 | 重い、状態を持つ |
| コネクションプール | 接続を事前に作って使い回す仕組み | HikariCPが標準 |
| DataSource | 接続取得の抽象インターフェース | 業務コードはこちらを使う |
| トランザクション | 「全部成功 or 全部失敗」の単位 |
setAutoCommit(false) + commit/rollback
|
現場あるある誤解集
| ❌ 誤解 | ⭕ 正しい理解 |
|---|---|
| 「JDBC = MySQLのこと」 | JDBCは仕様。MySQL以外でも同じAPIで操作できる |
| 「ドライバーはOSにインストールするもの」 | JAR形式で配布、Mavenで依存追加するもの |
| 「コネクションは作りっぱなしでOK」 | 確実にcloseする。DB側の上限に達すると本番障害 |
| 「コネクションプールサイズは大きいほど良い」 | 大きすぎるとDB側が詰まる。CPU数等から計算 |
「DriverManager で本番運用OK」 |
プール未対応で遅い。DataSource を使う |
「@Transactional は何にでも付ければOK」 |
内部メソッド呼び出しでは効かない等の罠あり |
| 「1個のSQLでもトランザクションが必要」 | 自動コミットで十分。複数SQLをまとめる時だけ必要 |
演習問題
問題1:JDBCとドライバーの関係 ⭐
次の文章の空欄を埋めてください。
「JDBCは ___ であって、実装ではない。各DBベンダー(MySQL、PostgreSQL等)が JDBC___ を提供している。Java側のコードは ___ パッケージのAPIを呼ぶだけで、DBの種類によらず動く。」
模範解答
「JDBCは 仕様 であって、実装ではない。各DBベンダー(MySQL、PostgreSQL等)が JDBCドライバー を提供している。Java側のコードは java.sql パッケージのAPIを呼ぶだけで、DBの種類によらず動く。」
ポイント:「仕様(インターフェース)と実装(具象クラス)の分離」がJDBCの設計の肝。これによりJavaコードがDB非依存になります。
問題2:コネクションプール ⭐
コネクションプールについて、間違っている 説明はどれですか?
- A. 起動時にあらかじめ複数の接続を作っておく
- B. 接続を使い終わったら破棄して、次の処理で新規作成する
- C. HikariCPがSpring Bootのデフォルト
- D. プールサイズは大きすぎるとDB側が詰まる
模範解答
正解:B
解説:
-
B はNG:コネクションプールは「使い終わったら 返却して再利用 」する仕組み。破棄しません。
conn.close()は呼びますが、実体は「プールに返す」動作になります。 - A、C、D は正しい
ポイント:「conn.close() が呼ばれた時に本当に物理的に閉じるかどうか」は、DataSource の実装次第。プール経由なら返却、DriverManager 経由なら本物のclose。
問題3:トランザクション ⭐
次の処理にトランザクションが必要なのはどれですか?(複数選択可)
- A. ユーザー1件をINSERTする
- B. 商品リストをSELECTする
- C. 注文を作成し、在庫を1減らす
- D. ログテーブルに1件INSERTする
- E. 口座Aから1000円引き、口座Bに1000円足す
模範解答
正解:C と E
解説:
- C はトランザクションが必要:「注文作成」と「在庫減算」が両方成功しないと不整合(注文だけ作って在庫が減らない、等)
- E はトランザクションが必要:典型的な「全部成功 or 全部失敗」のケース
- A、B、D は 単一のSQL なので、自動コミットで十分
ポイント:トランザクションが必要かは「複数のSQLが論理的にひとまとまり か」で判断します。1個ずつ独立しているなら不要です。
まとめ
DB接続まわりの6用語のおさらいです。
-
JDBC:DB接続の標準仕様(
java.sql) - JDBCドライバー:JDBC仕様の実装(DBベンダー製、JAR配布)
- Connection:DBへの1本の接続(重い・有限)
- コネクションプール:接続の使い回し機構(HikariCPが標準)
- DataSource:接続取得の抽象インターフェース(業務コードはこれを使う)
- トランザクション:「全部成功 or 全部失敗」の単位
新人のうちに「仕様と実装の分離」「重い接続をどう使い回すか」という設計思想を掴んでおくと、Spring Boot や JPA に進んでも迷いません。
次回予告
次回(#3)は Web/サーバー編 です。
- サーブレット・JSP・Tomcatの関係
- WARファイルとは何か
- フィルタの役割
- ステートフル/ステートレスの違い
を、現場で出会う形式で解説します。
参考
@kotaro_ai_lab
AI活用や開発効率化について発信しています。フォローお気軽にどうぞ!