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での使い方
INJava → DB に値を渡す(入力)setXxx(index, value)
OUTDB → 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 JDBCJDBCのラッパー。テンプレート的な扱い
JPAORM(JavaオブジェクトとDBの自動対応)
MyBatisSQLを明示的に書ける軽量ORM

☑ JDBCをきちんと理解しておくことで、これらのフレームワークへの理解も格段に深まります。

まとめ

ポイント内容
CallableStatementストアドを呼び出すためのJDBCインタフェース
IN/OUTパラメータ.setXxx() と .registerOutParameter() を併用
「index」は引数順番SELECTカラム順ではなく、ストアド引数順で指定する
複数の値を返す方法複数のOUTパラメータ or ResultSet
JDBCの吸収範囲接続・型・一部例外処理までは統一可能
実務とのつながりSpring/JPAへの橋渡しとしての重要な土台