QA@IT
この質問・回答は、@ITの旧掲示板からインポートされたものです。

ResultSet/Statementのクローズについて。

いつもお世話になっております。

サーブレット(struts)でDBへアクセスしデータを検索、更新などを行っています。
通常、finally句でResultSetやStatementをクローズすると思いますが、
close()メソッドを実行すると、時々、SQLExceptionが発生します。
以下、SQLExceptionの一部です。
---------------
java.sql.SQLException: ResultSet is closed
at sun.jdbc.odbc.JdbcOdbcResultSet.checkOpen(JdbcOdbcResultSet.java:6650)
at sun.jdbc.odbc.JdbcOdbcResultSet.clearWarnings(JdbcOdbcResultSet.java:1767)
at sun.jdbc.odbc.JdbcOdbcResultSet.close(JdbcOdbcResultSet.java:1470)
---------------

そこで、周囲に相談したのですが、インスタンスが消えていないから、nullをセットしてみては、と言われ、その通りにやったのですが、SQLExceptionは発生しなくなりました。
以下、記述例です。
---------------
if (rs != null) {
rs.close();
rs = null;
}
または、
if (rs != null) {
rs = null;
}
---------------

そこで疑問があって、インスタンスの破棄はVMに委ねられていると思っていましたが、nullをセットしてインスタンスを直ぐに破棄出来るものなのでしょうか?
あと、close()メソッドの代わりにnullをセットしても、システム上、問題ないのでしょうか??

すみませんが、アドバイスのほど、よろしくお願いいたします。

質問者:たかし

回答

nullをセットしてもインスタンスの破棄はJavaVMに委ねられます。
該当オブジェクトに対し参照がないと判断されてからJavaVMによって破棄されます。
nullのセットはJavaVMに対して、
「ここのスコープでは参照していません。」
ということを通知しているに過ぎません。

ちなみに「close」と「nullをセット」は意味が違います。
大抵のJDBCドライバはfinalizeメソッドでcloseを自動で行っていますが、
GCが行われなければいつまでもcloseは行われません。

何かしらの原因にてResultSetが閉じた状態になっていると思います。
ですので、既に閉じられているResultSetに対して
closeを実行している為例外が発生しています。
Connectionの使いまわしとかしていませんかね。

投稿者:かつのり

編集 履歴 (0)

直接的な回答はかつのりさんがしてくれていますが、JDBCのコーディングは冗長になりがちなので(バグの遠因になりやすいので)、可能であればJakarta DbUtilsなどを使用することをお勧めします。
O/R mappingほど大掛かりではないので簡単に導入できます。

投稿者:K

編集 履歴 (0)

Sampleさんの書き込み (2005-02-04 00:37) より:

nullをセットしてインスタンスを直ぐに破棄出来るものなのでしょうか?

変数へのnull代入は通常は全く無意味、あるいは有害です。

あと、close()メソッドの代わりにnullをセットしても、システム上、問題ないのでしょうか??

非常に問題です。
close()で開放しなければならない貴重なリソースを開放しないままに放って置くことになりますから。
こういうのをリソースリークと呼びます。

if (rs != null) {

このrsをどこで宣言して、どう使っていますか?
おそらく変数rs及びResultSetオブジェクトの誤った共有を行っていると考えられます。
close()されないままのResultSetオブジェクトの残骸がたくさん残っていることでしょう。

投稿者:kito

編集 履歴 (0)

データベースやファイルなんかの後処理(closeなど)が例外を投げる可能性があると、finally節に書くにしても面倒ですね。moge さんの紹介している DBUtils なんかだと、例外を握りつぶしてしまう closeQuietly() などが用意されています。( Java の作法として良いかどうかはともかく。)

ResultSet の close() で例外が発生するのは、ほとんど「すでに閉じられている」のが原因でしょうから、closeQuietly() のように例外を握りつぶしてしまうのもアリだと思います。

投稿者:未記入

編集 履歴 (0)

根本原因は、変数の再利用にあるのではないでしょうか?

StatementやResultの変数に何度も参照を代入するようなコーディングをするよりも、
毎回変数を宣言したほうが安全だと思います。

StatementやResultSetの変数を再利用して同様のエラーが発生しているのを何度も見ましたから。。。

投稿者:YOU@IT

編集 履歴 (0)

返信、ありがとうございます。

動きについて、もう少し説明します。
セションにコネクションを保持しており、あるイベントが発生すると必ず、setAutoCommit(false)とconn.commit()、ResultSet#close()、Statement#close()を実行します。
なので、DBへアクセスの処理がない場合でも上の処理は行われます。

上の条件の下、イベントAでDBへアクセスし、次に、イベントBが発生した場合はDBへアクセスしないという動きになっています。

かつのりさんがご指摘しているように、ResultSetがイベントAで閉じられており(closeの実行)、次のイベントBの時にResultSetのオブジェクトが残っている状態でcloseを実行しようとしてSQLExceptionが発生しているように見えます。

Connectionの使いまわしとかしていませんかね。
セションにConnectionのオブジェクトを保持している状態なので、使いまわしています。
すみません、使いまわすことの危険性が分からないのですが・・・

このrsをどこで宣言して、どう使っていますか?
おそらく変数rs及びResultSetオブジェクトの誤った共有を行っていると考えられます。
close()されないままのResultSetオブジェクトの残骸がたくさん残っていることでしょう。
グローバル変数で宣言し、nullをセットしています。

StatementやResultの変数に何度も参照を代入するようなコーディングをするよりも、
毎回変数を宣言したほうが安全だと思います。
ローカル変数で宣言するべきでしょうか。。。

投稿者:たかし

編集 履歴 (0)

セションにコネクションを保持しており、あるイベントが発生すると必ず、

HttpSesion内にコネクションを保持するのはお勧めしません。
コネクションプールから必要なときに取得して、やることだけやってすぐにプールに返却する方法が一般的だと思います。

StatementやResultの変数に何度も参照を代入するようなコーディングをするよりも、

毎回変数を宣言したほうが安全だと思います。

ローカル変数で宣言するべきでしょうか。。。

Connection、Statement、ResultSetはローカル変数として宣言してfinally句でcloseする方法が一般的だと思います。

投稿者:永井和彦

編集 履歴 (0)

Sampleさんの書き込み (2005-02-04 18:48) より:

セションにコネクションを保持しており、

セッションにコネクションを保持してはいけません。
セッションの数だけ、使われない無駄なConnectionが開きっぱなしになってしまいます。

Connectionの使いまわしとかしていませんかね。

セションにConnectionのオブジェクトを保持している状態なので、使いまわしています。

すみません、使いまわすことの危険性が分からないのですが・・・

絶対にConnection変数/オブジェクトを使いまわしてはいけません。
複数スレッドから非同期に操作されて、ストレージとしてのDBの意味が全くなくなります。

例えば、一つのConnectionに対して
(スレッド1): executeUpdate("insert into tableA values (***)");
(スレッド2): conn.rollback();
(スレッド1): conn.commit();

の順に実行された場合、
スレッド1ではtableAに行を追加したつもりなのに実際は何も追加されないことになります。
こんなプログラム信用できますか?

このrsをどこで宣言して、どう使っていますか?

おそらく変数rs及びResultSetオブジェクトの誤った共有を行っていると考えられます。

close()されないままのResultSetオブジェクトの残骸がたくさん残っていることでしょう。

グローバル変数で宣言し、nullをセットしています。

絶対にグローバル変数にしてはいけません。Connectionと全く同じ理由です。
必ずローカル変数にする必要があります。

(スレッド1): rs = executeQuery("select * from tableA");
(スレッド2): rs = executeQuery("select * from tableB");
スレッド1の次のコードはどうなりますか?

ローカル変数で宣言するべきでしょうか。。。

Connection,PreparedStatement,ResultSetはすべてローカル変数にしましょう。絶対です。
共有していいのは唯一javax.sql.DataSourceオブジェクトだけです。

[ メッセージ編集済み 編集者: kito 編集日時 2005-02-04 19:23 ]

投稿者:kito

編集 履歴 (0)
ウォッチ

この質問への回答やコメントをメールでお知らせします。