Excelデータのインポート・エクスポート機能を最適化する設計手法

導入と目的

本稿では、業務システムにおけるテーブルデータのインポート・エクスポート機能の実装方法について、Alibaba EasyExcelを利用した具体的な実装パターンと設計上の注意点を紹介します。特に、大量データ処理時のメモリ効率やエラー処理の設計、フロントエンドとの連携における制約に焦点を当てます。

主要ライブラリ:EasyExcelの概要

EasyExcelは、大容量のXLSXファイルを低メモリで読み書き可能なJavaライブラリです。3.0.2以降では、64MBのメモリ環境でも75MB(46万行×25列)のExcelファイルを20秒以内に読み込み可能です。

// 簡単な読み取り例
String fileName = "data.xlsx";
EasyExcel.read(fileName, UserData.class, new UserReadListener()).sheet().doRead();
// 簡単な書き出し例
String outputPath = "output_" + System.currentTimeMillis() + ".xlsx";
EasyExcel.write(outputPath, UserData.class).sheet("ユーザー一覧").doWrite(userList);

エクスポート機能の実装

1. 単一シート出力

HTTPレスポンスのOutputStreamに直接書き込むことで、ダウンロード形式の出力を実現します。コンテンツタイプと文字エンコーディングの設定が重要です。

@RequestMapping("/export/single")
public void exportSingle(HttpServletResponse response) throws IOException {
    response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
    response.setCharacterEncoding("UTF-8");
    String filename = URLEncoder.encode("ユーザー情報", "UTF-8").replaceAll("\\+", "%20");
    response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + filename + ".xlsx");

    EasyExcel.write(response.getOutputStream(), UserDTO.class)
        .inMemory(true)
        .sheet("ユーザー一覧")
        .doWrite(userService.findAll());
}

2. 複数シート出力

複数シートを含むファイルを作成するには、ExcelWriterクラスを使用し、各シートごとにWriteSheetを定義します。

@RequestMapping("/export/multi")
public void exportMulti(HttpServletResponse response) throws IOException {
    response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
    response.setCharacterEncoding("UTF-8");
    String filename = URLEncoder.encode("統合レポート", "UTF-8").replaceAll("\\+", "%20");
    response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + filename + ".xlsx");

    try (ExcelWriter writer = EasyExcel.write(response.getOutputStream()).inMemory(true).build()) {
        WriteSheet userSheet = EasyExcel.writerSheet(0, "ユーザー情報").head(UserDTO.class).build();
        writer.write(userService.findAll(), userSheet);

        WriteSheet citySheet = EasyExcel.writerSheet(1, "都市マスタ").head(CityDTO.class).build();
        writer.write(cityService.findAll(), citySheet);

        WriteSheet companySheet = EasyExcel.writerSheet(2, "企業情報").head(CompanyDTO.class).build();
        writer.write(companyService.findAll(), companySheet);
    }
}

インポート機能の実装

1. リスナーによる逐次処理

大規模なファイルを扱う場合、すべてのデータをメモリに保持するとオブジェクトメモリ不足(OOM)リスクが高まります。そのため、AnalysisEventListenerを継承して、バッチ単位でデータを処理する設計が推奨されます。

public class ImportListener extends AnalysisEventListener<UserData> {
    private final List<UserData> batchData = new ArrayList<>(100);
    private final List<String> errors = new ArrayList<>();

    @Override
    public void invoke(UserData data, AnalysisContext context) {
        if (validate(data)) {
            batchData.add(data);
        } else {
            errors.add("行" + (context.readRowHolder().getRowIndex() + 1) + "に不正データがあります");
        }

        if (batchData.size() >= 100) {
            saveBatch(batchData);
            batchData.clear();
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        if (!batchData.isEmpty()) {
            saveBatch(batchData);
        }
    }

    private boolean validate(UserData data) {
        return data.getUserName() != null && !data.getUserName().trim().isEmpty()
            && data.getUserPhone() != null && data.getUserPhone().matches("^1[0-9]{10}$");
    }
}

2. エラーハンドリングと制限事項

  • 最大行数制限:1回のアップロードで2,000行を超える場合は例外を発生させる
  • セル値の空チェックと正規表現によるバリデーション
  • Spring管理対象外のリスナークラス(毎回newが必要)

重要なアノテーションの使い方

アノテーション用途
@ExcelPropertyヘッダー名の指定および複数行ヘッダー(ネスト)に対応
@ColumnWidth列幅の固定(単位:文字数)
@ExcelIgnore出力対象から除外するフィールドの指定
@ExcelProperty(value = {"基本情報", "電話番号"}, index = 2)
@ColumnWidth(20)
private String phone;

設計上のポイントまとめ

  • 大量データの処理では、inMemory(true)を使ってメモリ上での一時保存を回避
  • Web出力時はresponse.getOutputStream()を直接使用し、自動クローズを活用
  • リスナーはステートレス設計が望ましく、DIコンテナ管理を避ける
  • エラーメッセージの集約と返却により、ユーザーに明確なフィードバックを提供
  • シート数が多すぎる場合は、分割ダウンロードを検討

タグ: Alibaba EasyExcel Excelインポート Excelエクスポート Java Spring Boot

8月26日 01:40 投稿