From e43cdb122bc1b9ff18395866fe4f6e8744207e66 Mon Sep 17 00:00:00 2001 From: hee9841 Date: Sun, 28 Dec 2025 15:06:38 +0900 Subject: [PATCH 1/3] chore: remove unused imports --- .../io/github/hee9841/excel/core/exporter/ExcelExporter.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/io/github/hee9841/excel/core/exporter/ExcelExporter.java b/src/main/java/io/github/hee9841/excel/core/exporter/ExcelExporter.java index 03460ab..30903d5 100644 --- a/src/main/java/io/github/hee9841/excel/core/exporter/ExcelExporter.java +++ b/src/main/java/io/github/hee9841/excel/core/exporter/ExcelExporter.java @@ -3,7 +3,6 @@ import java.io.IOException; import java.io.OutputStream; import java.util.List; -import org.apache.poi.ss.usermodel.Sheet; /** * Core interface for Excel file operations in the library. From d3841ee61ff9ccc8fbed2cdc866bf15ecabbd13b Mon Sep 17 00:00:00 2001 From: hee9841 Date: Sun, 28 Dec 2025 15:11:03 +0900 Subject: [PATCH 2/3] refactor: Rename Exporter classes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 변경된 클래스 목록: - SXSSFExporter to AbstractExcelExporter - DefaultExcelExporter to SXSSFExporter - DefaultExcelExporterBuilder to SXSSFExporterBuilder --- README.md | 2 +- .../hee9841/excel/annotation/Excel.java | 2 +- .../core/exporter/AbstractExcelExporter.java | 183 ++++++++++++ .../core/exporter/DefaultExcelExporter.java | 198 ------------- .../excel/core/exporter/SXSSFExporter.java | 263 +++++++++--------- ...Builder.java => SXSSFExporterBuilder.java} | 24 +- ...=> SXSSFExporterByteOutputStreamTest.java} | 30 +- 7 files changed, 351 insertions(+), 351 deletions(-) create mode 100644 src/main/java/io/github/hee9841/excel/core/exporter/AbstractExcelExporter.java delete mode 100644 src/main/java/io/github/hee9841/excel/core/exporter/DefaultExcelExporter.java rename src/main/java/io/github/hee9841/excel/core/exporter/{DefaultExcelExporterBuilder.java => SXSSFExporterBuilder.java} (79%) rename src/test/java/io/github/hee9841/excel/core/exporter/{DefaultExcelExporterByteOutputStreamTest.java => SXSSFExporterByteOutputStreamTest.java} (95%) diff --git a/README.md b/README.md index 6e898ef..2b6328c 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ List products = Arrays.asList( ); // 3. Export to Excel -DefaultExcelExporter exporter = DefaultExcelExporter.builder(Product.class, products) +SXSSFExporter exporter = SXSSFExporter.builder(Product.class, products) .sheetStrategy(SheetStrategy.MULTI_SHEET) // Optional, MULTI_SHEET is default .maxRows(100) // Optional, Max row of SpreadsheetVersion.EXCEL2007 is default .sheetName("Products") // Optional, if not specified sheets will be named Sheet0, Sheet1, etc. diff --git a/src/main/java/io/github/hee9841/excel/annotation/Excel.java b/src/main/java/io/github/hee9841/excel/annotation/Excel.java index e4adf85..201e476 100644 --- a/src/main/java/io/github/hee9841/excel/annotation/Excel.java +++ b/src/main/java/io/github/hee9841/excel/annotation/Excel.java @@ -41,7 +41,7 @@ * * // Usage example: * List{@literal <}UserData{@literal >} users = getUserData(); - * DefaultExcelExporter{@literal <}UserData{@literal >} exporter = DefaultExcelExporter.builder(UserData.class, users) + * SXSSFExporter{@literal <}UserData{@literal >} exporter = SXSSFExporter.builder(UserData.class, users) * .sheetName("Users") * .build(); * exporter.write(new FileOutputStream("users.xlsx")); diff --git a/src/main/java/io/github/hee9841/excel/core/exporter/AbstractExcelExporter.java b/src/main/java/io/github/hee9841/excel/core/exporter/AbstractExcelExporter.java new file mode 100644 index 0000000..562a77e --- /dev/null +++ b/src/main/java/io/github/hee9841/excel/core/exporter/AbstractExcelExporter.java @@ -0,0 +1,183 @@ +package io.github.hee9841.excel.core.exporter; + +import io.github.hee9841.excel.core.meta.ColumnInfo; +import io.github.hee9841.excel.core.meta.ColumnInfoMapper; +import io.github.hee9841.excel.exception.ExcelException; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.Field; +import java.util.List; +import java.util.Map; +import org.apache.commons.lang3.reflect.FieldUtils; +import org.apache.poi.ss.SpreadsheetVersion; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.xssf.streaming.SXSSFWorkbook; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Abstract base class for Excel file operations using Apache POI's SXSSF (Streaming XML Spreadsheet + * Format). + * This class provides the core functionality for handling Excel files with streaming support for + * large datasets. + * + *

Key features:

+ *
    + *
  • Uses SXSSFWorkbook for memory-efficient handling of large Excel files
  • + *
  • Supports Excel 2007+ format (XLSX)
  • + *
  • Provides column mapping and header generation
  • + *
  • Handles cell styling and data type conversion
  • + *
+ * + *

This class implements the core functionality while leaving sheet management strategies + * to be implemented by concrete subclasses.

+ * + * @param The type of data to be handled in the Excel file + */ +public abstract class AbstractExcelExporter implements ExcelExporter { + + protected static final Logger logger = LoggerFactory.getLogger(AbstractExcelExporter.class); + + protected static final SpreadsheetVersion supplyExcelVersion = SpreadsheetVersion.EXCEL2007; + + protected SXSSFWorkbook workbook; + protected Map columnsMappingInfo; + + protected String dtoTypeName; + + /** + * Constructs a new AbstractExcelExporter with a new SXSSFWorkbook instance. + */ + protected AbstractExcelExporter() { + this.workbook = new SXSSFWorkbook(); + } + + /** + * Initializes the Excel file with the specified type and data. + * This method performs validation and sets up column mapping information. + * + * @param type The class type of the data to be exported + * @param data The list of data objects to be exported + */ + protected void initialize(Class type, List data) { + this.dtoTypeName = type.getName(); + logger.info("Initializing Excel file for DTO: {}.java.", dtoTypeName); + + validate(type, data); + + logger.debug("Mapping DTO to Excel data - DTO class({}).", dtoTypeName); + //Map DTO to Excel data + this.columnsMappingInfo = ColumnInfoMapper.of(type, workbook).map(); + } + + + /** + * Creates headers using the column mapping information. + * + * @param sheet The sheet to add headers to + * @param headerRowIndex The headers row index + */ + protected void createHeader(Sheet sheet, Integer headerRowIndex) { + Row row = sheet.createRow(headerRowIndex); + for (Integer colIndex : columnsMappingInfo.keySet()) { + ColumnInfo columnMappingInfo = columnsMappingInfo.get(colIndex); + Cell cell = row.createCell(colIndex); + cell.setCellValue(columnMappingInfo.getHeaderName()); + cell.setCellStyle(columnMappingInfo.getHeaderStyle()); + } + } + + /** + * Creates a new sheet with headers. + * + *

This method resets the current row index, creates a new sheet, and adds headers to it. + * If a sheet name is provided, it will be used as a base name with an index(index starts from + * 0) suffix.

+ */ + protected Sheet createNewSheet(String sheetName, int sheetIndex) { + //If sheet name is provided, create sheet with sheet name + idx + final String finalSheetName = (sheetName != null) + ? String.format("%s(%d)", sheetName, sheetIndex) + : null; + + + Sheet sheet = (finalSheetName != null) + ? workbook.createSheet(finalSheetName) + : workbook.createSheet(); + + logger.debug("Create new Sheet : {}.", sheet.getSheetName()); + + return sheet; + } + + /** + * Creates a row in the Excel sheet for the given data object. + * This method handles field access and cell value setting based on column mapping information. + * + * @param sheet The Sheet object to create a row. + * @param data The data object for rendering data to cell + * @param rowIndex The index of the row to create + * @throws ExcelException if field access fails + */ + protected void createBody(Sheet sheet, Object data, int rowIndex) { + logger.debug("Add rows data - row:{}.", rowIndex); + Row row = sheet.createRow(rowIndex); + for (Integer colIndex : columnsMappingInfo.keySet()) { + ColumnInfo columnInfo = columnsMappingInfo.get(colIndex); + try { + Field field = FieldUtils.getField(data.getClass(), columnInfo.getFieldName(), true); + Cell cell = row.createCell(colIndex); + //Set cell value by cell type + columnInfo.getColumnType().setCellValueByCellType(cell, field.get(data)); + //Set cell style + cell.setCellStyle(columnInfo.getBodyStyle()); + } catch (IllegalAccessException e) { + throw new ExcelException( + String.format("Failed to create body(column:%d, row:%d) : " + + "Access to field %s failed.", + colIndex, rowIndex, columnInfo.getFieldName()), e); + } + } + } + + /** + * Writes the Excel file content to the specified output stream. + * This method ensures proper resource cleanup using try-with-resources. + * + * @param stream The output stream to write the Excel file to + * @throws IOException if an I/O error occurs during writing + */ + @Override + public final void write(OutputStream stream) throws IOException { + if (stream == null) { + throw new ExcelException("Output stream is null."); + } + logger.info("Start to write Excel file for DTO class({}.java).", dtoTypeName); + + try (SXSSFWorkbook autoCloseableWb = this.workbook) { + autoCloseableWb.write(stream); + logger.info("Successfully wrote Excel file for DTO class({}.java).", dtoTypeName); + } + } + + /** + * Validates the provided data and type. + * This method can be overridden by subclasses to add custom validation logic. + * + * @param type The class of the data type + * @param data The list of data objects to be exported + */ + protected abstract void validate(Class type, List data); + + /** + * Creates the Excel file with the provided data. + * This method must be implemented by subclasses to define their specific sheet management + * strategy. + * + * @param data The list of data objects to be exported + */ + protected abstract void createExcel(List data); + +} diff --git a/src/main/java/io/github/hee9841/excel/core/exporter/DefaultExcelExporter.java b/src/main/java/io/github/hee9841/excel/core/exporter/DefaultExcelExporter.java deleted file mode 100644 index 415c405..0000000 --- a/src/main/java/io/github/hee9841/excel/core/exporter/DefaultExcelExporter.java +++ /dev/null @@ -1,198 +0,0 @@ -package io.github.hee9841.excel.core.exporter; - -import io.github.hee9841.excel.exception.ExcelException; -import io.github.hee9841.excel.strategy.SheetStrategy; -import java.text.MessageFormat; -import java.util.List; -import org.apache.poi.ss.usermodel.Sheet; - -/** - * DefaultExcelExporter is a concrete implementation of {@link SXSSFExporter} that provides functionality - * for exporting data to Excel files. This class uses the SXSSFWorkbook from Apache POI for - * efficient - * handling of large datasets by streaming data to disk. - * - *

The DefaultExcelExporter supports two sheet management strategies:

- *
    - *
  • ONE_SHEET - All data is exported to a single sheet (limited by max rows per sheet)
  • - *
  • MULTI_SHEET - Data is split across multiple sheets when exceeding max rows per sheet
  • - *
- * - *

Use the {@link DefaultExcelExporterBuilder} to configure and instantiate this class.

- * - * @param The type of data to be exported to Excel. The type must be annotated appropriately - * for Excel column mapping using the library's annotation system. - * @see SXSSFExporter - * @see DefaultExcelExporterBuilder - * @see SheetStrategy - */ -public class DefaultExcelExporter extends SXSSFExporter { - - private static final String EXCEED_MAX_ROW_MSG_2ARGS = - "The data size exceeds the maximum number of rows allowed per sheet. " - + "The sheet strategy is set to ONE_SHEET but the data size is larger than " - + "the maximum rows per sheet (data size: {0}, maximum rows: {1} ).\n" - + "Please change the sheet strategy to MULTI_SHEET or reduce the data size."; - - private static final int ROW_START_INDEX = 0; - private int currentRowIndex = ROW_START_INDEX; - - private final String sheetName; - private final int maxRowsPerSheet; - - private SheetStrategy sheetStrategy; - - private Sheet currentSheet; - - - /** - * Constructs an DefaultExcelExporter with the specified configuration. - * - *

This constructor is not meant to be called directly. Use {@link DefaultExcelExporterBuilder} - * to create instances of DefaultExcelExporter.

- * - * @param type The class type of the data to be exported - * @param data The list of data objects to be exported - * @param sheetStrategy The strategy for sheet management (ONE_SHEET or MULTI_SHEET) - * @param sheetName Base name for sheets (null for default names) - * @param maxRowsPerSheet Maximum number of rows allowed per sheet - */ - DefaultExcelExporter( - Class type, - List data, - SheetStrategy sheetStrategy, - String sheetName, - int maxRowsPerSheet - ) { - super(); - this.maxRowsPerSheet = maxRowsPerSheet; - this.sheetName = sheetName; - setSheetStrategy(sheetStrategy); - - this.initialize(type, data); - this.createExcel(data); - } - - - /** - * Creates a new builder for configuring and instantiating an DefaultExcelExporter. - * - * @param The type of data to be exported - * @param type The class of the data type - * @param data The list of data objects to be exported - * @return A new DefaultExcelExporterBuilder instance - */ - public static DefaultExcelExporterBuilder builder(Class type, List data) { - return new DefaultExcelExporterBuilder<>(type, data, supplyExcelVersion.getMaxRows()); - } - - /** - * Sets the sheet strategy for this exporter. - * - *

This method also configures the workbook's Zip64 mode based on the selected strategy.

- * - * @param strategy The sheet strategy to use (ONE_SHEET or MULTI_SHEET)- - */ - private void setSheetStrategy(SheetStrategy strategy) { - - this.sheetStrategy = strategy; - workbook.setZip64Mode(sheetStrategy.getZip64Mode()); - - logger.debug("Set sheet strategy and Zip64Mode - strategy: {}, Zip64Mode: {}.", - strategy.name(), sheetStrategy.getZip64Mode().name()); - } - - - /** - * Validates the data size against the maximum rows per sheet limit. - * - *

This method checks if the data size exceeds the maximum allowed rows per sheet - * when using ONE_SHEET strategy. If the limit is exceeded, an ExcelException is thrown.

- * - * @param type The class type of the data being validated - * @param data The list of data objects to be validated - * @throws ExcelException if data size exceeds max rows limit with ONE_SHEET strategy - */ - @Override - protected void validate(Class type, List data) { - if (SheetStrategy.isOneSheet(sheetStrategy) && data.size() > maxRowsPerSheet - 1) { - throw new ExcelException( - MessageFormat.format(EXCEED_MAX_ROW_MSG_2ARGS, - data.size(), maxRowsPerSheet - ), dtoTypeName); - } - } - - /** - * Creates the Excel(workBook) with the provided data. - * - *

This method handles the creation of sheets and rows based on the data:

- *
    - *
  • If the data is empty, it creates a sheet with headers only
  • - *
  • Otherwise, it creates a sheet with headers and adds all data rows
  • - *
- * - * @param data The list of data objects to be exported - */ - @Override - protected void createExcel(List data) { - - currentSheet = createNewSheet(sheetName, 0); - createHeader(currentSheet, ROW_START_INDEX); - - // 1. If data is empty, create createHeader only. - if (data.isEmpty()) { - logger.warn("Empty data provided - Excel file will be created with headers only."); - return; - } - - //2. Add Rows - addRows(data); - } - - /** - * Adds rows to the current sheet for the provided data list. - * - *

If the number of rows exceeds the maximum allowed per sheet and the sheet strategy - * is MULTI_SHEET, a new sheet will be created to continue adding rows.

- * - *

If the sheet strategy is ONE_SHEET and the data size exceeds the maximum rows per sheet, - * an ExcelException will be thrown.

- * - * @param data The list of data objects to be added as rows - * @throws ExcelException if ONE_SHEET strategy is used and data exceeds max rows limit - */ - @Override - public void addRows(List data) { - int leftDataSize = data.size(); - for (Object renderedData : data) { - createBody(currentSheet, renderedData, currentRowIndex++); - leftDataSize--; - if (currentRowIndex == maxRowsPerSheet && leftDataSize > 0) { - //If one sheet strategy, throw exception - if (SheetStrategy.isOneSheet(sheetStrategy)) { - throw new ExcelException( - MessageFormat.format(EXCEED_MAX_ROW_MSG_2ARGS, - data.size(), maxRowsPerSheet), dtoTypeName); - } - - //If multi sheet strategy, create new sheet - currentRowIndex = ROW_START_INDEX; - currentSheet = createNewSheet(sheetName, workbook.getSheetIndex(currentSheet) + 1); - createHeader(currentSheet, ROW_START_INDEX); - } - } - } - - /** - * Override createHeader Method to add currentRowIndex. - * - * @param sheet The sheet to add headers to - * @param headerRowIndex The headers row index - */ - @Override - protected void createHeader(Sheet sheet, Integer headerRowIndex) { - super.createHeader(sheet, headerRowIndex); - currentRowIndex++; - } -} diff --git a/src/main/java/io/github/hee9841/excel/core/exporter/SXSSFExporter.java b/src/main/java/io/github/hee9841/excel/core/exporter/SXSSFExporter.java index 7578fe8..c111b35 100644 --- a/src/main/java/io/github/hee9841/excel/core/exporter/SXSSFExporter.java +++ b/src/main/java/io/github/hee9841/excel/core/exporter/SXSSFExporter.java @@ -1,183 +1,198 @@ package io.github.hee9841.excel.core.exporter; -import io.github.hee9841.excel.core.meta.ColumnInfo; -import io.github.hee9841.excel.core.meta.ColumnInfoMapper; import io.github.hee9841.excel.exception.ExcelException; -import java.io.IOException; -import java.io.OutputStream; -import java.lang.reflect.Field; +import io.github.hee9841.excel.strategy.SheetStrategy; +import java.text.MessageFormat; import java.util.List; -import java.util.Map; -import org.apache.commons.lang3.reflect.FieldUtils; -import org.apache.poi.ss.SpreadsheetVersion; -import org.apache.poi.ss.usermodel.Cell; -import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; -import org.apache.poi.xssf.streaming.SXSSFWorkbook; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** - * Abstract base class for Excel file operations using Apache POI's SXSSF (Streaming XML Spreadsheet - * Format). - * This class provides the core functionality for handling Excel files with streaming support for - * large datasets. + * SXSSFExporter is a concrete implementation of {@link AbstractExcelExporter} that provides functionality + * for exporting data to Excel files. This class uses the SXSSFWorkbook from Apache POI for + * efficient + * handling of large datasets by streaming data to disk. * - *

Key features:

+ *

The SXSSFExporter supports two sheet management strategies:

*
    - *
  • Uses SXSSFWorkbook for memory-efficient handling of large Excel files
  • - *
  • Supports Excel 2007+ format (XLSX)
  • - *
  • Provides column mapping and header generation
  • - *
  • Handles cell styling and data type conversion
  • + *
  • ONE_SHEET - All data is exported to a single sheet (limited by max rows per sheet)
  • + *
  • MULTI_SHEET - Data is split across multiple sheets when exceeding max rows per sheet
  • *
* - *

This class implements the core functionality while leaving sheet management strategies - * to be implemented by concrete subclasses.

+ *

Use the {@link SXSSFExporterBuilder} to configure and instantiate this class.

* - * @param The type of data to be handled in the Excel file + * @param The type of data to be exported to Excel. The type must be annotated appropriately + * for Excel column mapping using the library's annotation system. + * @see AbstractExcelExporter + * @see SXSSFExporterBuilder + * @see SheetStrategy */ -public abstract class SXSSFExporter implements ExcelExporter { +public class SXSSFExporter extends AbstractExcelExporter { - protected static final Logger logger = LoggerFactory.getLogger(SXSSFExporter.class); + private static final String EXCEED_MAX_ROW_MSG_2ARGS = + "The data size exceeds the maximum number of rows allowed per sheet. " + + "The sheet strategy is set to ONE_SHEET but the data size is larger than " + + "the maximum rows per sheet (data size: {0}, maximum rows: {1} ).\n" + + "Please change the sheet strategy to MULTI_SHEET or reduce the data size."; - protected static final SpreadsheetVersion supplyExcelVersion = SpreadsheetVersion.EXCEL2007; + private static final int ROW_START_INDEX = 0; + private int currentRowIndex = ROW_START_INDEX; - protected SXSSFWorkbook workbook; - protected Map columnsMappingInfo; + private final String sheetName; + private final int maxRowsPerSheet; - protected String dtoTypeName; + private SheetStrategy sheetStrategy; + + private Sheet currentSheet; - /** - * Constructs a new SXSSFExporter with a new SXSSFWorkbook instance. - */ - protected SXSSFExporter() { - this.workbook = new SXSSFWorkbook(); - } /** - * Initializes the Excel file with the specified type and data. - * This method performs validation and sets up column mapping information. + * Constructs an SXSSFExporter with the specified configuration. * - * @param type The class type of the data to be exported - * @param data The list of data objects to be exported + *

This constructor is not meant to be called directly. Use {@link SXSSFExporterBuilder} + * to create instances of SXSSFExporter.

+ * + * @param type The class type of the data to be exported + * @param data The list of data objects to be exported + * @param sheetStrategy The strategy for sheet management (ONE_SHEET or MULTI_SHEET) + * @param sheetName Base name for sheets (null for default names) + * @param maxRowsPerSheet Maximum number of rows allowed per sheet */ - protected void initialize(Class type, List data) { - this.dtoTypeName = type.getName(); - logger.info("Initializing Excel file for DTO: {}.java.", dtoTypeName); - - validate(type, data); - - logger.debug("Mapping DTO to Excel data - DTO class({}).", dtoTypeName); - //Map DTO to Excel data - this.columnsMappingInfo = ColumnInfoMapper.of(type, workbook).map(); + SXSSFExporter( + Class type, + List data, + SheetStrategy sheetStrategy, + String sheetName, + int maxRowsPerSheet + ) { + super(); + this.maxRowsPerSheet = maxRowsPerSheet; + this.sheetName = sheetName; + setSheetStrategy(sheetStrategy); + + this.initialize(type, data); + this.createExcel(data); } /** - * Creates headers using the column mapping information. + * Creates a new builder for configuring and instantiating an SXSSFExporter. * - * @param sheet The sheet to add headers to - * @param headerRowIndex The headers row index + * @param The type of data to be exported + * @param type The class of the data type + * @param data The list of data objects to be exported + * @return A new SXSSFExporterBuilder instance */ - protected void createHeader(Sheet sheet, Integer headerRowIndex) { - Row row = sheet.createRow(headerRowIndex); - for (Integer colIndex : columnsMappingInfo.keySet()) { - ColumnInfo columnMappingInfo = columnsMappingInfo.get(colIndex); - Cell cell = row.createCell(colIndex); - cell.setCellValue(columnMappingInfo.getHeaderName()); - cell.setCellStyle(columnMappingInfo.getHeaderStyle()); - } + public static SXSSFExporterBuilder builder(Class type, List data) { + return new SXSSFExporterBuilder<>(type, data, supplyExcelVersion.getMaxRows()); } /** - * Creates a new sheet with headers. + * Sets the sheet strategy for this exporter. * - *

This method resets the current row index, creates a new sheet, and adds headers to it. - * If a sheet name is provided, it will be used as a base name with an index(index starts from - * 0) suffix.

+ *

This method also configures the workbook's Zip64 mode based on the selected strategy.

+ * + * @param strategy The sheet strategy to use (ONE_SHEET or MULTI_SHEET)- */ - protected Sheet createNewSheet(String sheetName, int sheetIndex) { - //If sheet name is provided, create sheet with sheet name + idx - final String finalSheetName = (sheetName != null) - ? String.format("%s(%d)", sheetName, sheetIndex) - : null; - + private void setSheetStrategy(SheetStrategy strategy) { - Sheet sheet = (finalSheetName != null) - ? workbook.createSheet(finalSheetName) - : workbook.createSheet(); + this.sheetStrategy = strategy; + workbook.setZip64Mode(sheetStrategy.getZip64Mode()); - logger.debug("Create new Sheet : {}.", sheet.getSheetName()); - - return sheet; + logger.debug("Set sheet strategy and Zip64Mode - strategy: {}, Zip64Mode: {}.", + strategy.name(), sheetStrategy.getZip64Mode().name()); } + /** - * Creates a row in the Excel sheet for the given data object. - * This method handles field access and cell value setting based on column mapping information. + * Validates the data size against the maximum rows per sheet limit. + * + *

This method checks if the data size exceeds the maximum allowed rows per sheet + * when using ONE_SHEET strategy. If the limit is exceeded, an ExcelException is thrown.

* - * @param sheet The Sheet object to create a row. - * @param data The data object for rendering data to cell - * @param rowIndex The index of the row to create - * @throws ExcelException if field access fails + * @param type The class type of the data being validated + * @param data The list of data objects to be validated + * @throws ExcelException if data size exceeds max rows limit with ONE_SHEET strategy */ - protected void createBody(Sheet sheet, Object data, int rowIndex) { - logger.debug("Add rows data - row:{}.", rowIndex); - Row row = sheet.createRow(rowIndex); - for (Integer colIndex : columnsMappingInfo.keySet()) { - ColumnInfo columnInfo = columnsMappingInfo.get(colIndex); - try { - Field field = FieldUtils.getField(data.getClass(), columnInfo.getFieldName(), true); - Cell cell = row.createCell(colIndex); - //Set cell value by cell type - columnInfo.getColumnType().setCellValueByCellType(cell, field.get(data)); - //Set cell style - cell.setCellStyle(columnInfo.getBodyStyle()); - } catch (IllegalAccessException e) { - throw new ExcelException( - String.format("Failed to create body(column:%d, row:%d) : " - + "Access to field %s failed.", - colIndex, rowIndex, columnInfo.getFieldName()), e); - } + @Override + protected void validate(Class type, List data) { + if (SheetStrategy.isOneSheet(sheetStrategy) && data.size() > maxRowsPerSheet - 1) { + throw new ExcelException( + MessageFormat.format(EXCEED_MAX_ROW_MSG_2ARGS, + data.size(), maxRowsPerSheet + ), dtoTypeName); } } /** - * Writes the Excel file content to the specified output stream. - * This method ensures proper resource cleanup using try-with-resources. + * Creates the Excel(workBook) with the provided data. * - * @param stream The output stream to write the Excel file to - * @throws IOException if an I/O error occurs during writing + *

This method handles the creation of sheets and rows based on the data:

+ *
    + *
  • If the data is empty, it creates a sheet with headers only
  • + *
  • Otherwise, it creates a sheet with headers and adds all data rows
  • + *
+ * + * @param data The list of data objects to be exported */ @Override - public final void write(OutputStream stream) throws IOException { - if (stream == null) { - throw new ExcelException("Output stream is null."); - } - logger.info("Start to write Excel file for DTO class({}.java).", dtoTypeName); + protected void createExcel(List data) { - try (SXSSFWorkbook autoCloseableWb = this.workbook) { - autoCloseableWb.write(stream); - logger.info("Successfully wrote Excel file for DTO class({}.java).", dtoTypeName); + currentSheet = createNewSheet(sheetName, 0); + createHeader(currentSheet, ROW_START_INDEX); + + // 1. If data is empty, create createHeader only. + if (data.isEmpty()) { + logger.warn("Empty data provided - Excel file will be created with headers only."); + return; } + + //2. Add Rows + addRows(data); } /** - * Validates the provided data and type. - * This method can be overridden by subclasses to add custom validation logic. + * Adds rows to the current sheet for the provided data list. * - * @param type The class of the data type - * @param data The list of data objects to be exported + *

If the number of rows exceeds the maximum allowed per sheet and the sheet strategy + * is MULTI_SHEET, a new sheet will be created to continue adding rows.

+ * + *

If the sheet strategy is ONE_SHEET and the data size exceeds the maximum rows per sheet, + * an ExcelException will be thrown.

+ * + * @param data The list of data objects to be added as rows + * @throws ExcelException if ONE_SHEET strategy is used and data exceeds max rows limit */ - protected abstract void validate(Class type, List data); + @Override + public void addRows(List data) { + int leftDataSize = data.size(); + for (Object renderedData : data) { + createBody(currentSheet, renderedData, currentRowIndex++); + leftDataSize--; + if (currentRowIndex == maxRowsPerSheet && leftDataSize > 0) { + //If one sheet strategy, throw exception + if (SheetStrategy.isOneSheet(sheetStrategy)) { + throw new ExcelException( + MessageFormat.format(EXCEED_MAX_ROW_MSG_2ARGS, + data.size(), maxRowsPerSheet), dtoTypeName); + } + + //If multi sheet strategy, create new sheet + currentRowIndex = ROW_START_INDEX; + currentSheet = createNewSheet(sheetName, workbook.getSheetIndex(currentSheet) + 1); + createHeader(currentSheet, ROW_START_INDEX); + } + } + } /** - * Creates the Excel file with the provided data. - * This method must be implemented by subclasses to define their specific sheet management - * strategy. + * Override createHeader Method to add currentRowIndex. * - * @param data The list of data objects to be exported + * @param sheet The sheet to add headers to + * @param headerRowIndex The headers row index */ - protected abstract void createExcel(List data); - + @Override + protected void createHeader(Sheet sheet, Integer headerRowIndex) { + super.createHeader(sheet, headerRowIndex); + currentRowIndex++; + } } diff --git a/src/main/java/io/github/hee9841/excel/core/exporter/DefaultExcelExporterBuilder.java b/src/main/java/io/github/hee9841/excel/core/exporter/SXSSFExporterBuilder.java similarity index 79% rename from src/main/java/io/github/hee9841/excel/core/exporter/DefaultExcelExporterBuilder.java rename to src/main/java/io/github/hee9841/excel/core/exporter/SXSSFExporterBuilder.java index c9519cf..738906d 100644 --- a/src/main/java/io/github/hee9841/excel/core/exporter/DefaultExcelExporterBuilder.java +++ b/src/main/java/io/github/hee9841/excel/core/exporter/SXSSFExporterBuilder.java @@ -5,7 +5,7 @@ import java.util.List; /** - * Builder class for creating and configuring {@link DefaultExcelExporter} instances. + * Builder class for creating and configuring {@link SXSSFExporter} instances. * This class implements the Builder pattern to provide a fluent interface for * configuring Excel export settings. * @@ -18,7 +18,7 @@ * *

Example usage:

*
- * DefaultExcelExporter<MyData> exporter = DefaultExcelExporter.builder(MyData.class, dataList)
+ * SXSSFExporter<MyData> exporter = SXSSFExporter.builder(MyData.class, dataList)
  *     .sheetStrategy(SheetStrategy.ONE_SHEET)
  *     .maxRows(10000)
  *     .sheetName("MySheet")
@@ -27,7 +27,7 @@
  *
  * @param  The type of data to be exported
  */
-public class DefaultExcelExporterBuilder {
+public class SXSSFExporterBuilder {
 
     private final Class type;
     private final List data;
@@ -39,13 +39,13 @@ public class DefaultExcelExporterBuilder {
     private String sheetName;
 
     /**
-     * Constructs a new DefaultExcelExporterBuilder with the specified type and data.
+     * Constructs a new SXSSFExporterBuilder with the specified type and data.
      *
      * @param type               The class type of the data to be exported
      * @param data               The list of data objects to be exported
      * @param supplyExcelMaxRows The maximum number of rows supported by the Excel version
      */
-    DefaultExcelExporterBuilder(
+    SXSSFExporterBuilder(
         Class type,
         List data,
         int supplyExcelMaxRows
@@ -64,7 +64,7 @@ public class DefaultExcelExporterBuilder {
      * @param sheetStrategy The strategy to use for sheet management (ONE_SHEET or MULTI_SHEET)
      * @return This builder instance for method chaining
      */
-    public DefaultExcelExporterBuilder sheetStrategy(SheetStrategy sheetStrategy) {
+    public SXSSFExporterBuilder sheetStrategy(SheetStrategy sheetStrategy) {
         this.sheetStrategy = sheetStrategy;
         return this;
     }
@@ -76,7 +76,7 @@ public DefaultExcelExporterBuilder sheetStrategy(SheetStrategy sheetStrategy)
      * @return This builder instance for method chaining
      * @throws ExcelException if maxRowsPerSheet exceeds the Excel version's maximum row limit
      */
-    public DefaultExcelExporterBuilder maxRows(int maxRowsPerSheet) {
+    public SXSSFExporterBuilder maxRows(int maxRowsPerSheet) {
         if (maxRowsPerSheet > supplyExcelMaxRows) {
             throw new ExcelException(String.format(
                 "The maximum rows per sheet(%d) cannot exceed the supplied Excel sheet version's maximum row limit(%d).",
@@ -99,18 +99,18 @@ public DefaultExcelExporterBuilder maxRows(int maxRowsPerSheet) {
      * @param sheetName The base name for sheets
      * @return This builder instance for method chaining
      */
-    public DefaultExcelExporterBuilder sheetName(String sheetName) {
+    public SXSSFExporterBuilder sheetName(String sheetName) {
         this.sheetName = sheetName;
         return this;
     }
 
     /**
-     * Builds and returns a new DefaultExcelExporter instance with the configured settings.
+     * Builds and returns a new SXSSFExporter instance with the configured settings.
      *
-     * @return A new DefaultExcelExporter instance
+     * @return A new SXSSFExporter instance
      */
-    public DefaultExcelExporter build() {
-        return new DefaultExcelExporter(
+    public SXSSFExporter build() {
+        return new SXSSFExporter(
             this.type,
             this.data,
             this.sheetStrategy,
diff --git a/src/test/java/io/github/hee9841/excel/core/exporter/DefaultExcelExporterByteOutputStreamTest.java b/src/test/java/io/github/hee9841/excel/core/exporter/SXSSFExporterByteOutputStreamTest.java
similarity index 95%
rename from src/test/java/io/github/hee9841/excel/core/exporter/DefaultExcelExporterByteOutputStreamTest.java
rename to src/test/java/io/github/hee9841/excel/core/exporter/SXSSFExporterByteOutputStreamTest.java
index b796234..e6f03ee 100644
--- a/src/test/java/io/github/hee9841/excel/core/exporter/DefaultExcelExporterByteOutputStreamTest.java
+++ b/src/test/java/io/github/hee9841/excel/core/exporter/SXSSFExporterByteOutputStreamTest.java
@@ -52,8 +52,8 @@
 import org.slf4j.LoggerFactory;
 
 
-@DisplayName("DefaultExcelExporter 테스트")
-class DefaultExcelExporterByteOutputStreamTest {
+@DisplayName("SXSSFExporter 테스트")
+class SXSSFExporterByteOutputStreamTest {
 
     MemoryAppender memoryAppender;
     String loggerClassName;
@@ -61,8 +61,8 @@ class DefaultExcelExporterByteOutputStreamTest {
 
     @BeforeEach
     void beforeEach() {
-        Logger logger = (Logger) LoggerFactory.getLogger(SXSSFExporter.class);
-        loggerClassName = SXSSFExporter.class.getName();
+        Logger logger = (Logger) LoggerFactory.getLogger(AbstractExcelExporter.class);
+        loggerClassName = AbstractExcelExporter.class.getName();
         memoryAppender = new MemoryAppender();
         memoryAppender.setContext((LoggerContext) LoggerFactory.getILoggerFactory());
         logger.setLevel(Level.DEBUG);
@@ -92,7 +92,7 @@ void dataSizeExceedMaxRowsThrowsException() {
 
         // when & then
         ExcelException exception = assertThrows(ExcelException.class, () ->
-            DefaultExcelExporter.builder(TestDto.class, data)
+            SXSSFExporter.builder(TestDto.class, data)
                 .maxRows(maxRows)
                 .sheetStrategy(
                     SheetStrategy.ONE_SHEET) // Force ONE_SHEET strategy to ensure exception is thrown
@@ -113,7 +113,7 @@ void cannotExceedMaxRowOfImplementation() {
         int maxRowOfExcel2007 = 0x100000; // Excel 2007 최대 행 수 초과
 
         // when
-        ExcelException exception = assertThrows(ExcelException.class, () -> DefaultExcelExporter
+        ExcelException exception = assertThrows(ExcelException.class, () -> SXSSFExporter
             .builder(TestDto.class, data)
             .maxRows(maxRowOfExcel2007 + 1)
             .build());
@@ -131,7 +131,7 @@ void validateLogSequence() throws IOException {
         data.add(new TestDto("test1", 1));
         data.add(new TestDto("test2", 2));
 
-        DefaultExcelExporter exporter = DefaultExcelExporter
+        SXSSFExporter exporter = SXSSFExporter
             .builder(TestDto.class, data).build();
 
         exporter.write(os);
@@ -157,7 +157,7 @@ void createEmptyExcelFile() throws IOException {
         // given
         List emptyData = new ArrayList<>();
 
-        DefaultExcelExporter exporter = DefaultExcelExporter
+        SXSSFExporter exporter = SXSSFExporter
             .builder(TestDto.class, emptyData)
             .build();
 
@@ -193,7 +193,7 @@ void multiSheetMaxRowsExceedTest() throws IOException {
                 testData.add(new TestDto("test" + (i + 1), i + 1));
             }
             // when
-            DefaultExcelExporter.builder(TestDto.class, testData)
+            SXSSFExporter.builder(TestDto.class, testData)
                 .maxRows(10)
                 .build().write(os);
 
@@ -237,7 +237,7 @@ void throwExceptionWhenOneSheetAndExceedMaxRows() {
 
             //when
             ExcelException exception = assertThrows(ExcelException.class,
-                () -> DefaultExcelExporter.builder(TestDto.class, testData)
+                () -> SXSSFExporter.builder(TestDto.class, testData)
                     .maxRows(10)
                     .sheetStrategy(SheetStrategy.ONE_SHEET)
                     .build()
@@ -265,7 +265,7 @@ void createSheetWithSpecifiedName() throws IOException {
             }
 
             //when
-            DefaultExcelExporter.builder(TestDto.class, testData)
+            SXSSFExporter.builder(TestDto.class, testData)
                 .sheetName("TestSheet")
                 .maxRows(10)
                 .build()
@@ -300,7 +300,7 @@ void checkFormulaType() throws IOException {
         }
 
         //when
-        DefaultExcelExporter.builder(TypeAutoDto.class, testData)
+        SXSSFExporter.builder(TypeAutoDto.class, testData)
             .sheetName("TestSheet")
             .build()
             .write(os);
@@ -346,7 +346,7 @@ class TestDto {
         testData.add(new TestDto());
 
         //when
-        DefaultExcelExporter.builder(TestDto.class, testData)
+        SXSSFExporter.builder(TestDto.class, testData)
             .build()
             .write(os);
 
@@ -402,7 +402,7 @@ void createExcelFileWithSpecifiedCellStyle() throws IOException {
             testData.add(new TestDto("name value", 1));
 
             //when
-            DefaultExcelExporter.builder(TestDto.class, testData)
+            SXSSFExporter.builder(TestDto.class, testData)
                 .build()
                 .write(os);
 
@@ -454,7 +454,7 @@ void createExcelFileWithSpecifiedDataFormat() throws IOException {
         String signUpDatePattern = CellFormats.DEFAULT_DATE_FORMAT;
 
         //when
-        DefaultExcelExporter.builder(TestDto.class, testData)
+        SXSSFExporter.builder(TestDto.class, testData)
             .build()
             .write(os);
 

From 7a9b49b07f14838fa72bd517800674e1cac20c34 Mon Sep 17 00:00:00 2001
From: hee9841 
Date: Sun, 28 Dec 2025 15:30:19 +0900
Subject: [PATCH 3/3] =?UTF-8?q?chore:=20release=200.1.0=EC=9C=BC=EB=A1=9C?=
 =?UTF-8?q?=20=EC=88=98=EC=A0=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 build.gradle | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/build.gradle b/build.gradle
index cac2fda..76c399f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -11,7 +11,7 @@ plugins {
 }
 
 group = 'io.github.hee9841.excel'
-version = '0.0.4'
+version = '0.1.0'
 
 repositories {
     mavenCentral()