【Java】PreparedStatementのクエリー実行時に例外が発生する場合の対処方法の紹介(This method is not allowed for a prepared statement)

H2 DatabaseのPreparedStatement.executeQuery実行時に、次のような例外が発生することがあります。

org.h2.jdbc.JdbcSQLNonTransientException: プリペアドステートメントにこのメソッドは許されていません; かわりに通常のステートメントを使用してください
This method is not allowed for a prepared statement; use a regular statement instead. [90130-200]

これは、PreparedStatement.executeQueryの使用方法に誤りがあるため発生しています。次のサンプルプログラムを実行すると同様の現象が発生します。

String sql = "select * from examples where id = ?";
Connection connection = DriverManager.getConnection(url, user, passwod);
try (PreparedStatement statement = connection.prepareStatement(sql)) {
  statement.setInt(1, id);
  ResultSet rs = statement.executeQuery(sql);
} catch (SQLException exception) {
}

今回は、この例外が発生する原因と使用方法の誤りが発生する原因を合わせて紹介します。

スポンサーリンク

環境

検証に使用した環境/ライブラリを以下に記載します。

  • Java
    • Version:11
  • H2 Database
    • Version:1.4.200(2019-10-14)

原因

例外が発生する理由

PreparedStatement.executeQueryを使用する場合は、引数なしのメソッドを使用するのが正しいです。H2 DatabaseのPreparedStatementのリファレンスを確認すると引数ありのメソッドについては、次の記載があります。

executeQuery(String sql)
Calling this method is not legal on a PreparedStatement.
(訳:このメソッドの呼び出しは、PreparedStatementでは無効です。)

http://www.h2database.com/javadoc/org/h2/jdbc/JdbcPreparedStatement.html

このため、引数なしのメソッドを使用すると例外が発生する動作となります。

使用方法の誤りが発生する理由

PreparedStatement.executeQueryの使用方法の誤りが発生する理由の1つとして、Statement.executeQueryを使用している箇所をコピーして使用していることが考えられます。

次のようなStatement.executeQueryを使用しているサンプルプログラムがあります。

String sql = "select * from examples";
Connection connection = DriverManager.getConnection(url, user, passwod);
try (Statement statement = connection.createStatement()) {
  ResultSet rs = statement.executeQuery(sql);
} catch (SQLException exception) {
}

このサンプルプログラムをコピーして、PreparedStatement.executeQueryを使用する方法に修正した場合は、次のようなプログラムとなる可能性が高くなります。

String sql = "select * from examples where id = ?";
Connection connection = DriverManager.getConnection(url, user, passwod);
// 「Statement」を「PreparedStatement」に変更する。
// 「createStatement」を「prepareStatement」に変更して、引数が必要なため追加する。
try (PreparedStatement statement = connection.prepareStatement(sql)) {
  statement.setInt(1, id);
  // ここのメソッドの呼び方の修正を忘れる。
  ResultSet rs = statement.executeQuery(sql);
} catch (SQLException exception) {
}

Connection.createStatementからPreparedStatement.prepareStatementへ使用方法を変更した場合は、引数の指定が必須になります。このため、修正を忘れた場合でもコンパイルエラーになるため必ず気づくと思います。

ただし、PreparedStatement.executeQueryに指定している引数については、コンパイルエラーにならないため、削除するのを忘れてしまうことがあります。

まとめ

処理を流用した場合は、メソッドの使用方法が正しいことも確認する必要があります。

参考

H2 Database:PreparedStatementのリファレンス

JdbcPreparedStatement
declaration: package: org.h2.jdbc, class: JdbcPreparedStatement