就两个字,“好用”,分享给大家伙,不管你是新手,还是老鸟,希望对你们有帮助,我个人认为是极好的,阿里出品,必属精品,不吹不黑,个人get到了人家强大的地方了。
简单介绍一下EasyExcel是什么,EasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目。在尽可能节约内存的情况下支持读写百M的Excel,对官方文档就是这么阐述的,很简介明了,就是帮助我们干活的,他是基于poi的二次封装,我们只管处理业务,其他的交给EasyExcel,我只是研究了皮毛,他本身有好多api供大家调用,实现复杂组合表头,大数据导入导出,计算函数,多sheet,等等,从我这里入门,然后去扒人家更深的东西
先说导出吧
这是我个人封装的工具类,直接拿去使用,学会了回来留下你的大名就可以了
/**
* guoyunlong
* <p>
* 版本2.1.7 工具类可自定义样式格式等
* 后续会上传下载功能,大批量导出导出需要使用多线程,和多sheet完成
**/
@Component
public class ExcelUtil {
/**
* 导出 Excel :一个 sheet,带表头.
*
* @param response HttpServletResponse
* @param data 数据 list,每个元素为一个 BaseRowModel
* @param fileName 导出的文件名
* @param sheetName 导入文件的 sheet 名
* @param model 映射实体类,Excel 模型
* @throws Exception 异常
*/
public void writeExcel(HttpServletResponse response, List<? extends Object> data, String fileName, String sheetName,
Class model) {
try {
// 头的策略
WriteCellStyle headWriteCellStyle = new WriteCellStyle();
//设置表头居中对齐
headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
// 颜色
headWriteCellStyle.setFillForegroundColor(IndexedColors.PALE_BLUE.getIndex());
WriteFont headWriteFont = new WriteFont();
headWriteFont.setFontHeightInPoints((short) 10);
// 字体
headWriteCellStyle.setWriteFont(headWriteFont);
headWriteCellStyle.setWrapped(true);
// 内容的策略
WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
//设置内容靠中对齐
contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
// 这个策略是 头是头的样式 内容是内容的样式 其他的策略可以自己实现
HorizontalCellStyleStrategy horizontalCellStyleStrategy =
new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
EasyExcel.write(getOutputStream(fileName, response), model).excelType(ExcelTypeEnum.XLSX).sheet(sheetName)
.registerWriteHandler(horizontalCellStyleStrategy)
//最大长度自适应 目前没有对应算法优化 建议注释掉不用 会出bug
// .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
.doWrite(data);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 导出文件时为Writer生成OutputStream.
*
* @param fileName 文件名
* @param response response
* @return ""
*/
private OutputStream getOutputStream(String fileName, HttpServletResponse response) throws Exception {
try {
fileName = URLEncoder.encode(fileName, "UTF-8");
response.setContentType("application/json");
response.setCharacterEncoding("utf8");
response.setHeader("Content-Disposition", "attachment; filename=" + fileName + ".xlsx");
response.setHeader("Pragma", "public");
response.setHeader("Cache-Control", "no-store");
response.addHeader("Cache-Control", "max-age=0");
return response.getOutputStream();
} catch (IOException e) {
throw new Exception("导出excel表格失败!", e);
}
}
}
这是基于实体dto映射实现的
/**
* @HeadRowHeight() 设置表头高度。
* @ColumnWidth()设置列宽度。
* @ExcelProperty(value = "",index = )对应字段名称和位置。
*/
@Data
@HeadRowHeight(30)
public class ParkElectricityMeterDto {
@ColumnWidth(25)
@ExcelProperty(value = "监测点", index = 0)
private String name;
@ColumnWidth(25)
@ExcelProperty(value = "当前电量时间", index = 1)
private String time;
@ColumnWidth(25)
@ExcelProperty(value = "有功电能(kWh)", index = 2)
private String epI;
@ColumnWidth(25)
@ExcelProperty(value = "数据采集时间", index = 3)
private String time1;
@ColumnWidth(25)
@ExcelProperty(value = "电压(V)", index = 4)
private String volP;
@ColumnWidth(25)
@ExcelProperty(value = "电流(V)", index = 5)
private String cur;
@ColumnWidth(25)
@ExcelProperty(value = "有功功率(KW)", index = 6)
private String insP;
@ColumnWidth(25)
@ExcelProperty(value = "无功功率(KVar)", index = 7)
private String insQ;
@ColumnWidth(25)
@ExcelProperty(value = "功率因数", index = 8)
private String pwrF;
private static final long serialVersionUID = 1L;
}
按照对应的@ExcelProperty注解名字导出的表格也就是对应的名称
下面是我的调用方法
/**
* 导出
*
* @param resp
* @param parkId
* @param ids
* @param startTime
* @param endTime
* @return
*/
@Override
public List<ParkElectricityDto> excels(HttpServletResponse resp, Long parkId, String ids, String startTime,
String endTime) {
List<Map<String, Object>> mapList =
internetOfThingsApi.getApiCopying(ids, startTime, endTime);
List<ParkElectricityDto> arrayList = new ArrayList<>();
mapList.forEach(item -> {
ParkElectricityDto bo = new ParkElectricityDto();
bo.setName(item.get("name").toString());
bo.setStartTime(item.get("first").toString());
bo.setEndTime(item.get("last").toString());
bo.setDataValue(item.get("last_first").toString());
arrayList.add(bo);
});
String strName = "excel表名";
String sheetName = "sheet";
// 调用导出工具类实现导出
excelUtil.writeExcel(resp, arrayList, strName, sheetName, ParkElectricityDto.class);
return arrayList;
}
到这来我们的最简单的导出也就完成了,基本可以满足百分之80以上的项目组使用了,如果你们有合并表头这种需求,简单的也可以在实体类用包含关系实现,在复杂了就去研究一下官方文档了
导入
其实也很简单,他是实现了一个监听器,这个监听器是不会被spring管理的,很奇怪,我可以改进他这个机制,目前还没有去做,监听器重写了两个方法第一个:invoke()方法,他是用来实现导入数据的第一层解析,对你的集合进行判断,当达到你设定的查询条数的时候他就会进行一次集合内存清理,这样防止几万条数据存在内存,容易OOM,第二个方法:doAfterAllAnalysed(),这个方法是用来做数据处理完成最终确认的地方,保存数据的作用,废话不多说,直接上干货。
/*
* Copyright (C) 2020 Baidu, Inc. All Rights Reserved.
*/
package com.company.project.configurer;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.fastjson.JSON;
import com.company.project.domain.User;
import com.company.project.service.UserService;
import lombok.extern.slf4j.Slf4j;
/**
* 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,
* 然后里面用到spring可以构造方法传进去
*/
@Slf4j
public class DemoDataListener extends AnalysisEventListener<User> {
private static final Logger LOG = LoggerFactory.getLogger(DemoDataListener.class);
/**
* 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 3000;
List<User> list = new ArrayList<User>();
private UserService userService;
/**
* 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
*
* @param userService
*/
public DemoDataListener(UserService userService) {
this.userService = userService;
}
@Override
public void invoke(User parkElectricityDto, AnalysisContext analysisContext) {
LOG.info("解析到一条数据:{}", JSON.toJSONString(parkElectricityDto));
list.add(parkElectricityDto);
// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (list.size() >= BATCH_COUNT) {
saveData();
// 存储完成清理 list
list.clear();
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
// 这里也要保存数据,确保最后遗留的数据也存储到数据库
long startTime = System.currentTimeMillis();
saveData();
long endTime = System.currentTimeMillis();
LOG.info("所有数据解析完成!" + (endTime - startTime) + "ms");
}
/**
* 加上存储数据库
*/
private void saveData() {
LOG.info("开始存储数据库!", list.size());
userService.saveData(list);
LOG.info("存储数据库成功!");
}
}
仔细阅读上面代码,理解每一层代码的含义,不难,很简单,但是实现复杂数据导入还是在这个基础上去做修改,所以基础不好,房子在高也是样子货,抵不住暴风雨的侵蚀
调用
/**
* 导入
*
* @param file
* @return
* @throws IOException
*/
@ApiOperation("导入")
@RequestMapping(value = "/importBasic", method = RequestMethod.POST)
public Result importBasic(MultipartFile file) throws IOException {
// DemoDataListener dataListener = new DemoDataListener(userService);
// EasyExcel.read(file.getInputStream(), User.class, dataListener).sheet().doRead();
ExcelReader excelReader = null;
try {
excelReader = EasyExcel.read(file.getInputStream(), User.class, new DemoDataListener(userService)).build();
ReadSheet readSheet = EasyExcel.readSheet(0).build();
excelReader.read(readSheet);
} finally {
if (excelReader != null) {
// 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的
excelReader.finish();
}
}
return ResultGenerator.genSuccessResult();
}
以上调用实现两种方法,基本一样,我亲自测试了,导入2万条数据,我用时2.4秒,不能说快,基本上达到了很多项目组最高要求了
service业务实现,官方文档给出了一直解释,要想导入快,就必须实现一次insert,也就是说你要在mapping中自己写sql,也有道理的,不过这种也是有弊端的,常年写sql的同学应该知道
@Override
public Boolean saveData(List<User> user) {
User user1 = new User();
//业务逻辑
// for (User user2 : user) {
// user1.setUsername(user2.getUsername());
// user1.setPassword(user2.getPassword());
// user1.setNickName(user2.getNickName());
// user1.setSex(user2.getSex());
// user1.setRegisterDate(user2.getRegisterDate());
//将解析数据存储数据库
// userMapper.insertSelective(user1);
// }
userMapper.plInsert(user);
return true;
}
完结撒花,啦啦啦,今天北京下雪了,终于可以睡个懒觉了,希望大家学会了给我留个言,点个赞,虽然很简单的东西,但是实际开发中作用很大,成为架构师你也得走这条路