官网:https://easyexcel.opensource.alibaba.com/
EasyExcel是一个基于Java的、快速、简洁、解决大文件内存溢出的Excel处理工具。
他能让你在不用考虑性能、内存的等因素的情况下,快速完成Excel的读、写等功能。
优点
gitee地址:https://gitee.com/easyexcel/easyexcel
坐标依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.3.2</version>
</dependency>
读Excel
官方参考:https://easyexcel.opensource.alibaba.com/docs/current/quickstart/read
PS:@Data相当于@Getter @Setter @RequiredArgsConstructor @ToString @EqualsAndHashCode这5个注解的合集
最简单的读的对象
// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
@Slf4j
public class DemoDataListener implements ReadListener {
/**
* 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 100;
/**
* 缓存的数据
*/
private List<DemoData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
/**
* 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
*/
private DemoDAO demoDAO;
public DemoDataListener() {
// 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数
demoDAO = new DemoDAO();
}
/**
* 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
*
* @param demoDAO
*/
public DemoDataListener(DemoDAO demoDAO) {
this.demoDAO = demoDAO;
}
/**
* 这个每一条数据解析都会来调用
*
* @param data one row value. Is is same as {@link AnalysisContext#readRowHolder()}
* @param context
*/
@Override
public void invoke(DemoData data, AnalysisContext context) {
log.info("解析到一条数据:{}", JSON.toJSONString(data));
cachedDataList.add(data);
// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (cachedDataList.size() >= BATCH_COUNT) {
saveData();
// 存储完成清理 list
cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
/**
* 所有数据解析完成了 都会来调用
*
* @param context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 这里也要保存数据,确保最后遗留的数据也存储到数据库
saveData();
log.info("所有数据解析完成!");
}
/**
* 加上存储数据库
*/
private void saveData() {
log.info("{}条数据,开始存储数据库!", cachedDataList.size());
demoDAO.save(cachedDataList);
log.info("存储数据库成功!");
}
}
写Excel
https://easyexcel.opensource.alibaba.com/docs/current/quickstart/write
最简单的写的对象
最简单的读的监听器
@Getter
@Setter
@EqualsAndHashCode
public class DemoData {
@ExcelProperty("字符串标题")
private String string;
@ExcelProperty("日期标题")
private Date date;
@ExcelProperty("数字标题")
private Double doubleData;
/**
* 忽略这个字段
*/
@ExcelIgnore
private String ignore;
}
/**
* 最简单的写
* <p>
* 1. 创建excel对应的实体对象 参照{@link DemoData}
* <p>
* 2. 直接写即可
*/
@Test
public void simpleWrite() {
// 注意 simpleWrite在数据量不大的情况下可以使用(5000以内,具体也要看实际情况),数据量大参照 重复多次写入
// 写法1 JDK8+
// since: 3.0.0-beta1
String fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
// 如果这里想使用03 则 传入excelType参数即可
EasyExcel.write(fileName, DemoData.class)
.sheet("模板")
.doWrite(() -> {
// 分页查询数据
return data();
});
// 写法2
fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
// 如果这里想使用03 则 传入excelType参数即可
EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());
// 写法3
fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去写
try (ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build()) {
WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
excelWriter.write(data(), writeSheet);
}
}
填充Excel
https://easyexcel.opensource.alibaba.com/docs/current/quickstart/fill
简单填充(对象)
参考官网:
/**
* 填充列表
*
* @since 2.1.1
*/
@Test
public void listFill() {
// 模板注意 用{} 来表示你要用的变量 如果本来就有"{“,”}" 特殊字符 用"{“,”}"代替
// 填充list 的时候还要注意 模板中{.} 多了个点 表示list
// 如果填充list的对象是map,必须包涵所有list的key,哪怕数据为null,必须使用map.put(key,null)
String templateFileName =
TestFileUtil.getPath() + “demo” + File.separator + “fill” + File.separator + “list.xlsx”;
// 方案1 一下子全部放到内存里面 并填充
String fileName = TestFileUtil.getPath() + "listFill" + System.currentTimeMillis() + ".xlsx";
// 这里 会填充到第一个sheet, 然后文件流会自动关闭
EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(data());
// 方案2 分多次 填充 会使用文件缓存(省内存)
fileName = TestFileUtil.getPath() + "listFill" + System.currentTimeMillis() + ".xlsx";
try (ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build()) {
WriteSheet writeSheet = EasyExcel.writerSheet().build();
excelWriter.fill(data(), writeSheet);
excelWriter.fill(data(), writeSheet);
}
}
复杂填充(对象和列表)
@GetMapping("/export")
public void export(HttpServletResponse response) throws IOException {
Map<String,Object> map = this.reportService.export();
BusinessDataVO vo = (BusinessDataVO) map.get("vo");
List<BusinessDataVO> voList = (List<BusinessDataVO>) map.get("list");
File templateFile = new ClassPathResource("运营数据报表模板.xlsx").getFile();
ServletOutputStream responseOutputStream = response.getOutputStream();
// 以字节流的形式写回前端
try (ExcelWriter excelWriter = EasyExcel.write(responseOutputStream).withTemplate(templateFile).build()) {
WriteSheet writeSheet = EasyExcel.writerSheet().build();
// 这里注意 入参用了forceNewRow 代表在写入list的时候不管list下面有没有空行 都会创建一行,然后下面的数据往后移动。默认 是false,会直接使用下一行,如果没有则创建。
// forceNewRow 如果设置了true,有个缺点 就是他会把所有的数据都放到内存了,所以慎用
// 简单的说 如果你的模板有list,且list不是最后一行,下面还有数据需要填充 就必须设置 forceNewRow=true 但是这个就会把所有数据放到内存 会很耗内存
// 如果数据量大 list不是最后一行 参照下一个
// 新生成一行,然后总数往下推
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
// 复杂填充 填充对象
excelWriter.fill(vo,fillConfig, writeSheet);
//填充多条数据
excelWriter.fill(voList, fillConfig, writeSheet);
// excelWriter.fill(map,fillConfig, writeSheet);
}
}
@Override
public Map<String, Object> export() {
LocalDate beginTime = LocalDate.now().minusDays(30);
LocalDate endTime = LocalDate.now().minusDays(1);
BigDecimal turnover = new BigDecimal("0");
Integer validOrderCount = 0;
Integer newUsers = 0;
Integer total = 0;
Double unitPrice = 0D;
Double orderCompletionRate = 0D;
List<BusinessDataVO> voList = new ArrayList<>();
for (int i = 0; i < 30; i++) {
LocalDate date = beginTime.plusDays(i);
BusinessDataVO businessDataVO = workSpaceService.businessData(date);
voList.add(businessDataVO);
businessDataVO.setDate(date);
turnover = turnover.add(BigDecimal.valueOf(businessDataVO.getTurnover()));
validOrderCount += businessDataVO.getValidOrderCount();
newUsers += businessDataVO.getNewUsers();
total += businessDataVO.getTotal();
}
if (total>0) {
orderCompletionRate = BigDecimal.valueOf(validOrderCount)
.divide(BigDecimal.valueOf(total.doubleValue()),2,RoundingMode.HALF_UP).doubleValue();
}
if (validOrderCount > 0) {
unitPrice = turnover.divide(BigDecimal.valueOf(validOrderCount),2,RoundingMode.HALF_UP).doubleValue();
}
BusinessDataVO vo = BusinessDataVO.builder()
.unitPrice(unitPrice)
.orderCompletionRate(orderCompletionRate)
.turnover(turnover.doubleValue())
.newUsers(newUsers)
.beginTime(beginTime)
.endTime(endTime)
.validOrderCount(validOrderCount)
.total(voList.size())
.build();
Map<String, Object> map = new HashMap<>();
map.put("vo",vo);
map.put("list",voList);
return map;
}