Java通过EasyExcel生成excel文件(多个sheet)、列合并

该博客介绍了如何使用EasyExcel库在Java中处理Excel数据,包括读取和写入操作。示例涵盖了操作日志和用户信息实体类的定义,以及如何在写入时合并单元格。此外,还提供了测试数据和调用方的代码示例,展示了如何创建包含多个sheet的Excel文件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1、添加依赖

2、实体类

2.1 操作日志

2.2 用户信息

3、测试的excel数据文件

3.1 用户信息excel

3.2 操作日志excel

4、excel列合并工具类

5、调用方


1、添加依赖

<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>fastjson</artifactId>
	<version>1.2.3</version>
</dependency>
<dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>3.17</version>
        </dependency>

2、实体类

2.1 操作日志

package com.shucha.deveiface.biz.test;

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import lombok.Data;

/**
 * @author tqf
 * @Description
 * @Version 1.0
 * @since 2022-01-18 15:03
 */
@Data

public class Log {

    /**
     * 日志名称
     */
    @ColumnWidth(20)
    @ExcelProperty(value = "日志名称", index = 0)
    // @ExcelProperty(value = {"资源基本信息", "序号"},index = 0)
    private String logName;

    /**
     * 日志时间
     */
    @ColumnWidth(20)
    @ExcelProperty(value = "日志时间", index = 1)
    @DateTimeFormat("yyyy-MM-dd HH:mm:ss")
    private String date;

    /**
     * 字段名
     */
    @ColumnWidth(20)
    @ExcelProperty(value = "字段名", index = 2)
    private String fieldName;

    /**
     * 字段描述
     */
    @ColumnWidth(20)
    @ExcelProperty(value = "字段描述", index = 3)
    private String fieldDesc;

    /**
     * 字段值
     */
    @ColumnWidth(20)
    @ExcelProperty(value = "字段值", index = 4)
    private String fieldValue;
}

2.2 用户信息

package com.shucha.deveiface.biz.test;

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.HeadStyle;
import lombok.Data;
import org.apache.poi.ss.usermodel.FillPatternType;

/**
 * @author tqf
 * @Description
 * @Version 1.0
 * @since 2022-01-18 14:09
 */
@Data
public class User {

    /**
     * 姓名
     */
    // 字符串的头背景设置成粉红 IndexedColors.PINK.getIndex() 设置标题的背景颜色
    @HeadStyle(fillPatternType = FillPatternType.SOLID_FOREGROUND, fillForegroundColor = 14)
    @ExcelProperty(value = "姓名", index = 0)
    private String name;

    /**
     * 年龄
     */
    // 字符串的头背景设置成粉红 IndexedColors.PINK.getIndex() 设置标题的背景颜色
    @HeadStyle(fillPatternType = FillPatternType.SOLID_FOREGROUND, fillForegroundColor = 13)
    @ExcelProperty(value = "年龄", index = 1)
    private String age;
}

3、测试的excel数据文件

3.1 用户信息excel

姓名年龄
王海35
谭杰12
王娟25

3.2 操作日志excel

日志名称日志时间字段名字段描述字段值
根据ID查询详情2021-01-13size条数25
根据ID查询详情2021-01-13page页码21
根据ID查询详情2021-01-13total总数25
获取日志列表2021-01-12logName日志名称查询
获取日志列表2021-01-12logTime日志时间2021-10-01 14:25:14
获取日志列表2021-01-12logUser查询人姓名王杰
查询用户信息2021-01-11userName用户名tqf_123
查询用户信息2021-01-11age年龄25
查询用户信息2021-01-11sex性别(0-男,1-女,2-未知)0

4、excel列合并工具类

package com.shucha.deveiface.biz.test;

import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import lombok.Data;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;

import java.util.List;

/**
 * 单元格合并
 *
 * @author
 * @date
 */
@Data
public class ExcelFillCellMergeStrategy implements CellWriteHandler {
    /**
     * 合并字段的下标
     */
    private int[] mergeColumnIndex;
    /**
     * 合并几行
     */
    private int mergeRowIndex;

    public ExcelFillCellMergeStrategy() {
    }

    public ExcelFillCellMergeStrategy(int mergeRowIndex, int[] mergeColumnIndex) {
        this.mergeRowIndex = mergeRowIndex;
        this.mergeColumnIndex = mergeColumnIndex;
    }

    @Override
    public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row,
                                 Head head, Integer integer, Integer integer1, Boolean aBoolean) {

    }

    @Override
    public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell,
                                Head head, Integer integer, Boolean aBoolean) {

    }

    @Override
    public void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
                                       CellData cellData, Cell cell, Head head, Integer integer, Boolean aBoolean) {

    }

    @Override
    public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
                                 List<CellData> list, Cell cell, Head head, Integer integer, Boolean aBoolean) {
        //当前行
        int curRowIndex = cell.getRowIndex();
        //当前列
        int curColIndex = cell.getColumnIndex();

        if (curRowIndex > mergeRowIndex) {
            for (int i = 0; i < mergeColumnIndex.length; i++) {
                if (curColIndex == mergeColumnIndex[i]) {
                    mergeWithPrevRow(writeSheetHolder, cell, curRowIndex, curColIndex);
                    break;
                }
            }
        }
    }

    private void mergeWithPrevRow(WriteSheetHolder writeSheetHolder, Cell cell, int curRowIndex, int curColIndex) {
        //获取当前行的当前列的数据和上一行的当前列列数据,通过上一行数据是否相同进行合并
        Object curData = cell.getCellTypeEnum() == CellType.STRING ? cell.getStringCellValue() :
                cell.getNumericCellValue();
        Cell preCell = cell.getSheet().getRow(curRowIndex - 1).getCell(curColIndex);
        Object preData = preCell.getCellTypeEnum() == CellType.STRING ? preCell.getStringCellValue() :
                preCell.getNumericCellValue();
        // 比较当前行的第一列的单元格与上一行是否相同,相同合并当前单元格与上一行
        //
        if (curData.equals(preData)) {
            Sheet sheet = writeSheetHolder.getSheet();
            List<CellRangeAddress> mergeRegions = sheet.getMergedRegions();
            boolean isMerged = false;
            for (int i = 0; i < mergeRegions.size() && !isMerged; i++) {
                CellRangeAddress cellRangeAddr = mergeRegions.get(i);
                // 若上一个单元格已经被合并,则先移出原有的合并单元,再重新添加合并单元
                if (cellRangeAddr.isInRange(curRowIndex - 1, curColIndex)) {
                    sheet.removeMergedRegion(i);
                    cellRangeAddr.setLastRow(curRowIndex);
                    sheet.addMergedRegion(cellRangeAddr);
                    isMerged = true;
                }
            }
            // 若上一个单元格未被合并,则新增合并单元
            if (!isMerged) {
                CellRangeAddress cellRangeAddress = new CellRangeAddress(curRowIndex - 1, curRowIndex, curColIndex,
                        curColIndex);
                sheet.addMergedRegion(cellRangeAddress);
            }
        }
    }
}

5、调用方

package com.shucha.deveiface.biz.test;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.ServletOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * @author tqf
 * @Description 生成excel文件,多个sheet写入数据
 * @Version 1.0
 * @since 2022-07-11 10:16
 */
@Slf4j
public class MoreExcelSheet {
    // 生成的文件路径和名称
    private static File file = new File("D:/Excel多个sheet.xls");

    public static void main(String[] args) throws IOException {
        // 日志列表数据
        List<Log> dtoList = getData();
        // 用户信息列表数据
        List<User> userList = getUserData();

        String sheetName[] = {"操作日志","用户信息"};

        // sheet的对象class
        List<Class> classArray = new ArrayList<>();
        classArray.add(Log.class);
        classArray.add(User.class);

        //需要合并的列
        int[] mergeColumeIndex = {0,1};
        // 从第二行后开始合并
        int mergeRowIndex = 1;

        //不存在则新增
        if(!file.getParentFile().exists()){
            file.getParentFile().mkdirs();
        }
        if(!file.exists()){
            file.createNewFile();
        }
        FileOutputStream output = new FileOutputStream(file);
        // ServletOutputStream output = response.getOutputStream();
        ExcelWriter excelWriter = EasyExcel.write(output).build();
        for (int i=0;i<sheetName.length;i++) {
            WriteSheet writeSheet;
            if(i == 0 ){
                writeSheet = EasyExcel.writerSheet(i, sheetName[i])
                        // 指定合并列
                        .registerWriteHandler(new ExcelFillCellMergeStrategy(mergeRowIndex, mergeColumeIndex))
                        .head(classArray.get(i))
                        .build();
                excelWriter.write(dtoList, writeSheet);
            } else {
                writeSheet = EasyExcel.writerSheet(i, sheetName[i])
                        .head(classArray.get(i))
                        .build();
                excelWriter.write(userList, writeSheet);
            }
        }
        //千万别忘记finish 会帮忙关闭流
        excelWriter.finish();
        output.flush();

    }

    public static List<Log> getData(){
        String fileName = "D:/log1.xls";
        // 这里 需要指定读用哪个class去读,然后读取第一个sheet 同步读取会自动finish
        List<Log> list = EasyExcel.read(fileName).head(Log.class).sheet().doReadSync();
        return list;
    }

    public static List<User> getUserData(){
        String userPath = "D:/123.xls";
        List<User> userList = EasyExcel.read(userPath).head(User.class).sheet().doReadSync();
        return userList;
    }
/**
     * 1万条数据写入多个sheet
     * @throws IOException
     */
    public static void moreSheet() throws IOException {
        List<User> userList = getUserData();

        FileOutputStream output = new FileOutputStream(file);
        // ServletOutputStream output = response.getOutputStream();
        ExcelWriter excelWriter = EasyExcel.write(output).build();

        // 每个sheet写入的数据量
        int len = 2000;
        // 总的数据量
        int size = userList.size();
        // 多少个sheet
        int count = (size + len - 1) / len;
        for (int i = 0; i < count; i++) {
            List<User> dataList = userList.subList(i * len, ((i + 1) * len > size ? size : len * (i + 1)));
            WriteSheet writeSheet;
            writeSheet = EasyExcel.writerSheet(i, "sheet"+(i+1)+"")
                    .head(User.class)
                    .build();
            excelWriter.write(dataList, writeSheet);
        }
        //千万别忘记finish 会帮忙关闭流
        excelWriter.finish();
        output.flush();
    }
}

生成文件截图如下:

<think>好的,我现在需要帮助用户解决关于Java按模板导出Excel文件并根据模板生成多个表格的问题。首先,我得理清用户的需求。他们可能希望根据一个已有的Excel模板,动态生成多个相同格式的表格,并将数据填充进去。这通常用于生成报告、账单等需要统一格式的场景。 接下来,我应该考虑常用的Java库。Apache POI是处理Excel文件的主流选择,支持.xls和.xlsx格式。但是,使用POI直接操作模板可能会比较复杂,尤其是处理样式和合并单元格时。另一个选择是使用基于POI封装的工具,比如EasyExcel,它简化了一些操作,但可能在处理复杂模板时不够灵活。 用户提到需要根据模板生成多个表格,可能需要复制模板中的表格结构,然后逐个填充数据。这里的关键点是如何在保留原有样式的同时复制表格。Apache POI的XSSFWorkbook和HSSFWorkbook分别对应.xlsx和.xls格式,需要注意两者的差异。 步骤方面,首先需要加载模板文件,可能需要使用FileInputStream读取模板。然后,获取工作表,可能有两种情况:如果模板中已经有一个示例表格,需要复制它;或者模板中只是定义了一个表格的结构,需要在代码中动态创建多个表格。如果是复制现有表格,可能需要用到POI的cloneSheet方法,但需要注意样式的复制可能不完全,需要手动处理。 填充数据时,需要定位到每个单元格,设置值。可能需要遍历数据集合,为每个数据对象生成一个表格。这时候需要确定每个表格的位置,比如每个表格之间是否有空行,或者是否在同一个工作表中放置多个表格。 样式的处理是关键难点。直接复制行或单元格时,样式可能需要逐个应用,或者通过获取模板中的样式并应用到新单元格上。POI中样式是基于工作簿的,所以需要从模板中读取样式,然后创建新的样式对象并应用到目标单元格。 另外,合并单元格的情况需要特别注意。复制合并区域时,需要使用Sheet.addMergedRegion方法,并确保合并的起始和结束行正确。特别是在生成多个表格时,每个表格的位置偏移量需要计算正确,否则合并区域可能会错乱。 用户可能还会关心性能问题,当数据量较大时,生成多个表格可能会导致内存消耗增加。这时候可以考虑使用SXSSFWorkbook来优化内存,但需要注意它只支持.xlsx格式,并且是流式写入,可能不适合需要频繁修改的情况。 示例代码部分,可能需要展示如何加载模板、复制行、填充数据和保存文件。例如,读取模板中的第一行作为表头,然后循环数据表,为每个数据项复制表头行和数据行,替换相应的占位符。或者,如果模板中有占位符如${name},可以使用POI的CellScanner和DataFormatter来查找并替换这些占位符。 此外,用户可能需要了解如何处理动态行数的情况,比如一个表格的数据行数不固定,需要根据数据量动态添加行。这时候需要在模板中定义好行的样式,并在代码中根据数据量复制足够多的行,同时调整合并区域的位置。 最后,测试是很重要的。生成后的Excel文件需要打开检查样式是否正确,数据是否填充准确,合并单元格是否无误。可能还需要处理异常情况,比如模板文件不存在,或者数据格式不正确等。 总结下来,解决方案的大致步骤是:加载模板、定位模板中的表格结构、循环复制并填充数据、处理样式和合并区域、调整每个表格的位置、保存生成文件。同时,需要注意代码的可维护性和性能优化。</think>以下分步骤介绍Java按模板导出Excel生成多个表格的实现方法: 一、技术选型建议 1. 推荐使用Apache POI库(4.1.2+版本) 2. 备用方案:EasyExcel(适合简单模板) 3. 文件格式建议: - 复杂样式使用.xlsx(XSSFWorkbook) - 大数据量使用.xls(HSSFWorkbook) 二、实现步骤 1. 准备模板文件 ```excel | 姓名 | 年龄 | 部门 | <-- 第3行 | ${name} | ${age} | ${dept} | <-- 第4行(数据行) ``` 2. 添加POI依赖 ```xml <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>5.2.3</version> </dependency> ``` 3. 核心实现代码 ```java public class ExcelExporter { public void export(String templatePath, String outputPath, List<Employee> dataList) { try (FileInputStream fis = new FileInputStream(templatePath); Workbook workbook = WorkbookFactory.create(fis)) { Sheet sheet = workbook.getSheetAt(0); int templateStartRow = 3; // 模板数据起始行 int rowHeight = 2; // 每个数据对象占用的行数 // 复制样式缓存 Map<Integer, CellStyle> styleCache = cacheStyles(sheet.getRow(templateStartRow), workbook); for (int i = 0; i < dataList.size(); i++) { int targetRowNum = templateStartRow + i * rowHeight; copyRows(sheet, templateStartRow, targetRowNum, rowHeight, styleCache); fillData(sheet.getRow(targetRowNum), dataList.get(i)); } // 删除模板行 sheet.removeRow(sheet.getRow(templateStartRow)); try (FileOutputStream fos = new FileOutputStream(outputPath)) { workbook.write(fos); } } } private Map<Integer, CellStyle> cacheStyles(Row templateRow, Workbook workbook) { Map<Integer, CellStyle> styles = new HashMap<>(); for (Cell cell : templateRow) { CellStyle newStyle = workbook.createCellStyle(); newStyle.cloneStyleFrom(cell.getCellStyle()); styles.put(cell.getColumnIndex(), newStyle); } return styles; } private void copyRows(Sheet sheet, int sourceRow, int targetRow, int rowCount, Map<Integer, CellStyle> styles) { for (int i = 0; i < rowCount; i++) { Row newRow = sheet.createRow(targetRow + i); Row srcRow = sheet.getRow(sourceRow + i); for (Cell cell : srcRow) { Cell newCell = newRow.createCell(cell.getColumnIndex()); applyStyle(newCell, styles.get(cell.getColumnIndex())); } } } private void fillData(Row row, Employee employee) { row.getCell(0).setCellValue(employee.getName()); row.getCell(1).setCellValue(employee.getAge()); row.getCell(2).setCellValue(employee.getDepartment()); } } ``` 三、高级功能处理 1. 合并单元格处理 ```java // 复制原有合并区域 List<CellRangeAddress> mergedRegions = sheet.getMergedRegions(); for (CellRangeAddress region : mergedRegions) { CellRangeAddress newRegion = new CellRangeAddress( region.getFirstRow() + rowOffset, region.getLastRow() + rowOffset, region.getFirstColumn(), region.getLastColumn() ); sheet.addMergedRegion(newRegion); } ``` 2. 动态扩展处理 - 使用行克隆API(谨慎使用): ```java sheet.copyRow(sourceRowNum, targetRowNum); ``` 四、性能优化建议 1. 启用缓存模式(大数据量) ```java SXSSFWorkbook workbook = new SXSSFWorkbook(100); // 保留100行在内存 ``` 2. 样式复用原则 - 提前创建公用样式 - 避免循环内创建样式对象 五、常见问题解决方案 1. 样式丢失问题 - 使用`CellUtil`工具类同步样式 ```java CellUtil.copyCell(srcCell, destCell); ``` 2. 公式失效问题 - 最后执行公式刷新 ```java workbook.getCreationHelper().createFormulaEvaluator().evaluateAll(); ``` 六、扩展方案 1. 使用模板引擎(Freemarker) - 适合复杂模板但需要转换为XML格式 2. 基于注解的映射(EasyExcel) ```java @ExcelProperty("姓名") private String name; ``` 实际开发中建议: 1. 保持模板结构简单 2. 使用占位符标记数据位置 3. 做好异常处理(内存溢出、文件锁定等)
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码奴生来只知道前进~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值