22k+
Star,可见其非常流行常用注解:
依赖:等下demo有用到lombok和fastjson,所以这里我加上了
<dependency>
<groupId>com.alibabagroupId>
<artifactId>easyexcelartifactId>
<version>3.0.5version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.80version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.2version>
<scope>compilescope>
dependency>
实体类,转换类
public class SysExcelDemo implements Serializable {
private static final long serialVersionUID = 1L;
@ExcelIgnore
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
@ExcelProperty(value = "用户名")
private String userName;
@ExcelProperty(value = "手机号")
private String mobile;
@ExcelProperty(value = "性别", converter = SexExcelConverter.class)
private Integer sex;
}
public class SexExcelConverter implements Converter<Integer> {
@Override
public Integer convertToJavaData(ReadConverterContext<?> context) throws Exception {
//获取excel性别的值
String value = context.getReadCellData().getStringValue();
if ("男".equals(value)) {
return 1;
} else {
return 0;
}
}
}
监听器类,这个是重点
//有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
@Slf4j
public class DemoDataListener implements ReadListener<SysExcelDemo> {
//每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
private static final int BATCH_COUNT = 100;
//缓存的数据
private List<SysExcelDemo> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
//Service层
private ISysExcelDemoService demoService;
public DemoDataListener(ISysExcelDemoService demoService){
this.demoService = demoService;
}
private static final String[] headers = {"用户名", "手机号", "性别"};
//验证模板
@Override
public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
if (context.readRowHolder().getRowIndex() == 0) {
for (int i = 0; i < headers.length; i++) {
if(!headers[i].equals(headMap.get(i).getStringValue())){
throw new ExcelAnalysisException("请导入正确的模板");
}
}
}
}
//这个每一条数据解析都会来调用,
//这里也可以使用不创建对象的读invoke(Map data, AnalysisContext context),当我们这里验证数据之后再封装进我们的实体
@Override
public void invoke(SysExcelDemo data, AnalysisContext analysisContext) {
log.info("解析到一条数据:{}", JSON.toJSONString(data));
cachedDataList.add(data);
// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (cachedDataList.size() >= BATCH_COUNT) {
saveData();
// 存储完成清理 list
cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
//在转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。
@Override
public void onException(Exception exception, AnalysisContext context) {
//数据转换异常
if (exception instanceof ExcelDataConvertException) {
ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception;
log.error("第{}行,第{}列解析异常,数据为:{}", excelDataConvertException.getRowIndex(),
excelDataConvertException.getColumnIndex(), excelDataConvertException.getCellData());
}
//解析异常
if(exception instanceof ExcelAnalysisException){
ExcelAnalysisException excelAnalysisException = (ExcelAnalysisException) exception;
String message = excelAnalysisException.getMessage();
log.error(message);
//抛出异常,停止解析
throw new ExcelAnalysisStopException(message);
}
}
//存储数据库
private void saveData() {
log.info("{}条数据,开始存储数据库!", cachedDataList.size());
demoService.saveBatch(cachedDataList);
log.info("存储数据库成功!");
}
//所有数据解析完成了 都会来调用
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
// 这里也要保存数据,确保最后遗留的数据也存储到数据库
saveData();
log.info("所有数据解析完成!");
}
}
controller
@Controller
@RequestMapping("/sysExcelDemo")
public class SysExcelDemoController {
@Autowired
private ISysExcelDemoService demoService;
@PostMapping("/excelRead")
@ResponseBody
public AjaxResult read(@RequestParam(name = "file", required = true) MultipartFile file) throws IOException {
EasyExcel.read(file.getInputStream(), SysExcelDemo.class, new DemoDataListener(demoService)).sheet().doRead();
return AjaxResult.suc();
}
}
测试,导入我们准备的xlsx
下面直接使用官方的demo,就是创建了多个监听器读取不同的sheet
/**
* 读多个或者全部sheet,这里注意一个sheet不能读取多次,多次读取需要重新读取文件
*
* 1. 创建excel对应的实体对象 参照{@link DemoData}
*
* 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener}
*
* 3. 直接读即可
*/
@Test
public void repeatedRead() {
String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
// 读取全部sheet,这里其实就是同一个监听器去接收全部Sheet
// 这里需要注意 DemoDataListener的doAfterAllAnalysed 会在每个sheet读取完毕后调用一次。然后所有sheet都会往同一个DemoDataListener里面写
EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).doReadAll();
// 读取部分sheet
fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
ExcelReader excelReader = null;
try {
excelReader = EasyExcel.read(fileName).build();
// 这里为了简单 所以注册了 同样的head 和Listener 自己使用功能必须不同的Listener
ReadSheet readSheet1 =
EasyExcel.readSheet(0).head(DemoData.class).registerReadListener(new DemoDataListener()).build();
ReadSheet readSheet2 =
EasyExcel.readSheet(1).head(DemoData.class).registerReadListener(new DemoDataListener()).build();
// 这里注意 一定要把sheet1 sheet2 一起传进去,不然有个问题就是03版的excel 会读取多次,浪费性能
excelReader.read(readSheet1, readSheet2);
} finally {
if (excelReader != null) {
// 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的
excelReader.finish();
}
}
}
我们现在刚才导入数据库的数据导出到excel中,还原一下
还是刚才的实体
@TableName("sys_excel_demo")
@ApiModel(value = "SysExcelDemo对象", description = "")
public class SysExcelDemo implements Serializable {
private static final long serialVersionUID = 1L;
@ExcelIgnore
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
@ExcelProperty(value = "用户名")
private String userName;
@ColumnWidth(50)
@ExcelProperty(value = "手机号")
private String mobile;
@ExcelProperty(value = "性别", converter = SexExcelConverter.class)
private Integer sex;
}
public class SexExcelConverter implements Converter<Integer> {
@Override
public Integer convertToJavaData(ReadConverterContext<?> context) throws Exception {
//获取excel性别的值
String value = context.getReadCellData().getStringValue();
if ("男".equals(value)) {
return 1;
} else {
return 0;
}
}
@Override
public WriteCellData<?> convertToExcelData(WriteConverterContext<Integer> context) throws Exception {
Integer sex = context.getValue();
if(sex == 1){
return new WriteCellData<>("男");
}else {
return new WriteCellData<>("女");
}
}
}
controller
@Controller
@RequestMapping("/sysExcelDemo")
public class SysExcelDemoController {
@Autowired
private ISysExcelDemoService demoService;
@GetMapping("download")
public void download(HttpServletResponse response) throws IOException {
// 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
EasyExcel.write(response.getOutputStream(), SysExcelDemo.class).sheet("Sheet1").doWrite(demoService.list());
}
}
准备完成,开始测试,直接调用download接口,可以看到,跟刚才导入的数据一致
官方除了读写excel,还有填充excel,可以去官方文档看看,但是很多细节,还有出现的bug,看官方文档是远远不够的,需要自己去实践一下。其实还有挺多坑的