[Java]「ORA-01000:最大オープン・カーソル数を超えました」の注意点

先日出くわした「ORA-01000:最大オープン・カーソル数を超えました」というOracleDBのエラーについてメモ。

問題となったのは下記のようなコード
(業務コードを晒す訳には行かないため、サンプルとして書き直したもの。動作未確認。)


import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;

public class cursorLeakeSample {
	public cursorLeakeSample(List idList) {
		try {
			// Oracle JDBC Driverのロード
			Class.forName("oracle.jdbc.driver.OracleDriver");

			// コネクション取得
			Connection connection = DriverManager.getConnection(
					"jdbc:oracle:thin:@localhost:1521:ORCL", "scott", "tiger");

			String sql = "SELECT NAME FROM ITEM WHERE ID = ?";
			PreparedStatement statement = null;

			try {
				for (String id : idList) {

					Object[] params = new Object[] { id };

					// SQLにパラメータ埋め込みと実行
					statement = connection.prepareStatement(sql);
					statement.setObject(1, params[0]);
					statement.executeUpdate();

					connection.commit();
				}
			} catch (SQLException e) {
				// SQL実行失敗
				connection.rollback();
				e.printStackTrace();
			} finally {
				statement.close();
				connection.close();
			}
		} catch (SQLException e) {
			// コネクションの接続・切断・ロールバック、ステートメントの解放に失敗
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			// Oracle JDBC ドライバが見つからなかった
			e.printStackTrace();
		}
	}
}

強調してあるところが原因となった部分です。
なぜいけないのかわかりますか?
まず、変数statementを空箱として作成して、ループ内で取得した値を入れて使い回し、最後に解放。
一見、解放漏れを起こしてなさそうに見えるのですが、すこし処理を追うと問題を見つけることができます。

“prepareStatement”はSQLの事前準備のため暗黙でカーソルを作成します。
このことを知っていると問題のコードではループ内でたくさんの暗黙カーソルが生成されていることになります。
そして、解放されているのはループの外に抜けたときに格納されていた最後の”prepareStatement”の1つだけ。

たとえば、20件処理をしたなら19件は解放できていないというわけです。

では、どう書けばいいのか


import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;

public class cursorLeakeSample {
	public cursorLeakeSample(List idList) {
		try {
			// Oracle JDBC Driverのロード
			Class.forName("oracle.jdbc.driver.OracleDriver");

			// コネクション取得
			Connection connection = DriverManager.getConnection(
					"jdbc:oracle:thin:@localhost:1521:ORCL", "scott", "tiger");

			String sql = "SELECT NAME FROM ITEM WHERE ID = ?";
			PreparedStatement statement = connection.prepareStatement(sql);

			try {
				for (String id : idList) {

					Object[] params = new Object[] { id };

					// SQLにパラメータ埋め込みと実行
					statement.setObject(1, params[0]);
					statement.executeUpdate();

					connection.commit();
				}
			} catch (SQLException e) {
				// SQL実行失敗
				connection.rollback();
				e.printStackTrace();
			} finally {
				statement.close();
				connection.close();
			}
		} catch (SQLException e) {
			// コネクションの接続・切断・ロールバック、ステートメントの解放に失敗
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			// Oracle JDBC ドライバが見つからなかった
			e.printStackTrace();
		}
	}
}

たぶんこう。”PreparedStatement”を使用するなら。
ただの”Statement”を利用するならfor文内でclose();すればいいかと思います。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です