JavaでExcel出力ならJETTが扱いやすい

JavaでExcel出力ならPOIが定番だけど、ラッパーライブラリである「JETT」が扱いやすかったので紹介します。

JavaでExcel出力なら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)のテンプレートファイルを用意し、出力コードを書きます。実務で利用する例として、見積書・発注書・納品書・請求書などがあるでしょうか。ヘッダ部、明細部、フッター部に分けられた単票ですね。ここでは下図のような請求書のテンプレートファイルを用意しました。

JETT用請求書テンプレートファイルを用意

template_invoice.xlsxという名前で保存しました。

黄色部分にはJETTのタグが書いてあります。簡単に説明しておきます。

用途タグ使用箇所説明
請求書日付${inv.invoiceDate}ヘッダ部 
請求書番号${inv.invoiceNumber}ヘッダ部 
郵便番号${inv.postCode}ヘッダ部 
住所${inv.address}ヘッダ部 
顧客名${inv.customerName}ヘッダ部 
宛先担当者${inv.contactPerson}ヘッダ部 
請求額${inv.invoiceAmount}ヘッダ部 
費用項目${dtl.item}明細部リスト化して要素を出力する。
終わりの最終列にを記述する。
数量${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;
  }
}

ほぼ公式サイトに記載されているままですけど・・・^^;

JETT – Beans Map

次に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);
  }
}

ここまで作るとこんな感じになります。

JETTでExcel出力する方法クラス構成

test/resourcesにもテンプレートファイルをコピーするのを忘れずに

MakeInvoiceTestを実行すると・・・

JETTでExcel出力MakeInvoiceTestを実行

おおおー、請求書がExcel出力されたー^^

JETTのレスポンスを図る

Javaで簡単にExcel出力できることがわかったJETTですが、Excel出力の速度はどうなんでしょうか。ブック内1シートに、30列/5000行に対して値を埋め込むと何秒くらいかかるのか、実測してみましょう。

まず、Excelでテンプレートを作ります。

JETTのレスポンスを図るExcelテンプレートを作る

30列なので、A列からAD列までを要素で埋めます。

タグ説明
A列${data.col1}リスト化して要素を出力する。
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());
  }
}

ここまで作るとこんな感じになります。

JETTのレスポンスを図るクラス構成

test/resourcesにもテンプレートファイルをコピーするのを忘れずに。

MakeResChkTestを実行すると・・・

JETTのレスポンスを図る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には、シートコピーする方法とか、列にループする方法なんかも用意されています。

こちらは別の機会に紹介したいと思います。

おつかれさまでした。

この記事がお役に立ちましたら シェア をお願いいたします。