JAVA导出Excel通用工具——第二篇:使用EasyExcel导出excel的多种情况的例子介绍
- 1. 前言
- 2. 依赖
- 3. 导出简单例子
- 3.1 ① 基础入门例子
- 3.1.1 核心代码
- 3.1.2 效果展示
- 3.2 ② 注解的简单使用
- 3.2.1 @ExcelIgnore
- 3.2.2 @ExcelProperty
- 3.2.2.1 一般效果(表头合并等)
- 3.2.2.2 其他特殊情况(枚举映射等)
- 1. 枚举使用1
- 2. 枚举使用2
- 3. 其他
- 3.2.3 数据格式转换
- 3.2.3.1 @NumberFormat
- 3.2.3.2 @DateTimeFormat
- 3.2.4 @
- 4. 重复写入 和 使用table写入
- 4.1 重复多次写入
- 4.2 不同对象写到同一sheet页内(table写入)
- 5. 合并单元格
- 5.1 表头合并
- 5.2 内容合并
- 5.2.1 非动态合并
- 5.2.2 动态合并(自定义单元格合并策略)
- 5.2.2.1 核心代码
- 5.2.2.2 处理数据
- 5.2.2.3 测试看效果
- 6. 自定义拦截器
- 6.1 自定义拦截器->单元格合并策略
- 6.2 自定义拦截器->数据单元格增加下拉框
- 6.2.1 只针对单列设置下拉数据
- 6.2.2 支持可多列设置下拉数据
- 6.3 自定义拦截器->设置表头支持筛选
- 7. 表格样式
- 8. 结束
1. 前言
- 本文构造数据代码都可以参考上篇POI文章,上篇使用POI导出的请看下面文章:
JAVA导出Excel通用工具类——仅此一篇足以让你精通(详细介绍POI 和 EasyExcel导出excel的多种情况,包括筛选、合并单元格等复杂情况——保姆级别,不能再详细了,代码拿来即用.
2. 依赖
-
如下:
<dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>3.1.5</version> </dependency>
3. 导出简单例子
3.1 ① 基础入门例子
3.1.1 核心代码
- 实体:EasyCatEntity.java
package com.liu.susu.excel.easyexcel.example.export.data; import com.alibaba.excel.annotation.ExcelIgnore; import com.alibaba.excel.annotation.ExcelProperty; import com.liu.susu.excel.common.enums.CatKindEnum; import com.liu.susu.excel.common.enums.SexEnum; import lombok.Data; import lombok.experimental.Accessors; /** * description * * @author susu **/ @Data @Accessors(chain = true) public class EasyCatEntity { @ExcelProperty(value = "姓名") private String name; @ExcelProperty(value = "性别") @ExcelIgnore //忽略这个字段的导出 private SexEnum sex; @ExcelIgnore //忽略这个字段的导出 private CatKindEnum kind; @ExcelProperty(value = "年龄") private String age; @ExcelProperty(value = "颜色") private String colour; @ExcelProperty(value = "猫猫的两个好朋友") private String friendOne; @ExcelProperty(value = "猫猫的两个好朋友")//如果表头单元格一样,自动合并 private String friendTwo; @ExcelProperty(value = "猫猫头标志") private String headFlag; @ExcelProperty(value = "猫猫价格") private Double price; }
- 造数:MakeEasyCatData.java
- 导出测试类 EasyTestExportA.java:
package com.liu.susu.excel.easyexcel.example.export.example1; import com.alibaba.excel.EasyExcel; import com.alibaba.excel.ExcelWriter; import com.alibaba.excel.write.metadata.WriteSheet; import com.liu.susu.excel.easyexcel.example.export.data.EasyCatEntity; import com.liu.susu.excel.easyexcel.example.export.data.MakeEasyCatData; import java.util.List; /** * description 基础入门写法 * * @author susu **/ public class EasyTestExportA { public static void main(String[] args) { /** * 注意: * 下面的三种写法:在数据量不大的情况下可以使用(5000以内,具体也要看实际情况),数据量大参照 重复多次写入 */ List<EasyCatEntity> catList = MakeEasyCatData.getCats();//获取测试数据 String fileName = "C:\\Users\\Administrator\\Desktop\\easyexcel导出\\excel_A1.xlsx"; // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 // 如果这里想使用03 则 传入 excelType 参数即可 /写法 1/ EasyExcel.write(fileName, EasyCatEntity.class) .sheet("模板") .doWrite(() -> { return catList; }); /写法 2/ fileName = "C:\\Users\\Administrator\\Desktop\\easyexcel导出\\excel_A2.xlsx"; EasyExcel.write(fileName, EasyCatEntity.class).sheet("模板").doWrite(catList); /写法 3/ fileName = "C:\\Users\\Administrator\\Desktop\\easyexcel导出\\excel_A3.xlsx"; try (ExcelWriter excelWriter = EasyExcel.write(fileName, EasyCatEntity.class).build()) { WriteSheet writeSheet = EasyExcel.writerSheet("模板").build(); excelWriter.write(catList, writeSheet); } } }
3.1.2 效果展示
- 效果如图:
3.2 ② 注解的简单使用
3.2.1 @ExcelIgnore
- 忽略某个字段的导出:属性上加上此注解即可,上面有用到不说了
- 单独
3.2.2 @ExcelProperty
3.2.2.1 一般效果(表头合并等)
- 实现横向合并:
@ExcelProperty(value = "猫猫的两个好朋友")
注解中的value值一样即可横向合并
- 复杂表头的横向合并
@ExcelProperty({"猫猫的两个好朋友", "朋友1"})
效果如下:
- 其他
3.2.2.2 其他特殊情况(枚举映射等)
1. 枚举使用1
-
使用枚举的时候,如果不进行转换会报错,错误如下:
Exception in thread "main" com.alibaba.excel.exception.ExcelWriteDataConvertException: Can not find 'Converter' support class CatKindEnum. at com.alibaba.excel.write.executor.AbstractExcelWriteExecutor.doConvert(AbstractExcelWriteExecutor.java:323) at com.alibaba.excel.write.executor.AbstractExcelWriteExecutor.convert(AbstractExcelWriteExecutor.java:277) at com.alibaba.excel.write.executor.AbstractExcelWriteExecutor.converterAndSet(AbstractExcelWriteExecutor.java:58) at com.alibaba.excel.write.executor.ExcelWriteAddExecutor.addJavaObjectToExcel(ExcelWriteAddExecutor.java:174) at com.alibaba.excel.write.executor.ExcelWriteAddExecutor.addOneRowOfDataToExcel(ExcelWriteAddExecutor.java:82) at com.alibaba.excel.write.executor.ExcelWriteAddExecutor.add(ExcelWriteAddExecutor.java:58) at com.alibaba.excel.write.ExcelBuilderImpl.addContent(ExcelBuilderImpl.java:59) at com.alibaba.excel.ExcelWriter.write(ExcelWriter.java:70) at com.alibaba.excel.ExcelWriter.write(ExcelWriter.java:47) at com.alibaba.excel.write.builder.ExcelWriterSheetBuilder.doWrite(ExcelWriterSheetBuilder.java:62) at com.alibaba.excel.write.builder.ExcelWriterSheetBuilder.doWrite(ExcelWriterSheetBuilder.java:79) at com.liu.susu.excel.easyexcel.example.export.example1.EasyTestExportA.main(EasyTestExportA.java:31)
-
怎么解决呢?
-
- 先看我的枚举
- 先看我的枚举
-
- 再看
@ExcelProperty
注解要求,可以怎么处理
所以,写个实现Converter
接口的转换类,泛型用Ienum
即可解决问题,往下看吧
- 再看
-
解决问题:
-
- 首先先写一个转换类,因为我弄的导出,在导出时发现了问题,所以只重写了关于导出的方法,如下:
package com.liu.susu.excel.easyexcel.common; import com.alibaba.excel.converters.Converter; import com.alibaba.excel.converters.WriteConverterContext; import com.alibaba.excel.metadata.data.WriteCellData; import com.baomidou.mybatisplus.annotation.IEnum; /** * description * * @author susu **/ public class MyEnumConverter implements Converter<IEnum> { @Override public WriteCellData<?> convertToExcelData(WriteConverterContext<IEnum> context) throws Exception { // return null; return new WriteCellData(context.getValue().toString());//导出的时候重写改方法 } }
- 首先先写一个转换类,因为我弄的导出,在导出时发现了问题,所以只重写了关于导出的方法,如下:
-
- 注解上使用,如下:
@ExcelProperty(value = "种类",converter = MyEnumConverter.class)
- 注解上使用,如下:
-
效果:
2. 枚举使用2
- 如果按照上面的转换类写成
implements Converter<IEnum>
这样,还会有个问题,什么问题呢?
对于部分没有实现IEnum接口的枚举,依然不能会报上面的错误,所以直接改成implements Converter<Enum>
即可,这样所有枚举都解决了,如下:
3. 其他
3.2.3 数据格式转换
3.2.3.1 @NumberFormat
- 实现占比数字格式化:
@NumberFormat("#.##%")
3.2.3.2 @DateTimeFormat
- 日期格式设置,使用如下:
@DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")
3.2.4 @
4. 重复写入 和 使用table写入
4.1 重复多次写入
- 先看官网提供的重复写入的三种情况,如果你有需求再看下面《4.2 不同对象写到同一sheet页内》
-
- 先看效果:
看到了吧,就是同一对象数据重复写入到同一个sheet内,很简单,直接看实现代码
- 先看效果:
-
- 代码如下:
/** * description 同一数据重复多次写到同一个sheet页 * * @author susu **/ public class EasyTestExportB1 { public static void main(String[] args) { List<EasyCatEntity> catList = MakeEasyCatData.getCats();//获取测试数据 String fileName = "C:\\Users\\Administrator\\Desktop\\easyexcel导出\\excel_B1.xlsx"; String sheetName = "模板"; try (ExcelWriter excelWriter = EasyExcel.write(fileName, EasyCatEntity.class).build()) { // 这里注意 如果同一个sheet只要创建一次 WriteSheet writeSheet = EasyExcel.writerSheet(sheetName).build(); // 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来 for (int i = 0; i < 5; i++) { excelWriter.write(catList, writeSheet); } } } }
- 代码如下:
- 那有没有提供别的多次写入方法呢?写入到多个sheet?
有,看看官网提供的这3个例子吧:
这几个需要的话,自己去官网拿例子吧
4.2 不同对象写到同一sheet页内(table写入)
- 上面的三个例子都没有提到这种情况,当然你也可以用上面的例子实现,但是会导致第二个table没有表头了,所以要想实现下面的效果,参考官网提供的另一种实现方式:使用table去写入
- 先看效果:
- 核心代码如下:
package com.liu.susu.excel.easyexcel.example.export.example2; import com.alibaba.excel.EasyExcel; import com.alibaba.excel.ExcelWriter; import com.alibaba.excel.write.metadata.WriteSheet; import com.alibaba.excel.write.metadata.WriteTable; import com.liu.susu.excel.easyexcel.example.export.data.EasyCatEntity; import com.liu.susu.excel.easyexcel.example.export.data.EasyDogEntity; import com.liu.susu.excel.easyexcel.example.export.data.MakeEasyCatData; import com.liu.susu.excel.easyexcel.example.export.data.MakeEasyDogData; import java.util.List; /** * description 不同对象写到同一sheet页内(同一个sheet页导出两个不同业务的table数据) * * @author susu **/ public class EasyTestExportB2 { public static void main(String[] args) { List<EasyCatEntity> catList = MakeEasyCatData.getCats();//小猫数据 List<EasyDogEntity> dogList = MakeEasyDogData.getDogs();//小狗数据 String fileName = "C:\\Users\\Administrator\\Desktop\\easyexcel导出\\excel_B4.xlsx"; String sheetName = "模板"; try { ExcelWriter excelWriter = null; try { excelWriter = EasyExcel.write(fileName).build(); WriteSheet writeSheet = EasyExcel.writerSheet(sheetName).build(); // 第一个对象,这里指定需要表头 WriteTable writeTableCat = EasyExcel.writerTable(0).head(EasyCatEntity.class).needHead(Boolean.TRUE).build(); // 第二个对象 读取对象的excel实体类中的标题 WriteTable writeTableDog = EasyExcel.writerTable(1).head(EasyDogEntity.class).needHead(Boolean.TRUE).build(); // 第一次写入:cat数据 excelWriter.write(catList, writeSheet, writeTableCat); // 第二次写入:dog数据,在第一次后面写入数据(上面设置了表头就会含表头) excelWriter.write(dogList, writeSheet, writeTableDog); } finally { // 记得finally 会帮忙关闭流 if (excelWriter != null) { excelWriter.finish(); } } } catch (Exception e) { e.printStackTrace(); } } }
- 官网例子如下:
5. 合并单元格
5.1 表头合并
- 请参考上面的《3.2.2.1 一般效果》,有说到表头合并,以及复杂表头合并
5.2 内容合并
5.2.1 非动态合并
- 类注解:
@OnceAbsoluteMerge
,使用方法和效果,如下:@OnceAbsoluteMerge(firstRowIndex = 2, lastRowIndex = 3, firstColumnIndex = 1, lastColumnIndex = 2)
- 属性注解:
@ContentLoopMerge
@ContentLoopMerge(eachRow = 2)//每两行一合并
- 使用easyexcel自带的单元格合并策略:
-
- 使用方法如下:
- 使用方法如下:
-
- 官网的合并策略:
自己可以点进去看看,这种方式的合并与上面注解实现的效果差不多,因为这个eachRow
参数是定死的,不能动态地根据单元格的内容相同去合并,要想实现自己想要的效果,还需自定义一个合并策略,往下看……
- 官网的合并策略:
5.2.2 动态合并(自定义单元格合并策略)
5.2.2.1 核心代码
- 核心代码如下:
- 上代码 MergerCellStrategy.java:
package com.liu.susu.excel.easyexcel.common.merge; import com.alibaba.excel.write.handler.RowWriteHandler; import com.alibaba.excel.write.handler.context.RowWriteHandlerContext; import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.util.CellRangeAddress; /** * description 单元格合并策略 * * @author susu **/ public class MergerCellStrategy implements RowWriteHandler { private final int[] columnIndex; public MergerCellStrategy(int[] columnIndex) { this.columnIndex = columnIndex; } @Override public void afterRowDispose(RowWriteHandlerContext context) { dealVerticalMerger(context); } public void dealVerticalMerger(RowWriteHandlerContext context) { /** * 1. 先获取当前行号,根据当前行号 获取上一行中此列的内容 * 2. 对比上一行的内容与此行内容是否一致 * 3. 最后拿到要合并的起始行、结束行、起始实列 和 结束列(其中其实列和结束列就是本列) */ Integer currentRowIndex = context.getRowIndex(); if (columnIndex == null) { return; } if (currentRowIndex == 0) { return; } WriteSheetHolder writeSheetHolder = context.getWriteSheetHolder(); Sheet sheet = writeSheetHolder.getSheet(); Row currentRow = context.getRow(); for (int colIndex : columnIndex) { String currentCellValue = currentRow.getCell(colIndex).getStringCellValue(); String upCellValue = sheet.getRow(currentRowIndex - 1).getCell(colIndex).getStringCellValue(); if (currentCellValue.equals(upCellValue)) { CellRangeAddress cellRangeAddress = new CellRangeAddress(currentRowIndex - 1, currentRowIndex, colIndex, colIndex); sheet.addMergedRegionUnsafe(cellRangeAddress); } } } }
5.2.2.2 处理数据
- 为了观察效果,先给数据进行排序一下:
//按种类排序 Collections.sort(dogList, new Comparator<EasyDogEntity>(){ @Override public int compare(EasyDogEntity o1, EasyDogEntity o2) { return o1.getKind().compareTo(o2.getKind()); } });
5.2.2.3 测试看效果
- 测试代码:
package com.liu.susu.excel.easyexcel.example.export.example3; import com.alibaba.excel.EasyExcel; import com.liu.susu.excel.easyexcel.common.merge.MergerCellStrategy; import com.liu.susu.excel.easyexcel.example.export.data.EasyDogEntity; import com.liu.susu.excel.easyexcel.example.export.data.MakeEasyDogData; import java.util.List; /** * description 测试-自定义单元格合并策略 * * @author susu **/ public class EasyTestExportC2 { public static void main(String[] args) { List<EasyDogEntity> dogList = MakeEasyDogData.getDogs();//小狗数据 String fileName = "C:\\Users\\Administrator\\Desktop\\easyexcel导出\\excel_C2.xlsx"; String sheetName = "模板"; // 使用自定义的单元格合并策略 int[] columnIndex = {2}; MergerCellStrategy mergerCellStrategy = new MergerCellStrategy(columnIndex); EasyExcel.write(fileName, EasyDogEntity.class) .registerWriteHandler(mergerCellStrategy) .sheet(sheetName).doWrite(dogList); } }
- 测试效果:
6. 自定义拦截器
6.1 自定义拦截器->单元格合并策略
- 这个就是我们上面提到过的自定义单元格合并策略来动态实现纵向单元格的合并,忘记的可以往上翻,看上面《5.2.2 动态合并(自定义单元格合并策略)》
6.2 自定义拦截器->数据单元格增加下拉框
6.2.1 只针对单列设置下拉数据
- 核心代码如下:
-
- 自定义拦截器SelectSheetWriteHandler.java
package com.liu.susu.excel.easyexcel.common.handle.select; import com.alibaba.excel.write.handler.SheetWriteHandler; import com.alibaba.excel.write.handler.context.SheetWriteHandlerContext; import lombok.extern.slf4j.Slf4j; import org.apache.poi.ss.usermodel.DataValidation; import org.apache.poi.ss.usermodel.DataValidationConstraint; import org.apache.poi.ss.usermodel.DataValidationHelper; import org.apache.poi.ss.util.CellRangeAddressList; /** * description 自定义拦截器-数据单元格新增下拉框 * * @author susu **/ @Slf4j public class SelectSheetWriteHandler implements SheetWriteHandler { private final int firstRow; private final int lastRow; private final int colIndex; private final String[] selectData; public SelectSheetWriteHandler(int firstRow, int lastRow, int colIndex,String[] selectData) { this.firstRow = firstRow; this.lastRow = lastRow; this.colIndex = colIndex; this.selectData = selectData; } @Override public void afterSheetCreate(SheetWriteHandlerContext context) { CellRangeAddressList cellRangeAddressList = new CellRangeAddressList(firstRow, lastRow, colIndex, colIndex); DataValidationHelper helper = context.getWriteSheetHolder().getSheet().getDataValidationHelper(); DataValidationConstraint constraint = helper.createExplicitListConstraint(selectData); DataValidation dataValidation = helper.createValidation(constraint, cellRangeAddressList); context.getWriteSheetHolder().getSheet().addValidationData(dataValidation); } }
- 自定义拦截器SelectSheetWriteHandler.java
-
- 测试代码
- 测试代码
- 测试效果如下:
6.2.2 支持可多列设置下拉数据
- 在上面的基础上简单优化一下,实现可支持多列下拉:
- 修改后的代码也是很简单的,如下:
package com.liu.susu.excel.easyexcel.common.handle.select; import com.alibaba.excel.write.handler.SheetWriteHandler; import com.alibaba.excel.write.handler.context.SheetWriteHandlerContext; import lombok.extern.slf4j.Slf4j; import org.apache.poi.ss.usermodel.DataValidation; import org.apache.poi.ss.usermodel.DataValidationConstraint; import org.apache.poi.ss.usermodel.DataValidationHelper; import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.ss.util.CellRangeAddressList; import java.util.Map; /** * description 自定义拦截器-数据单元格新增下拉框 * * @author susu **/ @Slf4j public class SelectSheetWriteHandler2 implements SheetWriteHandler { private final int firstRow; private final int lastRow; private final Map<Integer,String[]> selectDataMap; public SelectSheetWriteHandler2(int firstRow, int lastRow, Map<Integer,String[]> selectDataMap) { this.firstRow = firstRow; this.lastRow = lastRow; this.selectDataMap = selectDataMap; } @Override public void afterSheetCreate(SheetWriteHandlerContext context) { if (selectDataMap!=null && !selectDataMap.isEmpty()){ DataValidationHelper helper = context.getWriteSheetHolder().getSheet().getDataValidationHelper(); selectDataMap.forEach((colIndex,selectData)->{ CellRangeAddressList cellRangeAddressList = new CellRangeAddressList(firstRow, lastRow, colIndex, colIndex); DataValidationConstraint constraint = helper.createExplicitListConstraint(selectData); DataValidation dataValidation = helper.createValidation(constraint, cellRangeAddressList); context.getWriteSheetHolder().getSheet().addValidationData(dataValidation); }); } } }
- 测试太简单了,简单给个图看看就行,如下:
6.3 自定义拦截器->设置表头支持筛选
- 效果如下:
- 核心代码如下:
-
- 拦截器代码:
package com.liu.susu.excel.easyexcel.common.handle.filter; import com.alibaba.excel.write.handler.SheetWriteHandler; import com.alibaba.excel.write.handler.context.SheetWriteHandlerContext; import lombok.extern.slf4j.Slf4j; import org.apache.poi.ss.usermodel.*; import org.apache.poi.ss.util.CellRangeAddress; /** * description 自定义拦截器-设置表头筛选的属性 * * @author susu **/ @Slf4j public class AutoFilterSheetWriteHandler implements SheetWriteHandler { private int firstRow = 0; private int lastRow = 0; private boolean headAutoFilterFlag; private int[] filterColumnsIndex; private int firstCol; private int lastCol; public AutoFilterSheetWriteHandler(boolean headAutoFilterFlag, int[] filterColumnsIndex) { this.headAutoFilterFlag = headAutoFilterFlag; this.filterColumnsIndex = filterColumnsIndex; } public AutoFilterSheetWriteHandler(boolean headAutoFilterFlag, int firstCol, int lastCol) { this.headAutoFilterFlag = headAutoFilterFlag; this.firstCol = firstCol; this.lastCol = lastCol; } @Override public void afterSheetCreate(SheetWriteHandlerContext context) { dealAutoFilter(context); } public void dealAutoFilter(SheetWriteHandlerContext context) { if (!headAutoFilterFlag) { return; } Sheet sheet = context.getWriteSheetHolder().getSheet(); //需要设置表头筛选 if (filterColumnsIndex == null) { if (!(firstCol >= 0 && lastCol >= firstCol)) { firstCol = 0; //int headColNum = context.getWriteWorkbookHolder().getHead().size(); int headColNum = 10;//默认给个 这个要么默认 要么不用,不满足直接return 要么用RowWriteHandler lastCol = headColNum - 1; } CellRangeAddress rangeAddress = new CellRangeAddress(firstRow, lastRow, firstCol, lastCol); sheet.setAutoFilter(rangeAddress); } else { //这个不行,不能指定列,先放着吧 for (int columnsIndex : filterColumnsIndex) { CellRangeAddress rangeAddress = new CellRangeAddress(firstRow, lastRow, columnsIndex, columnsIndex); sheet.setAutoFilter(rangeAddress); } } } }
- 拦截器代码:
-
- 测试代码
- 测试代码
7. 表格样式
- 样式先放这吧,后续再说
8. 结束
-
就介绍这么多吧,毕竟大多都能在官网上找到例子,实在没有的也能比葫芦画瓢自己写一个,都是能实现你的需求的,还有关于项目源码可以看上面前言去上篇POI文章里
-
最后,附上官网地址,多去官网走走,少走弯路。
官网地址:https://easyexcel.opensource.alibaba.com/