これはJava Advent Calendar 2016 ¥- Qiitaの10日目の記事です。
9日目の記事はdeaf_tadashiさんのマイクロベンチマークツール、JMHについて でした。
11日目は @leak4mk0 さんです。
このページについて
DBUnitを使用して、BLOB型カラムにテストデータ(主に画像とか、PDF)を入れたいときには、
XMLでそのファイルを指定する仕組みが用意されています。
が、今回は、EXCELからBLOB型カラムにテストデータを入れる方法を紹介したいと思います。
DBUnitを使ったことがない方は、使い方については、日本語情報が沢山あるので、
ぐぐってみてください。
書く目的
BLOB型カラムのデータをインポートする際に躓いたので、その共有です。
レアリティの高いシチュエーションだとは思いますが、
こんな方法でいけるぜ!というのを知っていただければ幸いです。
依存物
※ORACLEですが、恐らく他のDB製品も同じなんじゃないかなと思います
環境
OS : Redhat 6.5
Java : 1.8.20
DB : Oracle 12c
ライブラリ
DBUnit : 2.5.3
OJDBC : 8
ことの発端
冪等性のない単体テストコードの実行環境において(DBにデータありきだが、そのデータは人の手によって容易に変わる環境)、
テストコードがエラーになったら、原因を探して直せと言われるような開発の中、
これはイカンとCIを導入し、DBにデータありきなテストコードについては、今更そんなところまでは直せないので、
データを移行し、IDE上からいつでもデータを復元可能なようにDBUnitでインポートできるようにしとこう!という施策。
上記を行うことで、一先ずソフトの改造以外でテストが壊れる、ということを防げるようになって余計な工数かからないよ、
という点とCIをやることで得られる品質面のメリットを猛プッシュして導入しました。
本編
移行データをDBUnitでデータインポートに使うExcelに書き込む
SQL Developerからコピペするという荒業を使用して実施しました。
BLOB型カラムのデータはSELECT文でBASE64エンコードするストアドファンクション投げてコピペを実施。
※BLOBあっても、こうやればできるというのがありましたら、教えてください。
Excelに書き込んだテストデータをDBUnitで、EXCELからDBに書き込む(本題)
起きた問題
DBUnitのDatabaseOperation#executeを実行すると、
BLOB列の書き込み箇所で、ClassCastExceptionが発生して処理が落ちる。
原因
OJDBC側としては単純に、
「俺は、BLOB列にデータを書き込みたかったら、BlobObject使うのに、
お前の流し込んでるデータがbyteだから、キャストできねーよ!」
ということみたいです。
対策
読み込むときならともかく、自分でBLOBを書くときは、BlobObjectなんて使わないけどなー、と思いながらも無理くりbyteで書くために、
org.dbunit.dataset.ColumnのDataTypeをBINARYに変えてあげる必要がある、ってことで以下のコードを作成。
IDataSet databaseDataSet = con.createDataSet();
ITableMetaData tableMetaData = databaseDataSet.getTableMetaData(tableName);
//OraclePreparedStatementがBLOBカラムに対してBLOBオブジェクトを入れようとしてくるが、
//byteデータをセットしたいので、BINARYに変換する
//注意点として、ITableMetaData#columnsとIDataSet#ITableMetaData#columnsとの
//2重管理になっているため、両方ともBYNARYに更新する必要がある
ITable table = dataset.getTable(tableName);
Column[] cols = tableMetaData.getColumns();
for(int i = 0; i < cols.length; i++){
//BLOBのやつをBINARYに置き換えるぜい
if(DataType.BLOB.equals(cols[i].getDataType())){
Column col = new Column(cols[i].getColumnName(), DataType.BINARY);
//配列の参照関係は要素を自由に書き換えられるから、これを利用して目的を達成する
cols[i] = col;
int idx = table.getTableMetaData().getColumnIndex(cols[i].getColumnName());
table.getTableMetaData().getColumns()[idx] = col;
}
}
//データの流し込み
DatabaseOperation.CLEAN_INSERT.execute(con, dataset);
コメント中にもある通りで、参照状態のオブジェクトを無理矢理書き換えてあげることで実現。
※プロダクトコードでは、こんなコードを書いたらいけません。
これで、めでたくDBUnitを使ってDBにBLOB列データを書き込みができました。
同じ境遇に遭った人は是非どうぞ!
番外
何でXMLでリソースファイルの紐付ではいけなかったのか
BLOBデータが、システム側で作ったJavaのインスタンス(PDFとかイメージファイルではなく、業務処理の途中経過)だったため、
このような手法を取りました。
何でこんなことになっているのかは、突っ込まないでください。
※良い子は、マネしないでください。
あと、単純にExcelをDBのように使用する機会は多いため、
開発者がExcelにデータを定義できた方が、学習面で都合が良いためです。
まとめ
DBUnitを使用して、BLOB列にJDBC経由でデータを放り込む場合は、
ITableMetaDataとIDataSet#ITableMetaDataのcolumnsフィールドのDataTypeをBINARYにしてあげればOK!
という、誰得なテクニックをお届けしました。