[EasyExcel] 导出合并单元格

easyexcel,导出,合并,单元格 · 浏览次数 : 478

小编点评

## 生成内容时需要带简单的排版 以下内容需要带简单的排版: * **合并单元格后第一列序列号也需要根据其他列进行合并单元格且序列号还必须保持连续** * **根据部分列合并单元格,隔行合并单元格等等情况,就需要开发者对easyExcel的处理器类里面的api比较了解才能完成** * **遇到问题可以留言,看到也会尝试一起处理解决** ## 示例代码 以下代码展示了如何合并单元格后第一列序列号也需要根据其他列进行合并单元格且序列号还必须保持连续的例子: ```python # 使用easyExcel的处理类进行合并单元格操作 class SimpleCommonExcelMergeUtil(CellWriteHandler): def beforeCellCreate(self, writeSheetHolder, writeTableHolder, row, head, column, relativeRowIndex, isHead): # 如果当前单元格是合并单元格的第一列,则获取该列的第一个数据 if column == 0 and isHead: cellData = writeSheetHolder.getSheet().getCell(row, column) # 返回True表示可以进行合并单元格操作 return True def afterCellCreate(self, writeSheetHolder, writeTableHolder, cellData, cell, head, relativeRowIndex, isHead): # 如果当前单元格是合并单元格的第二列,则获取该列的第一个数据 if column == 1 and isHead: preCellData = writeSheetHolder.getSheet().getCell(row, column - 1) # 设置合并单元格的第一列和第二列的序列号保持连续 cellData.setSequenceNumber(preCellData.getSequenceNumber() + 1) # 创建合并单元格的类 class MergeCellUtil(SimpleCommonExcelMergeUtil): def afterCellDataConverted(self, writeSheetHolder, writeTableHolder, cellData, cell, head, relativeRowIndex, isHead): # 如果当前单元格是合并单元格的第三列,则判断该列是否已经被合并单元格 if column == 3 and isHead: # 如果该列已经被合并单元格,则获取该列的第一个数据 firstCellData = writeSheetHolder.getSheet().getCell(row, column - 1) # 设置合并单元格的第三列的序列号保持连续 cellData.setSequenceNumber(firstCellData.getSequenceNumber() + 1) ``` ## 总结 合并单元格后第一列序列号也需要根据其他列进行合并单元格且序列号还必须保持连续的例子,需要开发者对easyExcel的处理器类里面的api比较了解才能完成。遇到问题可以留言,看到也会尝试一起处理解决。

正文

前言

使用spring boot 对excel 进行操作在平时项目中要经常使用。常见通过jxl和poi 的方式进行操作。但他们都存在一个严重的问题就是非常的耗内存。这里介绍一种 Easy Excel 工具来对excel进行操作。

一、Easy Excel是什么?

EasyExcel是阿里巴巴开源的一个excel处理框架,以使用简单、节省内存著称。easyExcel能大大减少占用内存的主要原因是在解析Excel时没有将文件数据一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析。

二、使用EasyExcel 实现读操作

从excel 中读取数据,常用的场景就是读取excel的数据,将相应的数据保存到数据库中。需要实现一定的逻辑处理。

1、导入依赖

<dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>easyexcel</artifactId>
      <version>2.2.10</version>
</dependency>

2、创建读取数据封装类

@Data
public class User {

    @ExcelProperty(index = 0)
    private Integer id;

    @ExcelProperty(index = 1)
    private String name;

    @ExcelProperty(index = 2)
    private Integer age;
}

比如我们要读取两列的数据,就写两个属性。@ExcelProperty(index = 0)来设置要读取的列,index=0表示读取第一列。

3、创建读取excel的监听类

监听器继承 AnalysisEventListener 类

@Slf4j
public class UserExcelListener extends AnalysisEventListener<User> {

    /**
     * 解析excel文档的每一行
     * @param user 参数user即是每行读取数据转换的User对象
     * @param analysisContext
     */
    @Override
    public void invoke(User user, AnalysisContext analysisContext){
        log.info("excel数据行:{}",user.toString());
    }

    /**
     * 整个文档解析完执行
     * @param analysisContext
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        log.info("文档解析完毕");
    }
}

当解析每一条数据时都会调用invoke方法,当所有数据都解析完毕时最后会调用doAfterAllAnalysed方法。可以在监听类内的方法中将每次读取到的数据进行保存或者其他操作处理。

4、接口使用easyExcel读取excel文件调用监听器

/**
     * 上传excel文件并读取其中内容
     *
     * @param file
     * @return
     */
    @PostMapping("/upload")
    public String uploadExcel(MultipartFile file) {
        log.info("easyExcel上传文件:{}", file);
        try {
            InputStream inputStream = file.getInputStream();
            EasyExcel.read(inputStream, User.class, new UserExcelListener())
                    .sheet()
                    .doRead();
        } catch (Exception e) {

        }
        return "表格文件上传成功";
    }

三、使用EasyExcel 实现写操作

写操作有两种写法,一种是不创建对象的写入,另一种是根据对象写入。这里主要介绍创建对象写入

创建对象写入

1、创建excel对象类

@Data
public class User {

    @ExcelProperty(index = 0)
    private Integer id;

    @ExcelProperty(index = 1)
    private String name;

    @ExcelProperty(index = 2)
    private Integer age;
}

注意@ExcelProperty(“用户编号”) 会生成相应的列名为 用户编号,如果不设置,则会直接将字段名设置为excel的列名。

2、接口使用测试数据导出(常规导出不合并单元格)

/**
     * 输出导出excel
     */
    @PostMapping("/export")
    public void export() {
        ArrayList<User> users = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            User user = new User();
            user.setId(i);
            user.setName("测试用户-" + i);
            user.setAge(20 + i);
            users.add(user);
        }
        log.info("导出数据结果集:{}", users);
        String fileName = "C:\\Users\\pytho\\Desktop\\fsdownload\\用户信息表.xlsx";
        EasyExcel.write(fileName, User.class)
                .autoCloseStream(true)
                .sheet("sheet名称")
                .doWrite(users);
    }

3、接口测试导出(单列合并单元格)

/**
     * 输出导出excel
     */
    @PostMapping("/export1")
    public void export1() {
        ArrayList<User> users = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            User user = new User();
            user.setId(i);
            if (i == 3 || i == 4 || i == 5) {
                user.setName("测试用户-3");
            } else {
                user.setName("测试用户-" + i);
            }
            user.setAge(20 + i);
            users.add(user);
        }
        log.info("导出数据结果集:{}", users);
        String fileName = "C:\\Users\\pytho\\Desktop\\fsdownload\\(单列相同内容合并单元格)用户信息表.xlsx";
        EasyExcel.write(fileName, User.class)
                .registerWriteHandler(new SimpleExcelMergeUtil())
                .autoCloseStream(true)
                .sheet("sheet名称")
                .doWrite(users);
    }

如果要对导出的excel进行处理,就需要自定义处理器类进行处理

自定义easyExcel处理器(单列合并:根据用户id相同的列进行合并单元格):

/**
 * @version 1.0
 * @Package: com.stech.bms.buss.utils
 * @ClassName: ExcelMergeUtil
 * @Author: sgq
 * @Date: 2023/7/28 13:29
 * @Description: 仅处理单列数据相同合并单元格
 */
public class SimpleExcelMergeUtil implements CellWriteHandler {

    public SimpleExcelMergeUtil() {
    }

    /**
     * 创建每个单元格之前执行
     *
     * @param writeSheetHolder
     * @param writeTableHolder
     * @param row
     * @param head
     * @param columnIndex
     * @param relativeRowIndex
     * @param isHead
     */
    @Override
    public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) {

    }

    /**
     * 创建每个单元格之后执行
     *
     * @param writeSheetHolder
     * @param writeTableHolder
     * @param cell
     * @param head
     * @param relativeRowIndex
     * @param isHead
     */
    @Override
    public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {

    }

    /**
     * 每个单元格数据内容渲染之后执行
     *
     * @param writeSheetHolder
     * @param writeTableHolder
     * @param cellData
     * @param cell
     * @param head
     * @param relativeRowIndex
     * @param isHead
     */
    @Override
    public void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, CellData cellData, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {

    }

    /**
     * 每个单元格完全创建完之后执行
     *
     * @param writeSheetHolder
     * @param writeTableHolder
     * @param cellDataList
     * @param cell
     * @param head
     * @param relativeRowIndex
     * @param isHead
     */
    @Override
    public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<CellData> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
        // 当前行
        int curRowIndex = cell.getRowIndex();
        // 当前列
        int curColIndex = cell.getColumnIndex();

        if (!isHead) {
            if (curRowIndex > 1 && curColIndex == 1) {
                // 从第二行数据行开始,获取当前行第二列数据
                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> mergedRegions = sheet.getMergedRegions();
                    boolean isMerged = false;
                    for (int i = 0; i < mergedRegions.size() && !isMerged; i++) {
                        CellRangeAddress cellRangeAddr = mergedRegions.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);
                    }
                }
            }
        }
    }
}

4、接口测试导出(通用合并单元格)

/**
     * 输出导出excel
     */
    @PostMapping("/export2")
    public void export2() {
        ArrayList<User> users = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            User user = new User();
            user.setId(i);
            if (i == 3 || i == 4 || i == 5) {
                user.setName("测试用户-3");
            } else {
                user.setName("测试用户-" + i);
            }
            user.setAge(20 + i);
            users.add(user);
        }
        log.info("导出数据结果集:{}", users);
        // 从第几行开始合并
        int mergeStartRowIndex = 5;
        // 需要合并哪些列
        int[] mergeColumns = {1};
        String fileName = "C:\\Users\\pytho\\Desktop\\fsdownload\\(单列相同内容合并单元格-通用版)用户信息表.xlsx";
        EasyExcel.write(fileName, User.class)
                .registerWriteHandler(new SimpleCommonExcelMergeUtil(mergeStartRowIndex,mergeColumns))
                .autoCloseStream(true)
                .sheet("sheet名称")
                .doWrite(users);
    }

excel处理器类:

/**
 * @version 1.0
 * @Package: com.stech.bms.buss.utils
 * @ClassName: ExcelMergeUtil
 * @Author: sgq
 * @Date: 2023/7/28 13:29
 * @Description: 仅处理单列数据相同合并单元格
 */
public class SimpleCommonExcelMergeUtil implements CellWriteHandler {

    private int mergeStartRowIndex;
    private int[] mergeColumns;
    private List<Integer> mergeColumnList;

    public SimpleCommonExcelMergeUtil() {
    }

    public SimpleCommonExcelMergeUtil(int mergeStartRowIndex, int[] mergeColumns) {
        this.mergeStartRowIndex = mergeStartRowIndex;
        this.mergeColumns = mergeColumns;
        mergeColumnList = new ArrayList<>();
        for (int i : mergeColumns) {
            mergeColumnList.add(i);
        }
    }

    /**
     * 创建每个单元格之前执行
     *
     * @param writeSheetHolder
     * @param writeTableHolder
     * @param row
     * @param head
     * @param columnIndex
     * @param relativeRowIndex
     * @param isHead
     */
    @Override
    public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) {

    }

    /**
     * 创建每个单元格之后执行
     *
     * @param writeSheetHolder
     * @param writeTableHolder
     * @param cell
     * @param head
     * @param relativeRowIndex
     * @param isHead
     */
    @Override
    public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {

    }

    /**
     * 每个单元格数据内容渲染之后执行
     *
     * @param writeSheetHolder
     * @param writeTableHolder
     * @param cellData
     * @param cell
     * @param head
     * @param relativeRowIndex
     * @param isHead
     */
    @Override
    public void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, CellData cellData, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {

    }

    /**
     * 每个单元格完全创建完之后执行
     *
     * @param writeSheetHolder
     * @param writeTableHolder
     * @param cellDataList
     * @param cell
     * @param head
     * @param relativeRowIndex
     * @param isHead
     */
    @Override
    public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<CellData> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
        // 当前行
        int curRowIndex = cell.getRowIndex();
        // 当前列
        int curColIndex = cell.getColumnIndex();

        if (!isHead) {
            if (curRowIndex > mergeStartRowIndex && mergeColumnList.contains(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> mergedRegions = sheet.getMergedRegions();
                    boolean isMerged = false;
                    for (int i = 0; i < mergedRegions.size() && !isMerged; i++) {
                        CellRangeAddress cellRangeAddr = mergedRegions.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);
                    }
                }
            }
        }
    }
}

这只是简单的合并单元格例子,抛砖引玉的作用。工作中可能会遇到很多情况:合并单元格后第一列序列号也需要根据其他列进行合并单元格且序列号还必须保持连续,根据部分列合并单元格,隔行合并单元格等等情况,这就需要开发者对easyExcel的处理器类里面的api比较了解才能完成。遇到的问题也可以留言,看到也会尝试一起处理解决。

 

与[EasyExcel] 导出合并单元格相似的内容:

[EasyExcel] 导出合并单元格

前言 使用spring boot 对excel 进行操作在平时项目中要经常使用。常见通过jxl和poi 的方式进行操作。但他们都存在一个严重的问题就是非常的耗内存。这里介绍一种 Easy Excel 工具来对excel进行操作。 一、Easy Excel是什么? EasyExcel是阿里巴巴开源的一

比 poi导入导出更好用的 EasyExcel使用小结

转载请注明出处: 官方文档: https://easyexcel.opensource.alibaba.com/docs/current/quickstart/read 1.简洁 Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,p

EasyExcel处理Mysql百万数据的导入导出案例,秒级效率,拿来即用!

一、写在开头 今天终于更新新专栏 《EfficientFarm》 的第二篇博文啦,本文主要来记录一下对于EasyExcel的高效应用,包括对MySQL数据库百万级数据量的导入与导出操作,以及性能的优化(争取做到秒级性能!)。 二、如何做技术选型 其实在市面上我们有很多常用的excel操作依赖库,除了

easyExcel多行表头设定不同样式和特定单元格设定样式的实现

前言 有个需求,需要设置Excel导出的样式,样式如下图所示,有三个表头行,第一个表头行需要加粗和灰色背景,另外两个表头行使用另外的样式,并且当测试结果单元格出现x或者未通过的时候,设置其为红色字体。 实现步骤 写入ExcelSheet的部分代码 for (Map.Entry

EasyExcel 无法读取图片?用poi写了一个工具类

在平时的开发中,经常要开发 Excel 的导入导出功能。一般使用 poi 或者 EasyExcel 开发,使用 poi 做 excel 比较复杂,大部分开发都会使用 EasyExcel 因为一行代码就能实现导入和导出的功能。但是 EasyExcel 不支持图片的读的操作,本文操作如何实现图片的读和写

SpringBoot 整合 EasyExcel 实现自由导入导出,太强了

在实际的业务系统开发过程中,操作 Excel 实现数据的导入导出基本上是个非常常见的需求。 之前,我们有介绍一款非常好用的工具:EasyPoi,有读者提出在数据量大的情况下,EasyPoi 会占用内存大,性能不够好,严重的时候,还会出现内存异常的现象。 今天我给大家推荐一款性能更好的 Excel 导

厉害了!12秒将百万数据通过EasyExcel导入MySQL数据库中

一、写在开头 我们在上一篇文章中提到了通过EasyExcel处理Mysql百万数据的导入功能(一键看原文),当时我们经过测试数据的反复测验,100万条放在excel中的数据,4个字段的情况下,导入数据库,平均耗时500秒,这对于我们来说肯定难以接受,今天我们就来做一次性能优化。 二、性能瓶颈分析 一

在表格开发中,如何选择适合自己的处理工具?

引言 GcExcel和EasyExcel都是卓越的高性能Excel处理库。GcExcel是由葡萄城公司开发,可用于Java和.Net平台;而EasyExcel是阿里巴巴开发的基于Java的开源Excel处理库。 在本文中,我们将对GcExcel和EasyExcel进行比较,帮助读者在实际场景中做出明