- PR -

ファイルアップロード後DB格納に失敗

1
投稿者投稿内容
uh-R
会議室デビュー日: 2008/04/23
投稿数: 6
投稿日時: 2008-04-23 17:49
ブラウザからファイルをアップロードし、ファイル名やサイズ、ファイル内容を DB に格納しようとしています。

preparedStatementを使用してインサートしていますが、インサート処理(preparedStatement.executeUpdate)を行った時点で SQLException が発生しています。

エラーメッセージ:
[IBM][CLI Driver][DB2/6000] SQL0301N EXECUTE または OPEN ステートメント内のホスト変数の値は、データ・タイプが適切でないため使用できません。 SQLSTATE=07006

原因としてどのようなことが考えられるか、ご教示いただけますでしょうか。


以下のような流れで処理を行っています。

コード:
FormFile fileUp = form.getFileUp();


String id = "001";
String name = fileUp.getFileName();
InputStream is = fileUp.getInputStream();
int size = fileUp.getFileSize();

String sql = "INSERT INTO FILE_TBL (ID, NAME, FILE, SIZE) VALUES (?, ?, ?, ?)";

preparedStatement = connection.prepareStatement(sql);

preparedStatement.setString(1, id);
preparedStatement.setString(2, name);
preparedStatement.setBinaryStream(3, is, is.available());
preparedStatement.setInt(4, size);

int result = preparedStatement.executeUpdate();



なお、ファイル内容は BLOB 型のカラムに挿入しているのですが、preparedStatementにパラメータを設定した際に該当カラムにマッピングされた値を見ると、InputStream 型ではなく ByteArrayInputStream 型になっています。この辺りも影響がありますか?

環境:
DB:DB2/6000 7.2.9(AIX Version 5.2)
J2EE 1.3
Struts 1.1


[ メッセージ編集済み 編集者: hbk3 編集日時 2008-04-23 20:16 ]

[ メッセージ編集済み 編集者: hbk3 編集日時 2008-04-23 20:17 ]
Gio
ぬし
会議室デビュー日: 2003/11/28
投稿数: 350
お住まい・勤務地: 都内から横浜の間に少量発生中
投稿日時: 2008-04-23 20:06
DB2 は触ったことがないので JDBC に関する部分のみですが。

preparedStatement 変数の型が書かれていませんが、これが java.sql.PreparedStatement であるならば、id や name といった String、int である size を PreparedStatement#setBinaryStream()でバインドするのはさすがに変でしょう。

ちなみに、教えを乞う場合は「教授」ではなく「教示」です。
uh-R
会議室デビュー日: 2008/04/23
投稿数: 6
投稿日時: 2008-04-23 20:14
ご指摘ありがとうございます。

引用:

preparedStatement 変数の型が書かれていませんが、これが java.sql.PreparedStatement であるならば、id や name といった String、int である size を PreparedStatement#setBinaryStream()でバインドするのはさすがに変でしょう。



すみません、コードを記述するときに簡略化して書いたのですがそのときの編集ミスでした。
修正しておきます。

引用:

ちなみに、教えを乞う場合は「教授」ではなく「教示」です。



勘違いしていました。ありがとうございます。
Gio
ぬし
会議室デビュー日: 2003/11/28
投稿数: 350
お住まい・勤務地: 都内から横浜の間に少量発生中
投稿日時: 2008-04-23 21:14
すみません、脊髄反射で書いてしまいました。
メソッドシグネチャは PreparedStatement#setBinaryStream(int, InputStream, int) なので、例示されたコードはコンパイルが通りませんね。
きちんと setString() や setInt() が使われており、コード記述時に間違えただけとして読み替えます。

DB2 の知識がないので「DB2 SQL0301N」で検索してみただけですが、以下のサイトによると、setBinaryStream() でバインドできるためには、カラムの型が longvarbinary であることを推奨、binary と varbinary は使用可能とあります。
http://www-1.ibm.com/support/docview.wss?uid=std3ae8c2716ab7375ca49256f1c0014e49e

つまり、カラム型が blob である場合、setBinaryStream() でバインドしてはいけないということです。
PreparedStatement#setBlob() を使うべきでしょう。

で、問題は InputStream から java.sql.Blob オブジェクトを作るにはどうすれば良いかというところに行き着きますが、JDK 1.4.x/JDK 5 の JDBC API を見た限りでは以下のような方法しか見当たりませんでした。

(1) Blob 以外のカラムだけバインドして INSERT を実行する
(2) (1) で登録したキーで SELECT して ResultSet#getBlob() で空の Blob を取得する
(3) (2) で取得した Blob に対して setBinaryStream(0)すると Blob に書き込むための OutputStream が返値として得られるので、これに内容を書き込む
(ここは直観的でなくて非常にわかりにくいです。setBinaryStream() への引数 0 は書き込むポジションを表すもので、新規登録の場合は 0 固定で良いでしょう。
既存のデータを更新する場合は、正の数を指定すると追記 or 後の方だけ上書きといった操作も可能です。)
(4) (1) と同じキーで UPDATE を実行して、ようやく DB 反映完了
(注: (1)〜(4) はアトミックに実行しないと DB 不整合が発生するかもしれません。)

なお、少々乱暴な方法ですが、InputStream から内容を byte[] として取得できれば、その値をそのまま Blob#setBytes(long, byte[]) してしまっても良いです。
(ファイルが長大でオンメモリで処理するのが危険な場合は Blob#setBytes(long, byte[], int, int) です。)

Blob を作る際、(1), (2) というしち面倒くさい処理が必要なのを疑問に思われると思います。
一発で Blob オブジェクトを作成する Connection#createBlob() というメソッドもあるにはあるのですが、これは JDK 6 (JDBC 4.0 仕様)からしかサポートされていません。
(DB2 の JDBC ドライバも JDBC 4.0 対応である必要があります。)
JDK 1.4.x や JDK 5 の場合は上記の方法が必要かと思います。

[ メッセージ編集済み 編集者: Gio 編集日時 2008-04-23 21:36 ]
uh-R
会議室デビュー日: 2008/04/23
投稿数: 6
投稿日時: 2008-04-24 17:03
Gio さん
回答ありがとうございます。

教えていただきました(1)〜(4)のやり方でやってみました。

(1)の段階で Blob 以外のカラムで Insert し、登録されたレコードを取得して ResultSet の getBlob() を実行したのですが、Blob 型のカラムには null が入っており、getBlob で取得した値も null となり setBinaryStream(0) を行うと NullPointerException が発生してしまいました。

Insert 時に Blob 型には null ではなくなにかしらの初期値を入れなければいけないのかと思い調べてみましたが、DB2 で該当するようなものが見つかりませんでした(Oracle では EMPTY_BLOB という関数があり、初期値を設定してくれるようなのですが)。

もし教えていただいたやり方を勘違いしているようでしたらご指摘ください。


また、DB2 でも Oracle と同様に null 以外の初期値を設定できる方法をご存知の方がいらっしゃいましたら教えていただけますでしょうか。
uh-R
会議室デビュー日: 2008/04/23
投稿数: 6
投稿日時: 2008-04-25 19:19
引用:

Insert 時に Blob 型には null ではなくなにかしらの初期値を入れなければいけないのかと思い調べてみましたが、DB2 で該当するようなものが見つかりませんでした(Oracle では EMPTY_BLOB という関数があり、初期値を設定してくれるようなのですが)。



CAST('' AS BLOB(1) ) を使用すれば空の Blob 型のレコードを追加できることがわかりました。

これで(1)のレコードの挿入はできたのですが、その後の setBinaryStream(0) でエラーが発生し、終了してしまいます。(Blob 型のデータは取得できているようです)

Exception にも引っかからず、どのような原因でエラーが発生しているか不明です。

何か心当たりがあればご教示お願いいたします。
kuma
大ベテラン
会議室デビュー日: 2004/02/25
投稿数: 110
投稿日時: 2008-04-25 21:38
setBinaryStream
OutputStream setBinaryStream(long pos)
throws SQLExceptionこの BLOB オブジェクトが表す Blob 値への書き込みに使用するストリームを取得します。ストリームは pos の位置から開始します。ストリームに書き込まれるバイトは、pos の位置から始まる Blob オブジェクトの既存バイトを上書きします。ストリームへの書き込み中に Blob 値の終わりに達すると、余分なバイトを格納するために Blob 値の長さが増加します。
os に指定された値が BLOB 値の length + 1 より大きい場合、動作は未定義です。JDBC ドライバによって、SQLException をスローするものもあれば、この操作をサポートするものもある可能性があります。


パラメータ:
pos - 書き込みを開始する BLOB 値内の位置。最初の位置は 1
戻り値:
データが書き込まれる java.io.OutputStream オブジェクト
例外:
SQLException - BLOB 値にアクセスするときにエラーが発生した場合、または pos が 1 より小さい場合
SQLFeatureNotSupportedException - JDBC ドライバがこのメソッドをサポートしない場合

# 追記
java5以前のsetBinaryStreamの項には書いてありませんが
getByteやpositionのを参考に

[ メッセージ編集済み 編集者: kuma 編集日時 2008-04-25 21:49 ]
uh-R
会議室デビュー日: 2008/04/23
投稿数: 6
投稿日時: 2008-04-25 22:52
kuma さん

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

引用:

注:Pos に指定された値が BLOB 値の length + 1 より大きい場合、動作は未定義です。JDBC ドライバによって、SQLException をスローするものもあれば、この操作をサポートするものもある可能性があります。



引用:

パラメータ:
pos - 書き込みを開始する BLOB 値内の位置。最初の位置は 1



ご指摘いただきましたように setBinaryStream(1) にしたり、Pos < Blob 値 + 1 になるように Blob 型の初期値に 1 バイトの文字を入れてみたりしましたが、動作は変わりありませんでした。

引き続き、何かございましたらよろしくお願いいたします。
1

スキルアップ/キャリアアップ(JOB@IT)