フォーラム: 公開討議 (スレッド #35941)

ストリームの明示的なクローズ処理について (2014-09-05 10:04 by anonymous #74325)

お世話になります。

「TERASOLUNA Server Framework for Java WEB」2.0.5.0を利用しております。

ibatisでBLOBカラムのデータをjp.terasoluna.fw.orm.ibatis.support.BlobInputStreamTypeHandlerを使って取得した
InputStream型のプロパティがあるBeanクラスで明示的なストリームのクローズ処理は必要でしょうか?

例として、下記のようなコーティングでhogeBeanのfileBlobがInputStream型で
取得したあとにダウンロード用にDownloadInputStreamを生成するような処理です。

--------------------------------------------------------------------------------
HogeBean hogeBean
= queryDAO.executeForObject(SQL_ID, null, HogeBean.class);

try {
// ダウンロード用データ作成
AbstractDownloadObject downloadObject
= new DownloadInputStream(
hogeBean.getFileName(),
hogeBean.getFileBlob());

// ビジネスロジックの戻り値にセット
result.setResultObject(downloadObject);
} finally {
// クローズ処理
IOUtils.closeQuietly(hogeBean.getFileBlob());
}
--------------------------------------------------------------------------------

経験上、クローズ処理を意識して明示的にやってたのは、取得したInputStream型を別オブジェクトを
生成して処理を行うようなときで、上記のように直接Beanの中のストリームをクローズするような
処理はやったことがありません。

イメージ的にはhogeBeanが利用されなくなったらクラスのガベージ処理で
勝手にストリームもクローズされるようなことないのかなぁとか、、

上記のようにクローズを明示的に書かないと
FindBugsが「Streamがfinallyブロック中でクローズされていません」
とか静的チェックで言ってきたりするので気になり投稿させて頂きました。

メッセージ #74325 への返信×

Wiki文法は使えません
ログインしていません。投稿を区別するために投稿者のニックネームをつけてください(ニックネームの一意性は保証されません。全く別の人も同じ名前を利用することが可能ですので本人であることの特定には利用できません。本人であることを保証したい場合にはログインして投稿を行なってください)。 ログインする

Re: ストリームの明示的なクローズ処理について (2014-09-05 11:23 by taromaru #74326)

BLogicで、DownloadInputStreamに与えたInputStreamをcloseする必要はありません。

InputStreamをDownloadInputStreamに与えてから
BLogicResultに入れてreturnした場合、
DownloadBLogicActionまで処理が返ったあとで、
FileDownloadUtil#downloadにDownloadInputStreamが渡り、
レスポンスを返し終わってから、
FileDownloadUtilがDownloadInputStream内のInputStreamをcloseします。

よって、BLogicで、DownloadInputStreamに与えたInputStreamをcloseする必要はありません。
逆に、closeしてしまうと、FileDownloadUtilが内容を読めずに、ファイルの内容をレスポンスに載せることができないかと思います。

なお、現在のBLogic実装には、気づきにくい問題が潜んでいますので、
付け加えておきます。

まず、JDBCの仕様として、BLobの有効範囲はトランザクション内に限られます。

DownloadInputStreamに与えたInputStreamは、
BLogicの処理が終わってから読み取られるため、
BLogicにAOPでトランザクションをかける一般的なトランザクション境界では、
BLobの有効範囲外(トランザクション外)でBLobの内容を読もうとしてしまいます。
(Oracleでは、どういうわけか、読み取れてしまったりしますが、コネクションをコネクションプールに返してしまった後にDBと通信しているため、
場合によっては別のトランザクションが割り込んだり、ドライバのコネクションがクローズされることもあり得ます。)

対処としては、2つ方法があります。

<対処方法1>
DownloadBLogicActionにAOPでトランザクションをかけ、
DownloadBLogicAction実行時にトランザクションを開始し、
ダウンロード用のBLogicでは、そのトランザクション上で動作するようにする。
(ダウンロード用のBLogicは、トランザクションAOPの対象から外すか、REQUIREDにしておく。)

こちらの方法は、設定で解決するので、楽ですが、1つだけ懸念事項があります。
DownloadBLogicAction(インタフェースなし)にAOPをかけることになり、
AOPを実現するプロキシに、CGLibプロキシが利用されることになります。
CGLibはJava8では動かないという情報があります。

<対処方法2>
BLogicでBLobから取得したInputStreamを読み取り、一時ファイルに書き込み、
DownloadInputStreamには、一時ファイルのFileInputStreamを渡す。

こちらの方法は、一時ファイルの削除タイミングを別途検討する必要があります。
#74325 への返信

メッセージ #74326 への返信×

Wiki文法は使えません
ログインしていません。投稿を区別するために投稿者のニックネームをつけてください(ニックネームの一意性は保証されません。全く別の人も同じ名前を利用することが可能ですので本人であることの特定には利用できません。本人であることを保証したい場合にはログインして投稿を行なってください)。 ログインする

Re: ストリームの明示的なクローズ処理について (2014-09-05 11:46 by anonymous #74327)

素早い回答ありがとうございます。

「BLogicで、DownloadInputStreamに与えたInputStreamをcloseする必要はありません。」
とのことで了解しました。

なお、の別問題の話しですが
トランザクション外でBLobを読もうとすることで
実際に発生する事象としては内容が読み取れず正常に処理(この例でいうとダウンロード処理)が出来ないこともあり得るということでしょうか?

それとも、そのBLogic内でなんらかのDB更新処理があった場合にトランザクション外でのエラー発生でロールバック処理が走らないということでしょうか?

対処するにあたり発生事象を的確に認識したく
引き続きの質問となり恐縮ですが、ご回答頂きたく。
#74326 への返信

メッセージ #74327 への返信×

Wiki文法は使えません
ログインしていません。投稿を区別するために投稿者のニックネームをつけてください(ニックネームの一意性は保証されません。全く別の人も同じ名前を利用することが可能ですので本人であることの特定には利用できません。本人であることを保証したい場合にはログインして投稿を行なってください)。 ログインする

Re: ストリームの明示的なクローズ処理について (2014-09-05 12:26 by taromaru #74328)

> トランザクション外でBLobを読もうとすることで
> 実際に発生する事象としては内容が読み取れず正常に処理(この例でいうとダウンロード処理)が出来ないこともあり得るということでしょうか?
十分あり得ます。
まず、トランザクション外では、
コネクションはコネクションプールに返し終わっている状態です。
そのコネクションに割り当てられた物理コネクションが閉じられると、
DBとの通信が切れている状態になるので、データを取得することは不可能になります。
# ただし、ドライバのConnectionのcloseメソッドを実行したときに、
# 直ちに通信を切断するかは、JDBCドライバの実装によります。
物理コネクションが閉じられていない場合、経験上、データは取得できるのですが、
そのデータが正しく取得できる保証はありません。
(仕様外の挙動なので、仮に今大丈夫で、
DBにパッチを当てて動作に影響がでても、
有効範囲外で使用しているのがそもそもの誤りなので、
文句は言えないと思ってください。)

> それとも、そのBLogic内でなんらかのDB更新処理があった場合にトランザクション外でのエラー発生でロールバック処理が走らないということでしょうか?
トランザクション外ではロールバックできません。
BLogicにトランザクション境界があれば、
BLogicが終わった時点(ダウンロード前)で、すでにコミットされています。
(BLogic内から例外が上がった場合は、ロールバックされます。)
#74327 への返信

メッセージ #74328 への返信×

Wiki文法は使えません
ログインしていません。投稿を区別するために投稿者のニックネームをつけてください(ニックネームの一意性は保証されません。全く別の人も同じ名前を利用することが可能ですので本人であることの特定には利用できません。本人であることを保証したい場合にはログインして投稿を行なってください)。 ログインする

Re: ストリームの明示的なクローズ処理について (2014-09-05 13:36 by anonymous #74329)

taromaru様
回答ありがとうございます。

最初に教えて頂いた<対処方法2>で一時ファイルを作成する形で検討します。
(一時ファイルの削除タイミングは別途検討します)

ちなみに「DownloadInputStreamに一時ファイルのFileInputStreamを渡す」とのことでしたが
そもそもダウンロード用のオブジェクトにDownloadInputStreamではなく、DownloadFileを使って
IOUtils.copyなどでFileOutputStreamから書き込んだ一時ファイルを渡せば
FileInputStreamは使用しなくて良いと思うのですが

どちらのオブジェクトをダウンロード用で使うかでBLogic以降の処理で
何かメリデメや他の懸念点などはありますか?
#74328 への返信

メッセージ #74329 への返信×

Wiki文法は使えません
ログインしていません。投稿を区別するために投稿者のニックネームをつけてください(ニックネームの一意性は保証されません。全く別の人も同じ名前を利用することが可能ですので本人であることの特定には利用できません。本人であることを保証したい場合にはログインして投稿を行なってください)。 ログインする

Re: ストリームの明示的なクローズ処理について (2014-09-05 16:49 by taromaru #74331)

> どちらのオブジェクトをダウンロード用で使うかでBLogic以降の処理で
> 何かメリデメや他の懸念点などはありますか?
DownloadFileの方が、レスポンスにファイルサイズ(正のint)が入るため
正のintで表現可能なサイズであれば、
ブラウザでプログレスバーを表示できるはずです。
2GB以上の場合は、ファイルサイズ[byte]が正のintに収まらないため、
レスポンスにファイルサイズが入りません。
(DownloadInputStreamの場合は、常に、レスポンスにファイルサイズが入りません。)
そうなると、ブラウザはダウンロードするファイルのサイズを
ダウンロード完了時まで知ることができないため、
プログレスバーを表示することはできません。

よって、ファイルサイズは2GBはないが、
そこそこダウンロードに時間がかかるサイズである
(プログレスバーを確認したくなるサイズである)
という場合には、DownloadFileの方がお奨めといえます。

DownloadFile使用時に気をつけることは、
コンストラクタで与えるファイル名(最初に貼り付けられたBLogicではDBから取得)が
nullまたは空文字だった場合、ファイルシステム上のファイル名が、
ダウンロードファイル名として利用される点です。
APサーバで使用している一時ファイルのファイル名がクライアントで分かり、
OSやその他ソフトウェアの脆弱性との合わせ技で、
他者のダウンロードファイルの詐取の手助けになってしまう可能性があります。
そのため、
DBから取得したファイル名がnullや空文字列だった時には
固定のファイル名を使用するよう、
BLogicで実装することをお奨めします。

その他の懸念として、
今回はBLobから取得したデータなので無いとは思いますが、
ダウンロードするファイルのサイズが4GBを超える場合は、
DownloadFileを使用すると、
ファイルサイズをintにする際に、ファイルサイズより小さな正のintになってしまうことで、
レスポンスに誤ったファイルサイズが与えられてしまい、
ファイルの一部しかダウンロードできない事象が発生する可能性があります。
このような、特大のファイルをサポートする場合は、
DownloadInputStreamを選択することになります。
#74329 への返信

メッセージ #74331 への返信×

Wiki文法は使えません
ログインしていません。投稿を区別するために投稿者のニックネームをつけてください(ニックネームの一意性は保証されません。全く別の人も同じ名前を利用することが可能ですので本人であることの特定には利用できません。本人であることを保証したい場合にはログインして投稿を行なってください)。 ログインする

Re: ストリームの明示的なクローズ処理について (2014-09-05 20:34 by anonymous #74333)

taromaru様

様々な有益な情報ありがとうございます。
教えて頂いた情報を元に検討を進めます。
#74331 への返信

メッセージ #74333 への返信×

Wiki文法は使えません
ログインしていません。投稿を区別するために投稿者のニックネームをつけてください(ニックネームの一意性は保証されません。全く別の人も同じ名前を利用することが可能ですので本人であることの特定には利用できません。本人であることを保証したい場合にはログインして投稿を行なってください)。 ログインする