0
1

More than 1 year has passed since last update.

[DDD]Apache POIでExcelドメインを作成する

Posted at

はじめに

DDD×Apache POI でエクセル出力ドメインを作成していきます

Apache POIとは

Apache POI(以前はJakarta POIと呼ばれていました)はJavaアプリケーションからExcelやWordなどのMicrosoft製品のフォーマットファイルを読み書きするためのAPIです。

環境

Java 11
Spring Boot 2.6.1
Gradle 2.24
Apache POI 4.1.2

設定

poi-ooxmlも追加することで、 xlsxdockといった2007形式のファイルの読み書きが可能となります。

build.gradle
dependencies {

    implementation('org.apache.poi:poi:4.1.2')
    implementation('org.apache.poi:poi-ooxml:4.1.2')

}

エクセルドメイン

ここからDDDの出番です
まずは、Excel出力の業務仕様をモデル化し、コードで表記していきます
今回実現したい業務は以下です
・ Excelファイルを作成する
・ Excelファイルをダウンロードする

必要な構成要素は以下です
・拡張子
・ファイル名
・シート名
・タイトル一覧
・タイトル行フォント種別
・タイトル行太字設定
・タイトル行横位置
・タイトル行縦位置
・データ一覧
・データ行フォント種別

Excel出力クラスをただのデータの入れ物ではなく、モデルを正確に表すため、
属性以外の情報(ファイル作成ロジック等)も作成していきます
出力内容毎に構成要素を追加できるよう抽象クラスにしています

Excel.java
@RequiredArgsConstructor
public abstract class Excel {

    /** ファイル拡張子 */
    private static final String EXTENSION = ".xlsx";
    /** 日時フォーマッタ */
    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmm");
    /** タイトル行フォント種別 */
    private static final FontType titleFontType = FontType.MSP_GOTHIC;
    /** タイトル行太文字フラグ */
    private static final boolean isBoldTitle = true;
    /** タイトル行横位置 */
    private static final HorizontalAlignment titleHorizontalAlignment = HorizontalAlignment.CENTER;
    /** タイトル行縦位置 */
    private static final VerticalAlignment titleVerticalAlignment = VerticalAlignment.CENTER;
    /** データ行フォント種別 */
    private static final FontType bodyFontType = FontType.MSP_GOTHIC;

    /** タイムスタンプ */
    private final String TIMESTAMP = LocalDateTime.now().format(FORMATTER);

    private final ServletContext context;
    private final HttpServletRequest request;

    /**
     * ファイル名(先頭のみ)を取得します。
     * @return ファイル名(先頭のみ)
     */
    abstract public String getFileNamePrefix();

    /**
     * シート名を取得します。
     * @return シート名
     */
    abstract public String getSheetName();

    /**
     * タイトル行に出力するタイトル一覧を取得します。
     * @return タイトル一覧
     */
    abstract public List<String> getTitles();

    /**
     * データ行に出力する行列の値一覧を取得します。
     * @return 行列の値一覧
     */
    abstract public List<List<String>> getMatrixValues();

    /**
     * ファイルを作成します。
     * @return ファイル作成成功の場合はtrue
     */
    public boolean createFile() {
        final FileOutputStream outputStream;
        try {
            outputStream = new FileOutputStream(getFilePath());
        } catch (FileNotFoundException e) {
            return false;
        }

        XSSFWorkbook workbook = new XSSFWorkbook();
        XSSFSheet sheet = workbook.createSheet(getSheetName());

        int rowNum = 0;

        // タイトル行書き込み
        Row headerRow = sheet.createRow(rowNum);
        XSSFCellStyle titleStyle = createTitleStyle(workbook);
        for (int i = 0; i < getTitles().size(); i++) {
            Cell cell = headerRow.createCell(i);
            cell.setCellValue(getTitles().get(i));
            cell.setCellStyle(titleStyle);
        }

        // データ行書き込み
        for (List<String> columnValues : getMatrixValues()) {
            rowNum++;

            // フォント設定
            XSSFFont font = workbook.createFont();
            font.setFontName(bodyFontType.getText());
            CellStyle bodyStyle = workbook.createCellStyle();
            bodyStyle.setFont(font);
            bodyStyle.setLocked(false);

            // データ書き込み
            XSSFRow row = sheet.createRow(rowNum);
            for (int j = 0; j < columnValues.size(); j++) {
                row.createCell(j).setCellValue(columnValues.get(j));
            }
        }

        // 列サイズ自動調整
        for (int i = 0; i < sheet.getRow(0).getPhysicalNumberOfCells(); i++) {
            sheet.autoSizeColumn(i);
        }

        // ファイル出力
        try {
            workbook.write(outputStream);
            outputStream.flush();
            outputStream.close();
            workbook.close();
        } catch (IOException e) {
            return false;
        }

        return true;
    }

    /**
     * ファイルをダウンロードします。
     * @return ファイルデータ
     */
    public HttpEntity<byte[]> downloadFile() {
        File file = new File(getFilePath());
        if (file.exists()) {
            try {
                FileInputStream inputStream = new FileInputStream(file);
                val fileData = IOUtils.toByteArray(inputStream);

                val fileTypeMap = new MimetypesFileTypeMap();
                val headers = new HttpHeaders();
                val mimeType = fileTypeMap.getContentType(getFilePath());
                val encodedFilename = URLEncoder.encode(getFileName(), StandardCharsets.UTF_8);
                headers.setContentType(MediaType.valueOf(mimeType));
                headers.setContentDisposition(ContentDisposition.parse(
                        "attachment; filename=\"" + getFileName() + "\";filename*=utf-8''" + encodedFilename
                ));
                return new HttpEntity<>(fileData, headers);

            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                file.delete();
            }
        }

        return new HttpEntity<>(new byte[]{});
    }

    /**
     * ファイル名を取得します。
     * @return ファイル名
     */
    private String getFileName() {
        return getFileNamePrefix() + "-" + TIMESTAMP + EXTENSION;
    }

    /**
     * ファイルパスを取得します。
     * @return ファイルパス
     */
    private String getFilePath() {
        String contextPath = context.getRealPath(request.getContextPath());
        File contextDirectory = new File(contextPath);
        if (!contextDirectory.exists()) {
            contextDirectory.mkdirs();
        }
        return contextDirectory + "/" + getFileName();
    }

    /**
     * タイトル行のセルスタイルを設定します。
     * @param workbook 対象ブック
     * @return セルスタイル
     */
    private XSSFCellStyle createTitleStyle(XSSFWorkbook workbook) {
        XSSFFont font = workbook.createFont();
        font.setBold(isBoldTitle);
        font.setFontName(titleFontType.getText());
        XSSFCellStyle style = workbook.createCellStyle();
        style.setAlignment(titleHorizontalAlignment);
        style.setVerticalAlignment(titleVerticalAlignment);
        style.setFont(font);
        return style;
    }


    /**
     * フォント種別
     */
    @AllArgsConstructor
    @Getter
    public enum FontType {
        MSP_GOTHIC("MS Pゴシック (本文)"),
        ;
        private final String text;
    }
}

Excelドメインを継承する

Excelドメインを継承し、ファイル名のセットや、出力データのセットをしていきます。

SampleExcel.java
public class SampleExcel extends Excel {

    private static final String FILE_NAME = "サンプルファイル";
    private static final String SHEET_NAME = "サンプル一覧";
    private static final List<String> TITLES = List.of("名称", "登録日時");
    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");

    @Setter
    private List<SampleDto> dtoList;

    public SampleExcel(ServletContext context, HttpServletRequest request) {
        super(context, request);
    }

    @Override
    public String getFileNamePrefix() {
        return FILE_NAME;
    }

    @Override
    public String getSheetName() {
        return SHEET_NAME;
    }

    @Override
    public List<String> getTitles() {
        return TITLES;
    }

    @Override
    public List<List<String>> getMatrixValues() {
        val rows = new ArrayList<List<String>>();
        for (SampleDto dto : dtoList) {
            rows.add(getColumnValues(dto));
        }
        return rows;
    }

    /**
     * カラムに出力する値一覧を取得します。
     * @param dto サンプルDTO
     * @return カラムに出力する値一覧
     */
    private List<String> getColumnValues(SampleDto dto) {
        val columns = new ArrayList<String>();
        columns.add(dto.getName());
        columns.add(dto.getRegisterTime().format(FORMATTER));
        return columns;
    }
}

最後に

内容的に至らない部分もあると思います。
間違っている、気になる等ありましたら、優しく諭していただけるととても喜びます。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1