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出力 してみます。
環境
- Eclipse 4.6 Neon
- Java 8
- JETT 0.11.0
- Lombok 1.16.10
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秒、十分な速度でしょう。
参考サイト
過去にPOIとかPOIラッパーライブラリを扱ったことがある方でしたら、本家サイトを見れば理解できちゃうと思います。
・JETT – Welcome to JETT
こちらは大変参考になりました。当記事が似たような記事になってしまいました^^;
・ExcelテンプレートエンジンJETTを利用して請求書を出力しよう
まとめ
JavaでJETTを使ってExcel出力する方法を紹介しました。
いかがでした?今回は「JavaでExcel出力ならJETTが扱いやすい」ということを紹介しました。
JETTには、シートコピーする方法とか、列にループする方法なんかも用意されています。
こちらは別の機会に紹介したいと思います。
おつかれさまでした。