JavaでExcel出力ならJETTが扱いやすい
JavaでExcel出力ならPOIが定番だけど、ラッパーライブラリである「JETT」が扱いやすかったので紹介します。
「Java Excel 出力」みたいなワードで検索すると、ほとんどがPOIのネタばかり。それくらいJavaでExcelといえばPOIなわけです。ただ、POI本体は非常にプリミティブなライブラリで、扱いは正直しんどい。コーディング量も多いし、Excelのバージョンにも悩まされた時期もありました。やはりコードがサクサク書けて、管理も楽チンみたいな感じでないとモチベーションが下がります。
過去には ExCella Reports という帳票ツールにも活躍してもらいましたが、こちらはMavenリポジトリに登録されていないため、ローカルでライブラリ管理しなければならず若干面倒です。おまけに最近は更新もされていないようだし。
JETT(Java Excel Template Translator)は、POIのラッパーライブラリで、Java 7以上で動作します。Mavenリポジトリにも登録されているためライブラリ管理も楽チン。コーディング量も少ないし、レスポンスも良い。しかも割と頻繁にバージョンアップしている感じ。なので色々と検証してみたので何回かに分けて紹介します。
ここでは JETTを使ってExcel出力 してみます。
Sponsored Links
環境
- Eclipse 4.6 Neon
- Java 8
- JETT 0.11.0
- Lombok 1.16.10
Sponsored Links
JETTでExcel出力する方法
JETTでExcel出力には、Excel(xlsx)のテンプレートファイルを用意し、出力コードを書きます。実務で利用する例として、見積書・発注書・納品書・請求書などがあるでしょうか。ヘッダ部、明細部、フッター部に分けられた単票ですね。ここでは下図のような請求書のテンプレートファイルを用意しました。
template_invoice.xlsxという名前で保存しました。
黄色部分にはJETTのタグが書いてあります。簡単に説明しておきます。
用途 | タグ | 使用箇所 | 説明 |
---|---|---|---|
請求書日付 | ${inv.invoiceDate} | ヘッダ部 | |
請求書番号 | ${inv.invoiceNumber} | ヘッダ部 | |
郵便番号 | ${inv.postCode} | ヘッダ部 | |
住所 | ${inv.address} | ヘッダ部 | |
顧客名 | ${inv.customerName} | ヘッダ部 | |
宛先担当者 | ${inv.contactPerson} | ヘッダ部 | |
請求額 | ${inv.invoiceAmount} | ヘッダ部 | |
費用項目 | 明細部 | リスト化して要素を出力する。 終わりの最終列にを記述する。 | |
数量 | ${dtl.quantity} | 明細部 | |
単価 | ${dtl.unitPrice} | 明細部 | |
金額 | ${dtl.amount} | 明細部 | |
備考 | ${dtl.remarks} | 明細部 | |
小計 | $[SUM(E13)] | フッタ部 | リスト化後の合計に使用できる。 5行展開した場合: $[SUM(E13)]⇒SUM(E13:E17) |
消費税 | ${inv.tax} | フッタ部 | |
立替金 | ${inv.reimburse} | フッタ部 | |
合計 | ${inv.invoiceAmount} | フッタ部 | |
コメント | ${inv.comment} | フッタ部 |
ここで利用しているもの以外にも実に様々なタグがあります。
詳しくは「JETT – Tag Basics」を参照してください。
Mavenプロジェクトを作成し、テンプレートファイルをresources内に保存しましょう。
<dependencies> <dependency> <groupId>net.sf.jett</groupId> <artifactId>jett-core</artifactId> <version>0.11.0</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.10</version> </dependency> </dependencies>
まずはテンプレートファイルにパラメータを指定し、Excelファイルへの変換をおこなうクラスを用意します。
・JettUtils.java
package utils; import java.io.IOException; import java.util.Map; import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import net.sf.jett.transform.ExcelTransformer; public class JettUtils { /** * テンプレートファイルにパラメータを指定し、Excelファイル変換をおこなう * * @param templatePath テンプレートパス * @param resultPath 作成ファイルパス * @param beans パラメータ * @return 処理結果 */ public static boolean ExcelTransform(String templatePath, String resultPath, Map<String, Object> beans) { try { ExcelTransformer transformer = new ExcelTransformer(); transformer.transform(templatePath, resultPath, beans); } catch (IOException e) { System.err.println("IOException reading " + templatePath + ": " + e.getMessage()); } catch (InvalidFormatException e) { System.err.println("InvalidFormatException reading " + templatePath + ": " + e.getMessage()); } return true; } }
ほぼ公式サイトに記載されているままですけど・・・^^;
次にDTOクラスを用意します。Lombokを使ってます。使ったことがない方は下記を参考にしてみてください。
・InvoiceDetail.java
package dto; import java.io.Serializable; import java.math.BigDecimal; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; @Setter @Getter @AllArgsConstructor public class InvoiceDetail implements Serializable { /** 費用項目 **/ private String item; /** 数量 **/ private Double quantity; /** 単価 **/ private BigDecimal unitPrice; /** 金額 **/ private BigDecimal amount; /** 備考 **/ private String remarks; }
・Invoice.java
package dto; import java.io.Serializable; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Date; import java.util.List; import lombok.Getter; import lombok.Setter; @Setter @Getter public class Invoice implements Serializable { /** 請求書日付 **/ private Date invoiceDate; /** 請求書番号 **/ private String invoiceNumber; /** 郵便番号 **/ private String postCode; /** 住所 **/ private String address; /** 顧客名 **/ private String customerName; /** 宛先担当者 **/ private String contactPerson; /** 請求額 **/ private BigDecimal invoiceAmount; /** 消費税 **/ private BigDecimal tax; /** 立替金 **/ private BigDecimal reimburse; /** コメント **/ private String comment; /** 費目明細 **/ private List<InvoiceDetail> details = new ArrayList<InvoiceDetail>(); }
最後にテストクラスを書きます。
・MakeInvoiceTest.java
package excel; import java.math.BigDecimal; import java.util.Date; import java.util.HashMap; import java.util.Map; import org.junit.Test; import dto.Invoice; import dto.InvoiceDetail; import utils.JettUtils; public class MakeInvoiceTest { @Test public void makeInvoiceTest() throws Exception { Invoice inv = new Invoice(); // ヘッダ部 inv.setInvoiceNumber("INV181213"); inv.setPostCode("〒131-1234"); inv.setAddress("東京都墨田区押上1丁目1-2"); inv.setCustomerName("株式会社アースツリースミダ"); inv.setContactPerson("墨田 太郎"); inv.setInvoiceDate(new Date()); // 明細部 inv.getDetails().add(new InvoiceDetail("ホームページ制作費", Double.valueOf(1), BigDecimal.valueOf(150000), BigDecimal.valueOf(150000), "一式")); inv.getDetails().add(new InvoiceDetail("ドメイン取得", Double.valueOf(1), BigDecimal.valueOf(0), BigDecimal.valueOf(0), "立替")); inv.getDetails().add(new InvoiceDetail("ドメイン契約代行手数料", Double.valueOf(1), BigDecimal.valueOf(3000), BigDecimal.valueOf(3000), "年間費")); inv.getDetails().add(new InvoiceDetail("サーバー利用料", Double.valueOf(12), BigDecimal.valueOf(500), BigDecimal.valueOf(12*500), "")); inv.getDetails().add(new InvoiceDetail("サーバー設定初期費用", Double.valueOf(1), BigDecimal.valueOf(5000), BigDecimal.valueOf(5000), "")); BigDecimal total = BigDecimal.ZERO; for (InvoiceDetail dtl : inv.getDetails()) { total = total.add(dtl.getAmount()); } // 消費税 inv.setTax(total.multiply(BigDecimal.valueOf(0.08))); // 立替金 inv.setReimburse(BigDecimal.valueOf(4980)); // 請求額 inv.setInvoiceAmount(total.add(inv.getTax()).add(inv.getReimburse())); // 備考 inv.setComment("年末は28日まで、年始は7日から営業"); // ファイルパス設定 String dir = Thread.currentThread().getContextClassLoader().getResource("").getPath(); System.out.println(dir); String templatePath = dir + "/template_invoice.xlsx"; String resultPath = dir + "/result_invoice.xlsx"; // Excel変換 Map<String, Object> map = new HashMap<String, Object>(); map.put("inv", inv); JettUtils.ExcelTransform(templatePath, resultPath, map); } }
ここまで作るとこんな感じになります。
test/resourcesにもテンプレートファイルをコピーするのを忘れずに
MakeInvoiceTestを実行すると・・・
おおおー、請求書がExcel出力されたー^^
JETTのレスポンスを図る
Javaで簡単にExcel出力できることがわかったJETTですが、Excel出力の速度はどうなんでしょうか。ブック内1シートに、30列/5000行に対して値を埋め込むと何秒くらいかかるのか、実測してみましょう。
まず、Excelでテンプレートを作ります。
30列なので、A列からAD列までを要素で埋めます。
列 | タグ | 説明 |
---|---|---|
A列 | リスト化して要素を出力する。 | |
B列 | ${data.col2} | 30個要素を作る。 |
・・・省略・・・ | ・・・省略・・・ | |
AD列 | ${data.col30} | |
AE列 | 終わりタグ。 |
template_response_check.xlsxという名前でresourcesに保存します。
次に、DTOクラスを作ります。
・ColItem.java
package dto; import java.io.Serializable; import lombok.Getter; import lombok.Setter; @Setter @Getter public class ColItem implements Serializable { private String col1; private String col2; private String col3; private String col4; private String col5; private String col6; private String col7; private String col8; private String col9; private String col10; private String col11; private String col12; private String col13; private String col14; private String col15; private String col16; private String col17; private String col18; private String col19; private String col20; private String col21; private String col22; private String col23; private String col24; private String col25; private String col26; private String col27; private String col28; private String col29; private String col30; }
最後にテストクラスを作ります。
・MakeResChkTest.java
package excel; import java.beans.IntrospectionException; import java.beans.PropertyDescriptor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import org.junit.Test; import dto.ColItem; import utils.JettUtils; public class MakeResChkTest { @Test public void makeResChkTest() throws Exception { // ファイルパス設定 String dir = Thread.currentThread().getContextClassLoader().getResource("").getPath(); System.out.println(dir); String templatePath = dir + "/template_response_check.xlsx"; String resultPath = dir + "/result_response_check.xlsx"; // データ作成 List<ColItem> datas = new ArrayList<ColItem>(); for (int i = 0; i < 5000; i++) { ColItem colItem = new ColItem(); for (int j = 0; j < 30; j++) { try { PropertyDescriptor prop = new PropertyDescriptor("col" + String.valueOf(j + 1), colItem.getClass()); Method setter = prop.getWriteMethod(); setter.invoke(colItem, "ColData" + String.valueOf(j + 1)); } catch (IntrospectionException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { e.printStackTrace(); break; } } datas.add(colItem); } Map<String, Object> map = new HashMap<String, Object>(); map.put("datas", datas); System.out.println(new Date()); JettUtils.ExcelTransform(templatePath, resultPath, map); System.out.println(new Date()); } }
ここまで作るとこんな感じになります。
test/resourcesにもテンプレートファイルをコピーするのを忘れずに。
MakeResChkTestを実行すると・・・
おおおー、30列/5000行が書き込まれたー^^
ちなみに 32Bit Win7 Core i5-3200M 2.6GHz 3.16GB RAM HDDのクソ遅いPCで、
Wed Dec 12 15:25:26 JST 2018
Wed Dec 12 15:25:46 JST 2018
という結果になりました。約20秒、十分な速度でしょう。
Sponsored Links
参考サイト
過去にPOIとかPOIラッパーライブラリを扱ったことがある方でしたら、本家サイトを見れば理解できちゃうと思います。
・JETT – Welcome to JETT
こちらは大変参考になりました。当記事が似たような記事になってしまいました^^;
・ExcelテンプレートエンジンJETTを利用して請求書を出力しよう
まとめ
JavaでJETTを使ってExcel出力する方法を紹介しました。
いかがでした?今回は「JavaでExcel出力ならJETTが扱いやすい」ということを紹介しました。
JETTには、シートコピーする方法とか、列にループする方法なんかも用意されています。
こちらは別の機会に紹介したいと思います。
おつかれさまでした。
Sponsored Links