Java开发笔记之EasyExcel实现自定义合并策略
0x00 概述
本文转载,原文
原本是想学习使用Apache的POI的,但是无意中看到Alibaba的开源工具EastExcel,据说比POI更加快速高效,关键是使用起来也简单。
官网地址为:https://alibaba-easyexcel.github.io/index.html,里面讲解地非常清楚易懂,我这里就不再赘述了,只是记录下写表格时如何通过自定义合并策略来实现动态地合并单元格。
0x01 入门例子
如果我们不合并单元格,那么下载的样式将是如下这样的未合并单元格:
那么我们只需要使用如下例子即可:
@Test public void commonWriteTest() { String fileName = "mergeWrite" + System.currentTimeMillis() + ".xlsx"; EasyExcel.write(fileName, DownloadDTO.class).sheet("sheet名称") .doWrite(getFruitData()); }
// 模拟从数据库读取需要下载的列表信息 private List<DownloadDTO> getFruitData() { List<DownloadDTO> returnList = new ArrayList<>(); DownloadDTO d1 = new DownloadDTO(); d1.setCategory("水果"); d1.setFruit("苹果"); d1.setColor("红色"); d1.setProduceDate(new Date()); DownloadDTO d2 = new DownloadDTO(); BeanUtils.copyProperties(d1, d2); d2.setColor("绿色"); DownloadDTO d3 = new DownloadDTO(); BeanUtils.copyProperties(d1, d3); d2.setColor("白色"); DownloadDTO t1 = new DownloadDTO(); t1.setCategory("水果"); t1.setFruit("香蕉"); t1.setColor("黄色"); t1.setProduceDate(new Date()); DownloadDTO t2 = new DownloadDTO(); BeanUtils.copyProperties(t1, t2); t2.setColor("青色"); returnList.add(d1); returnList.add(d2); returnList.add(d3); returnList.add(t1); returnList.add(t2); return returnList; }
@Data public class DownloadDTO { @ExcelProperty(value = "物品种类", index = 0) private String category; @ExcelProperty(value = "水果名称", index = 1) private String fruit; @ExcelProperty(value = "水果颜色", index = 2) private String color; @ExcelProperty(value = "水果产期", index = 3) private Date produceDate; }
其中,方法getFruitData
和DownloadDTO
在后面的例子中还会用到,就不再写出。
如果我们想要合并单元格,那么官方文档上的例子是这样的loopMerge:
与之对应的代码如下:
@Test public void loopMergeStrategyTest() { String fileName = "mergeWrite" + System.currentTimeMillis() + ".xlsx"; // 将第一列的数据每隔两行进行合并 LoopMergeStrategy loopMergeStrategy = new LoopMergeStrategy(2, 0); EasyExcel.write(fileName, DownloadDTO.class).registerWriteHandler(loopMergeStrategy) .sheet("sheet名称").doWrite(getFruitData()); }
其中LoopMergeStrategy的源码其实也很简单,底层还是用到了apache poi的API:
protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) { if (relativeRowIndex != null) { Integer currentColumnIndex; if (head != null) { currentColumnIndex = head.getColumnIndex(); } else { currentColumnIndex = cell.getColumnIndex(); } // 将入参中指定的列this.columnIndex按照每this.eachRow行进行合并,this.eachRow也是入参 if (currentColumnIndex == this.columnIndex && relativeRowIndex % this.eachRow == 0) { CellRangeAddress cellRangeAddress = new CellRangeAddress(cell.getRowIndex(), cell.getRowIndex() + this.eachRow - 1, cell.getColumnIndex(), cell.getColumnIndex() + this.columnCount - 1); sheet.addMergedRegionUnsafe(cellRangeAddress); } } }
然后,在github的issue中,有这样一个使用OnceAbsoluteMergeStrategy
的例子onceAbsoluteMerge:
在这个例子中,我们可以指定坐标范围,对固定区间进行合并,其对应的代码如下:
@Test public void onceMergeStrategyTest() { String fileName = "mergeWrite" + System.currentTimeMillis() + ".xlsx"; OnceAbsoluteMergeStrategy onceAbsoluteMergeStrategy = new OnceAbsoluteMergeStrategy(1, 3, 1, 1); EasyExcel.write(fileName, DownloadDTO.class).registerWriteHandler(onceAbsoluteMergeStrategy) .sheet("sheet名称").doWrite(getFruitData()); }
当然了,我们可以一次使用多个合并策略,比如我们想实现这样的效果onceAbsoluteMerge2:
用OnceAbsoluteMergeStrategy
可以这么做:
@Test public void onceMergeStrategyTest() { String fileName = "mergeWrite" + System.currentTimeMillis() + ".xlsx"; OnceAbsoluteMergeStrategy onceAbsoluteMergeStrategy = new OnceAbsoluteMergeStrategy(1, 3, 1, 1); OnceAbsoluteMergeStrategy onceAbsoluteMergeStrategy2 = new OnceAbsoluteMergeStrategy(4, 5, 1, 1); EasyExcel.write(fileName, DownloadDTO.class) .registerWriteHandler(onceAbsoluteMergeStrategy) .registerWriteHandler(onceAbsoluteMergeStrategy2) .sheet("sheet名称").doWrite(getFruitData()); }
0x02 自定义实现合并策略
当我们需要在写Excel的时候实现更加复杂乃至动态地合并单元格时,就需要自己实现一个合并策略。
比如当我们想实现这样的效果时myMerge:
在这个例子中,对于第1、4列,我们需要根据记录总数,都合并成一个单元格;
对于第2列,我们需要根据每组的记录个数,分别进行单元格的合并;而对于第3列,则不要使用合并策略。
与之对应的代码是:
@Test public void myMergeStrategyTest() { String fileName = "mergeWrite" + System.currentTimeMillis() + ".xlsx"; MyMergeStrategy myMergeStrategy = new MyMergeStrategy(getFruitData(), getGroupData()); EasyExcel.write(fileName, DownloadDTO.class).registerWriteHandler(myMergeStrategy) .sheet("sheet名称").doWrite(getFruitData()); }
public class MyMergeStrategy extends AbstractMergeStrategy { private List<DownloadDTO> fruitList; private List<Integer> fruitGroupCount; private Sheet sheet; public MyMergeStrategy(List<DownloadDTO> fruitList, List<Integer> fruitGroupCount) { this.fruitList = fruitList; this.fruitGroupCount = fruitGroupCount; } // 将该列全部合并成一个单元格 private void mergeCommonColumn(Integer index) { CellRangeAddress cellRangeAddress = new CellRangeAddress(1, fruitList.size(), index, index); sheet.addMergedRegionUnsafe(cellRangeAddress); } // 按照分组将各种类别分别合并成一个单元格 private void mergeGroupColumn(Integer index) { Integer rowCnt = 1; for (Integer count : fruitGroupCount) { CellRangeAddress cellRangeAddress = new CellRangeAddress(rowCnt, rowCnt + count - 1, index, index); sheet.addMergedRegionUnsafe(cellRangeAddress); rowCnt += count; } } @Override protected void merge(org.apache.poi.ss.usermodel.Sheet sheet, Cell cell, Head head, Integer integer) { this.sheet = sheet; if (cell.getRowIndex() == 1) { switch (cell.getColumnIndex()) { case 0: this.mergeCommonColumn(0); break; case 1: this.mergeGroupColumn(1); break; case 2: break; case 3: this.mergeCommonColumn(3); break; default: break; } } } }
为什么不使用多个OnceAbsouluteMergeStrategy
?
因为我们在写代码的时候并不知道每个分组的数量究竟有多少个,所以只能自己写合适的合并策略了。