[EasyExcel] 导出合并单元格
前言
使用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] 导出合并单元格的更多相关文章
- C#导出Excel,并且设置Excel单元格格式,合并单元格.
注:要添加COM组件 Microsoft Excel 11.0 Object Library 引用. 具体代码如下: using System; using System.Collections.G ...
- 带复杂表头合并单元格的HtmlTable转换成DataTable并导出Excel
步骤: 一.前台JS取HtmlTable数据,根据设定的分隔符把数据拼接起来 <!--导出Excel--> <script type="text/javascript&qu ...
- poi导出Excel报表多表头双层表头、合并单元格
效果图: controller层方法: /** * * 导出Excel报表 * @param request * @return * */ @ ...
- poi合并单元格同时导出excel
poi合并单元格同时导出excel POI进行跨行需要用到对象HSSFSheet对象,现在就当我们程序已经定义了一个HSSFSheet对象sheet. 跨第1行第1个到第2个单元格的操作为 sheet ...
- java使用freemarker模板导出word(带有合并单元格)文档
来自:https://blog.csdn.net/qq_33195578/article/details/73790283 前言:最近要做一个导出word功能,其实网上有很多的例子,但是我需要的是合并 ...
- poi导出excel合并单元格(包括列合并、行合并)
1 工程所需jar包如下:commons-codec-1.5.jarcommons-logging-1.1.jarlog4j-1.2.13.jarjunit-3.8.1.jarpoi-3.9-2012 ...
- 在Asp.Net MVC中使用NPOI插件实现对Excel的操作(导入,导出,合并单元格,设置样式,输入公式)
前言 NPOI 是 POI 项目的.NET版本,它不使用 Office COM 组件,不需要安装 Microsoft Office,目前支持 Office 2003 和 2007 版本. 1.整个Ex ...
- WPF 导出Excel(合并单元格)
WPF 导出Excel(合并单元格) DataTable 导出Excel(导出想要的列,不想要的去掉) ,B1,B2,B3,B4,B5} MisroSoft.Office.Interop.Excel. ...
- 复杂的POI导出Excel表格(多行表头、合并单元格)
poi导出excel有两种方式: 第一种:从无到有的创建整个excel,通过HSSFWorkbook,HSSFSheet HSSFCell, 等对象一步一步的创建出工作簿,sheet,和单元格,并添加 ...
- java导出标题多行且合并单元格的EXCEL
场景:项目中遇到有需要导出Excel的需求,并且是多行标题且有合并单元格的,参考网上的文章,加上自己的理解,封装成了可自由扩展的导出工具 先上效果,再贴代码: 调用工具类进行导出: public st ...
随机推荐
- 值得推荐的Blazor UI组件库
前言 本文主要是推荐一些开源.免费.实用.美观的Blazor UI组件库,提供给广大C#/.NET开发者们学习和使用(注意:排名不分先后,都是十分优秀的开源框架和项目).本文中的所有框架都已经收录到适 ...
- tomghost
思路: 先使用端口扫描,会发现22,8009,8080 8009的考察点:tomcat ajp协程属性设置导致的文件读取和文件执行. https://github.com/00theway/Ghost ...
- x86 机器指令编码规则
x86 机器指令编码依次由一下部分组成: 指令前缀(prefix,非必需) 操作码(opcode,必需) 寻址方式 R/M(ModR/M,非必需) 比例因子-变址-基址(SIB,非必需) 地址偏移量( ...
- Hybrid 实验
实验拓扑 实验需求 按图示给各 PC 配置 IP 地址 PC1属于 VLAN 10 : PC2 与 PC5 属于 VLAN 20:PC4属于 VLAN 30 实现全网互通 实验步骤 1.配置链路聚合 ...
- error: #20: identifier "arm_cfft_instance_f64" is undefined
在使用Keil5的过程中,偶尔遇到这个问题,以及类似的问题,报错的数量大概200多个. error type>(42): error: #20: identifier "arm_cff ...
- WPF实现新手引导
1. 半透明灰的遮罩层 新建一个遮盖的window窗体 canvas是后期可以在思显示高亮区域 //定义一个window将它的样式设置透明等可以覆盖到其他窗体上,其中遮罩层使用border控件 //原 ...
- Cause: org.apache.ibatis.builder.BuilderException: Ambiguous collection type for property 'emps'. You must specify 'javaType' or 'resultMap'
错误原因 这个错误通常表示在解析 Mybatis 映射文件(Mapper XML)时出现了问题,可能的原因有两个: 集合属性缺少 javaType 或 resultMap 属性:该错误信息显示了 &q ...
- 获取邮箱(QQ、126、163)的客户端授权码
获取QQ邮箱的客户端授权码 1.首先登录QQ邮箱(https://mail.qq.com),然后,点击"设置"按钮. 2.点击"账户"按钮,进入"账户 ...
- 用写代码的方式画图-试下PlantUML吧
1 序言 所谓一图胜千言,大家平日在工作中编写文档时,往往都需要画各种图来表达中心思想,比如流程图.时序图.UML 图,很多人选择使用 Axure .PrecessOn.Diagrams(darw.i ...
- CH32V003使用ADC八通道转换注意事项
本文以CH32V003_F4P6(20Pin)为模板 1.PA1.PA2为外部晶振输入引脚,同时也是ADC的CH1与CH0,所以需要先在system_ch32v00x.c文件中更改为内部48M的宏即可 ...