Java Gold SE11対策:CallableStatementと応用編:ストアドプロシージャ・DB依存・ドライバの違いまで

JDBCの基本をマスターしたら、次のステップは ストアドプロシージャの呼び出しやデータベース間の違いを意識した設計です。
本記事では、CallableStatement を使った ストアドプロシージャ呼び出しの方法 を中心に、JDBCがどこまでDBの違いを吸収するか、そしてその限界について実務視点で整理します。
目次
CallableStatementとは?
ストアドプロシージャや関数を実行するために使うのが CallableStatement。
PreparedStatement に似ていますが、SQL文の形とパラメータの扱いが異なります。
CallableStatement cs = conn.prepareCall(“{call procedure_name(?, ?)}”);
- {call …} の形式が重要
- パラメータがある場合は ? プレースホルダを使う
- 実行は cs.execute() または cs.executeQuery() などを使用
IN / OUT パラメータの使い方
ストアドプロシージャは、引数の方向(IN / OUT / INOUT)を明示する必要があります。
これは SQL 側で定義されており、Java 側ではその方向に応じた書き方をする必要があります。
例:ユーザー名を取得するストアドプロシージャ
CREATE PROCEDURE getUserName (
IN userId INT,
OUT userName VARCHAR(100)
)
BEGIN
SELECT name INTO userName FROM users WHERE id = userId;
END;
📌 Javaの感覚では「引数で戻り値を受け取る」ような書き方は違和感があるかもしれません。
SQLでは関数のような戻り値ではなく、OUTパラメータに値を代入することで呼び出し元に返すという書き方をします。
Javaからの呼び出しコード
CallableStatement cs = conn.prepareCall(“{call getUserName(?, ?)}”);
// INパラメータ(ユーザーIDを渡す)
cs.setInt(1, 123);
// OUTパラメータ(ユーザー名を受け取る準備)
cs.registerOutParameter(2, Types.VARCHAR);
// 実行
cs.execute();
// OUTパラメータを取得(2番目の引数に入った値を取得する)
String userName = cs.getString(2);
📝 .registerOutParameter(2, Types.VARCHAR) の 「2」 は、SELECT 文のカラムではなく、ストアドプロシージャの2番目の引数(OUT)を指定しています。
この誤解は非常に多いため注意が必要です。
複数の OUT パラメータを使う場合
CREATE PROCEDURE getUserInfo (
IN userId INT,
OUT userName VARCHAR(100),
OUT userEmail VARCHAR(100)
)
BEGIN
SELECT name, email INTO userName, userEmail FROM users WHERE id = userId;
END;
CallableStatement cs = conn.prepareCall(“{call getUserInfo(?, ?, ?)}”);
cs.setInt(1, 123);
cs.registerOutParameter(2, Types.VARCHAR);
cs.registerOutParameter(3, Types.VARCHAR);
cs.execute();
String name = cs.getString(2);
String email = cs.getString(3);
パラメータの種類と使い方(Java側)
種類 | 説明 | JDBCでの使い方 |
IN | Java → DB に値を渡す(入力) | setXxx(index, value) |
OUT | DB → Java に値を返す(出力) | registerOutParameter(index, Types.Xxx) + getXxx(index) |
INOUT | 入出力の両方 | setXxx() + registerOutParameter() 両方 |
📝 注意点:
- OUT パラメータには setXxx() は不要
- INOUT は両方使う必要あり
複数行を返したい場合はどうする?
OUTパラメータで返せるのは 1つの値(スカラー値)だけです。
「ユーザー一覧」や「検索結果」のように複数行・複数列を返したい場合は、SELECT文を使って、ResultSet で受け取るのが基本です。
例:全ユーザーを返すストアドプロシージャ
CREATE PROCEDURE getAllUsers()
BEGIN
SELECT id, name FROM users;
END;
Java側:
CallableStatement cs = conn.prepareCall(“{call getAllUsers()}”);
ResultSet rs = cs.executeQuery();
while (rs.next()) {
int id = rs.getInt(“id”);
String name = rs.getString(“name”);
System.out.println(“ID: ” + id + “, Name: ” + name);
}
OUTパラメータとResultSetの使い分け
目的 | 使い方 | 例 |
単一の値を返す | OUTパラメータ | ユーザー名、カウントなど |
複数行・複数列を返す | SELECT文 + ResultSet | ユーザー一覧、検索結果など |
📌 実務では ResultSet を使うケースの方が圧倒的に多いです。
OUT パラメータは古いシステムや PL/SQL 系の処理で使われる傾向がありますが、Java では SELECT で返す方が自然です。
❗ よくある誤解:registerOutParameterの「index」はカラム順ではない
cs.registerOutParameter(2, Types.VARCHAR);
この「2」は、SELECT文の2列目ではなく、ストアドプロシージャの2番目の引数を指します。
複数カラムを返すには、OUTパラメータを複数定義するか、SELECT文を使ってResultSetで処理する必要があります。
SQL方言とJDBCの吸収範囲
JDBCは「DB間の違いを吸収する仕組み」とよく言われますが、完全に吸収できるわけではありません。
例 | JDBCが吸収できるか? | 備考 |
接続方法 | ✅ | Driverごとに対応 |
SQL文法の違い(LIMIT, ROWNUM等) | ❌ | 書き方がDB依存 |
データ型の違い(TEXT, CLOB等) | ❌(一部△) | DB固有の型がある |
ストアドプロシージャ構文 | ❌ | DBごとに構文異なる |
型情報(Types.VARCHAR など) | ✅ | JDBCで統一可能 |
JDBCでできる範囲・限界
項目 | JDBCでできる? | 備考 |
SQLの組立(PreparedStatement) | ✅ | DBに依存しない安全な組立 |
接続・実行・結果取得 | ✅ | ドライバが対応していればOK |
ストアドの呼び出し | ✅ | ただし構文はDB依存 |
DB間のSQL方言を吸収 | ❌ | LIMIT, OFFSET など構文が異なる |
データ型の違いを吸収 | △ | Types である程度対応、だが完全ではない |
JDBCの限界とその先へ(JPAやSpringの導入)
実務では、JDBCの次のステップとして以下のようなフレームワークが登場します:
技術 | 特徴 |
Spring JDBC | JDBCのラッパー。テンプレート的な扱い |
JPA | ORM(JavaオブジェクトとDBの自動対応) |
MyBatis | SQLを明示的に書ける軽量ORM |
☑ JDBCをきちんと理解しておくことで、これらのフレームワークへの理解も格段に深まります。
まとめ
ポイント | 内容 |
CallableStatement | ストアドを呼び出すためのJDBCインタフェース |
IN/OUTパラメータ | .setXxx() と .registerOutParameter() を併用 |
「index」は引数順番 | SELECTカラム順ではなく、ストアド引数順で指定する |
複数の値を返す方法 | 複数のOUTパラメータ or ResultSet |
JDBCの吸収範囲 | 接続・型・一部例外処理までは統一可能 |
実務とのつながり | Spring/JPAへの橋渡しとしての重要な土台 |