はじめに
Webシステムでデータをダウンロードする場合、CSV形式が一般的ですが、Excel形式で出力したいという要望も良くあります。これは、罫線やフォントの変更など見栄えのよい帳票が作成できることや、Excelが表計算アプリケーションとしてエンドユーザに広く受け入れられているためだと思われます。そこで本稿ではJavaからExcelファイルを出力しダウンロードする方法を紹介します。サンプルでは、請求書番号を入力すると、Excelにて作成された請求書がダウンロードできるという機能を実装しています。
対象読者
Javaプログラミングを行ったことがある、もしくは、Javaプログラミングに興味のある方を対象としています。
必要な環境
このサンプルはJ2SE(TM) Development Kit 5.0 Update 2、Tomcat5.5.7にて動作確認をしています。また、WebフレームワークのStruts1.2を使用していますが、サンプルソースに内包しているためJ2SDKとサーブレットコンテナ以外の環境は必要ありません。またExcelのインストールも必要ありません。環境についての詳細や設定方法については、「サーバサイド技術の学び舎 - WINGS」にある「サーバサイド環境構築設定」を参照してください。
サンプルアプリケーションの概要
サンプルアプリケーションの流れは以下の通りです。
- ブラウザにて、ダウンロードボタンが押される。
- リクエストを受け取ったサーブレットコンテナにて、テンプレートとして用意したExcelファイルを読み込む。
- 請求書固有の情報をExcelファイルに書き込む。
- ブラウザにExcelファイルを出力(ダウンロード)する。
タイトルや罫線など共通レイアウトは上図のようなテンプレートとしてあらかじめ作成しておき、顧客名称や金額といった請求書ごとに異なる値はプログラムから設定するようにします。レイアウトを動的に設定することも可能ですが、プログラムが複雑になってしまうのでテンプレートを作成しておくことを推奨します。
なお、サンプルを実際に動作させてみたい場合には、記事上部のリンクから、ファイル「PoiExcel.war」をダウンロードしてTomcatなどのアプリケーションサーバの「webapps」フォルダに配置(デプロイ)してください。アプリケーションサーバ起動後に、「http://localhost:8080/PoiExcel」にアクセスすればサンプルを動作させることができます(ホスト名・ポート番号は読者の環境に合わせて変更してください)。
また、今回のサンプルの主要ファイルは以下の通りです。
ファイル名 | 概要 |
invoice.xls | テンプレートとなるExcelファイル。 |
Input.jsp | Excelをダウンロードする画面のJSPファイル。 |
InvoiceAction.java | StrutsのActionクラス。 |
InvoiceForm.java | StrutsのActionFormクラス。 |
InvoiceService.java | ビジネスサービスを担当するクラス。 |
HeaderData.java | データを保持するクラス(ヘッダー部)。 |
DetailData.java | データを保持するクラス(明細部)。 |
POIとは
JavaからExcelファイルの読込や更新を行うためには「POI」と呼ばれるライブラリを使用します。POIはExcelやWordといったMicrosoftOLE2ドキュメントを操作するAPIを提供しており、Jakartaプロジェクトにて開発が進められています。入手するにはJakartaのサイトからダウンロードできます。なお、今回のサンプルをコンパイルするためには「poi-2.5.1-final-20040804.jar」にクラスパスを通しておく必要があります。
Excelファイルのダウンロード処理
それでは、ソースコードを見ながらアプリケーションの流れを見てみましょう。
リクエストの受付
ブラウザから「Excel出力」ボタンを押下すると、StrutsのAction
クラスのexecute
メソッドが実行されます。
public class InvoiceAction extends Action { public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { //請求書Noを元に請求書の詳細情報を取得し //「invoiceData」に設定します。 InvoiceForm iForm = (InvoiceForm) form; InvoiceService iService = new InvoiceService(); HeaderData invoiceData = iService.getInvoiceByInvoiceNo(iForm.getInvoiceNo()); //(以下省略) } }
ここでは、StrutsのActionForm
からリクエスト情報(請求書番号)を取り出し、ビジネスロジックにあたるInvoiceService
クラスから請求書の詳細データを取得しています。この処理にてinvoiceData
オブジェクトに帳票出力用のデータが設定されます。
Excelファイルの読込・更新
続いて以下のソースでは、Excelファイルの読込と更新を行っています。
public class InvoiceAction extends Action { public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { //(中略) //テンプレートとなるExcelファイルのパスを取得します。 final String INVOICE_FILE = "/WEB-INF/classes/to/msn/wings/invoice.xls"; String filePath = this.getServlet() .getServletContext().getRealPath(INVOICE_FILE); //ファイルを読み込みます。 POIFSFileSystem filein = new POIFSFileSystem(new FileInputStream(filePath)); //ワークブックを読み込みます。 HSSFWorkbook wb = new HSSFWorkbook(filein); //シートを読み込みます。 HSSFSheet sheet = wb.getSheet("invoice"); //行番号用の定数(縦) final int ROW_INVOICE_NO = 4; final int ROW_CUSTOMER_NAME = 6; final int ROW_DETAIL_START = 12; //列番号用の定数(横) final short COL_HEADER = 5; final short COL_DETAIL_GOODS_NAME = 3; final short COL_DETAIL_PRICE = 7; final short COL_DETAIL_COUNT = 8; final short COL_DETAIL_MEMO = 10; //請求書番号を設定します。 HSSFRow row = sheet.getRow(ROW_INVOICE_NO); HSSFCell cell = row.getCell(COL_HEADER); cell.setEncoding(HSSFCell.ENCODING_UTF_16); cell.setCellValue(invoiceData.getInvoiceNo()); //顧客名を設定します。 row = sheet.getRow(ROW_CUSTOMER_NAME); cell = row.getCell(COL_HEADER); cell.setEncoding(HSSFCell.ENCODING_UTF_16); cell.setCellValue(invoiceData.getCustomerName()); //明細行を設定します。 Iterator it = invoiceData.getDetailList().iterator(); int rowCount = 0; while (it.hasNext()) { row = sheet.getRow(ROW_DETAIL_START + rowCount); DetailData dd = (DetailData) it.next(); //商品名の設定 cell = row.getCell(COL_DETAIL_GOODS_NAME); cell.setEncoding(HSSFCell.ENCODING_UTF_16); cell.setCellValue(dd.getGoodsName()); //cell.setCellStyle(styleB); //価格の設定 cell = row.getCell(COL_DETAIL_PRICE); cell.setCellValue(dd.getPrice()); //数の設定 cell = row.getCell(COL_DETAIL_COUNT); cell.setCellValue(dd.getCount()); //摘要の設定 cell = row.getCell(COL_DETAIL_MEMO); cell.setEncoding(HSSFCell.ENCODING_UTF_16); cell.setCellValue(dd.getMemo()); //次の明細行へ rowCount++; } //(以下省略) } }
ここでは、Excelファイルを読み込み、請求書固有の値(顧客名や明細データ等)を設定しています。POIではまずワークブックにアクセスし、それから、ワークシート→行→列→セルという順番にたどっていくことで目的のセルにアクセスすることができます。
POIの主要クラスとメソッド
Excelファイルの読み込みと更新を行うための主要なクラスとメソッドは以下の通りです。
POIFSFileSystem
POIFSFileSystem
はExcelファイルを読み込む時に使用するクラスです。今回のサンプルでは特定の環境に依存しないように、パスを固定で埋め込まずServletContext
のgetRealPath
メソッドを使用して相対的にパスを読み込むようにしています。
HSSFWorkbook
HSSFWorkbook
はワークブックを扱うクラスです。getSheetメソッドでシート名を指定してHSSFSheet
オブジェクトを取得します。
HSSFSheet
HSSFSheet
はワークシートを扱うクラスです。getRow
メソッドを用いて、特定の行(HSSFRow
オブジェクト)を取得することができます。getRow
メソッドの引数はint
型で、0をベースとしているため1行目を取得したい場合は「0」を引数とします。
HSSFRow
HSSFRow
は行を扱うクラスです。getCell
メソッドを用いて特定の行(HSSFCell
オブジェクト)を取得することができます。getCell
メソッドの引数はshort
型で、0をベースとしているためA列を取得したい場合は「0」を引数とします。
HSSFCell
HSSFCell
はセルを扱うクラスです。setCellValue
メソッドでセルの値に引数の文字列や数値を設定することができます。なお、文字列に日本語が含まれる場合には、事前にsetEncoding(HSSFCell.ENCODING_UTF_16)
を呼び出しておく必要があります。
ダウンロードファイルの送信(レスポンス)
ここでは、作成したExcelファイルをブラウザに送ります。
public class InvoiceAction extends Action { public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { //(中略) //クライアントへのレスポンスを設定 response.setHeader("Content-Disposition", "attachment;filename=invoice.xls"); response.setContentType("application/vnd.ms-excel"); wb.write(response.getOutputStream()); //(中略) return null; } }
HSSFWorkbook
クラスのwrite
メソッドを呼び出してExcelファイルを出力しています。ここではHttpServletResponse#getOutputStream
メソッドによってServletOutputStream
を出力先に指定しています。
また、ダウンロード用に渡されたデータであることをブラウザ側に伝えるためにHttpServletResponse#setHeader
メソッドにてヘッダ情報を設定しています。そして、コンテンツタイプ(MIME)をHttpServletResponse#setContentType
メソッドにて設定しています。
この一連の流れにて、Excelファイルのダウンロード処理が完成となります。
TIPS
ここまでは、JavaからExcelを出力する方法を紹介しましたが、ここからはPOIを用いてプログラムをする上で便利なテクニックについて紹介します。
定数の利用
Excelのプログラムでは列番号と行番号を数字で指定してセルを特定しますが、その数字をそのまま書いてしまうのは避けたほうが良いでしょう。これは、プログラムに埋め込まれた数字の意味がわからないために後々バグの原因になる可能性があるためです(「マジックナンバー」と呼ばれ可読性が低下します)。そのため、定数を宣言することを推奨します。
//行番号用の定数(縦) final int ROW_INVOICE_NO = 4; final int ROW_CUSTOMER_NAME = 6; final int ROW_DETAIL_START = 12; //列番号用の定数(横) final short COL_HEADER = 5; final short COL_DETAIL_GOODS_NAME = 3; final short COL_DETAIL_PRICE = 7; final short COL_DETAIL_COUNT = 8; final short COL_DETAIL_MEMO = 10;
なお、今回のサンプルでは「COL
~」や「ROW
~」で始まる定数名にて統一しています。これはEclipseのような統合開発環境を使っている場合、自動的に定数の候補が表示されるというメリットがあるためです。
Excelの設定(R1C1参照形式)
通常、Excelの列の定義は「A~IV」というアルファベットで表示されていますが、Javaのプログラムからは数字で指定する必要があります。そのためPOIのプログラムをするときには、Excelのメニューの[ツール]-[オプション]で表示されるダイアログの[全般]-[R1C1参照形式を使用する]にチェックを入れておくと良いでしょう。この設定を行うと「A~IV」という表示が「1~256」という表示に変更されるため、アルファベットを数字に変換する手間が不要になります。複雑な帳票を作成するときには、この設定に変更しておくと便利でしょう。
まとめ
この記事では、JavaからPOIを使用してExcelを出力させる方法を解説しました。Excel出力のポイントを簡単にまとめると次のようになります。
- ファイルの読込・・・
POIFSFileSystem
クラス - ワークブックの読込・・・
HSSFWorkbook
クラス - シートの読込・・・
HSSFWorkbook#getSheet
メソッド - 行の読込・・・
HSSFSheet#getRow
メソッド - セルの読込・・・
HSSFRow#getCell
メソッド - セルのエンコードを設定・・・
HSSFCell#setEncoding
メソッド - セルに文字列を設定・・・
HSSFCell#setCellValue
メソッド
Excelに対するプログラムはサンプルと似たようなパターンがほとんどなので、この基本的な流れを押さえておくと良いでしょう。