-DataSetとExcel
ひがさんのところでSeasar2の次バージョン追加機能としてExcelを操作できるDataSetの予告がありました。Java-Excelの処理を行うPOIのTipsもありました。今の仕事でちょうど既存の顧客データベースに対してシステム投資効果を計るためのROIを取る作業をしていて、その技術側面はDB + Seasar2 + POIなんですよ。ひがさんも似たようなことをちょうどやっていたのでしょうか。
DBからSesar2コンポーネントで結果セットを取ってくるところ、以下のようなつくりでやってます。
public interface ActionArg { public String getPrepareArgument(); } public interface Action { public void addArg(ActionArg arg); public void execute() throws Exception; } public interface Outputter { public void write(Action action) throws Exception; public void flash() throws Exception; } public interface ActionManager { public void addAction(Action action); public void executeActions() throws Exception; } <component name="roi1sql" class="analize.SQLFileArg"> <arg>'roi1sql.sql'</arg> <initMethod name="setDateParam"> <arg>0</arg> <arg>'2003-11-30'</arg> </initMethod> <initMethod name="setDateParam"> <arg>1</arg> <arg>'2004-03-16'</arg> </initMethod> </component> <component name="roi1" class="analize.SQLAction"> <initMethod name="addArg"> <arg>roi1sql</arg> </initMethod> <aspect pointcut="execute">backgroundRunningAdvice</aspect> </component> <component name="excelOutputter" class="analize.ExcelOutputter"> <arg>'ANALIZED_20040322.xls'</arg> </component> <component class="analize.DBCPActionManager"> <arg>excelOutputter</arg> <arg>connectionPool</arg> <initMethod name="addAction">roi1<property> <initMethod name="addAction">roi2<property> <initMethod name="addAction">roi3<property> <destroyMethod name="flashOutputter"/> </component> ActionManager manager = (ActionManager)container.getComponent(ActionManager.class); manager.executeActions();
コンテナの中で名前がユニークになれば同じ型のクラスでも登録できる例になります。<initMethod name="addAction">roi1<property>のようなやり方はTipsですね。その後、Excelに出力します。この場合、ActionとOutputterの依存性が内部でペアになりがちなうまくないつくりですが、次のSeasar2のDataSetみたいな仕組みで一回受ければ柔軟なつくりになるでしょう。そのうち、ExcelOutputterに続いてHTMLOutputterの需要が見えていて、丸ごとTapestryの後ろに入ることになりそうですから、そのうちリファクタリングしましょう。Seasar2のテスト兼ROIレポート作成の仕事でコードは納品物でないから、手を抜くところ抜いてます。
ExcelOutputterでは、結果セットをExcelのシートに単純に書き出してます。後で、その書き出しをExcelのピボットテーブルやグラフを使ってレポート体裁を整えています。Excelのピボットテーブルやグラフは、Javaでやるのはバカバカしいぐらい便利で高機能ですから、これはこれで一つのソリューションだと思います。惜しむらくは、POIにピボットテーブルを作る機能がまだ実装されていない(既知の制限事項)ので、そこが手動になってしまうということです。グラフはPOIで未確認ながらできるみたいですが、これも手でやってます。
POIのTipsです。
public static HSSFSheet createSheet(HSSFWorkbook wb, String name) { HSSFSheet ret = null; ret = wb.getSheet(name); if(ret != null) { wb.removeSheetAt(wb.getSheetIndex(name)); ret = null; } String dummy = (new Date()).toString(); ret = wb.createSheet(dummy); int i = wb.getSheetIndex(dummy); wb.setSheetName(i, name, HSSFWorkbook.ENCODING_UTF_16); return ret; } public static HSSFCell createCell(HSSFRow row, int col) { HSSFCell ret = row.createCell((short)col); ret.setEncoding(HSSFWorkbook.ENCODING_UTF_16); return ret; }
例外処理とか省略してますがPOIで日本語に対応するTipsです。HSSFWorkbook.ENCODING_UTF_16という定数をエンコーディングに指定しないと、マルチバイト文字が化けます。シート名に日本語をセットするcreateSheet(...)のほうは、POIのExcelシートを作るAPIでエンコーディングを指定できるものが見つからなかったので、一度日付から作ったダミー名で作ってから、シート名の変更をしてます。これはおそらくもっとうまいやり方があるはずだと思います。
ひがさんのBLOGフォローによると、createSheet(/*引数なし*/)メソッドでシートが最後尾に作られるの保証されているみたいですね。それなら、まずシートを作って、後に最後尾シートにsetSheetName(...)でOK。でももっとなんか直接的なのがあってもいいかなと。理想はcreateSheet(String name, short encoding)。