2010年1月31日日曜日

【Oracle】JDBC経由でBLOBデータ挿入

BLOB型のカラムにJDBC経由でデータを挿入する方法。
Oracle11gのマニュアルには、BLOBよりも直接バインディングを使うとラウンドトリップを削減できるとある。
また、直接バインディングではバッチ実行を使った場合でも、1回のネットワーク操作で送信されるとあるので、もはやBLOBオブジェクトって使うことないのではないかと。(Integer#MAX_VALUEを超えるデータ量を、設定する場合はBLOBを使うしかないけど、そんなデータ量になることなんてまれな気もするし。)

直接バインディングの実装例
この実装でも、バインドされる値が4000バイトを超えると(4001バイト以上)ストリームバインディングになるらしい。
PreparedStatement statement = con.prepareStatement(
        "insert into blob_test (ID, BLOB_DATA) values (?, ?)");
try {
    statement.setString(1, "01");
    // BLOBに10進数の「12345」を設定
    statement.setBytes(2, new byte[] {0x31, 0x32, 0x33, 0x34, 0x35});
    statement.executeUpdate();
} finally {
    statement.close();
}

実行結果
insert文でバインドした値(0x31, 0x32, 0x33, 0x34, 0x35)が挿入されている。





ファイルなどのデータサイズが大きい物を登録する方法。
ファイルの場合であれば、ヒープ上にファイルの中身を読み込んでからバインドするのではなくInputStreamをバインドしてあげればよい。
この場合は、データサイズが4000バイトを超えるのでストリームバインディングになるらしい。またラウンドトリップが複数回になるので、それなりに性能面に与える影響があると思う。
PreparedStatement statement = con.prepareStatement(
        "insert into blob_test (ID, BLOB_DATA) values (?, ?)");
FileInputStream fis = null;
try {

    statement.setString(1, "02");
    // BLOBにFileInputStreamを設定
    File file = new File("C:\\work\\hoge.pdf");
    fis = new FileInputStream(file);
    statement.setBinaryStream(2, fis, (int) file.length());
    statement.executeUpdate();
} catch (FileNotFoundException e) {
    throw new RuntimeException("file io error.", e);
} finally {
    if (fis != null) {
        try {
            fis.close();
        } catch (IOException e) {
            // nop
        }
    }
    statement.close();
}

実行結果
でかめのPDFをinsertしてみたが、ちゃんとDBに登録されている。

2010年1月17日日曜日

VBAでの配列

VBAの配列は、JavaやCとは異なり要素数を指定するのではなく、添え字の最大数を指定するようになっている。なので、配列を0から始めた場合は0~最大数となりJavaやCの配列より要素数が1大きくなるのである。このことをきちんと理解していないと、思わぬ不具合が埋め込んでしまう可能性があるので注意が必要である。

個人的には、「Option Base」ステートメントを使用して配列の最小値を1に変更してあげて、添え字の最大数と要素数をイコールにしてあげることが好ましいと思う。


2010年1月16日土曜日

インスタンス生成のコスト

Javaでのインスタンス生成時のコストはかなり高いといわれている。このため、繰り返し処理(特にバッチ処理かな?)では、インスタンス生成の回数を減らすのが好ましいとのことである。

本当にそこまで気にしなければいけないのかを計測してみました。

・計測内容
JDK5とJDK6で下記処理を50万回行った際の処理時間を計測。
  1. List#newとList#clear
  2. StringBuffer#newとStringBuffer#setLength
  3. StringBuilde#newとStringBuilder#setLength

・計測結果
計測内容
JDK5
JDK6
List:new
3,422ms
3,032ms
List:clear
3,391ms
2,906ms
StringBuffer:new
4,375ms
3,750ms
StringBuffer:setLength
3,687ms
3,297ms
StringBuilder:new
4,000ms
3,421ms
StringBuilder:setLength
3,750ms
3,141ms

・結果
やはり、インスタンスを生成しなおすよりはArrayList#clearやStringBuffer#setLengthを使ったほうが早いことが分かりますね。
また、JDK6では処理の最適化が行われているのか全体的に性能は改善しています。ただし、インスタンス生成をした場合としなかった場合の、性能差はあまり変わっていません。

・どう対応すべきか
計測結果を見ると、明らかにインスタンス生成のコストがオーバヘッドになっていることが分かります。ただし、だからといって何が何でもインスタンス生成を控えるべきかといったら??な気がします。
実システムでは可読性や保守性も意識して作成しなければならないため、インスタンス生成のコストを削減することによって、それらを犠牲にすることはNGであるはずです。たとえば、参照渡しを多用していて値の書き変わる場所やタイミングが不明瞭になったり、クラス間の結合度が高くなったりするのはよろしくないですよね。
ただし、限られたスコープ内で閉じられている のであれば、積極的にコスト削減を行っていくべきですよ。
まぁ、インスタンス作成のコスト削減と可読性・保守性のトレードオフを見極めて適宜判断していくしかないのかなっと。