Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。
easyexcel重写了poi对07版Excel的解析,一个3M的excel用POI sax解析依然需要100M左右内存,改用easyexcel可以降低到几M,并且再大的excel也不会出现内存溢出;03版依赖POI的sax模式,在上层做了模型转换的封装,让使用者更加简单方便。
引入依赖
<dependency>
<groupId>com.alibabagroupId>
<artifactId>easyexcelartifactId>
<version>3.1.1version>
dependency>
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class TempData {
private Long id;
/**
* 用户名
*/
private String userName;
/**
* 生日
*/
private Date birthday;
/**
* 性别
*/
private String sex;
/**
* 地址
*/
private String address;
}
// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
package com.geekmice.springbootselfexercise;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.fastjson.JSON;
import com.geekmice.springbootselfexercise.dao.UserDao;
import com.geekmice.springbootselfexercise.domain.TempData;
import com.geekmice.springbootselfexercise.service.UserService;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
/**
* @BelongsProject: spring-boot-self-exercise
* @BelongsPackage: com.geekmice.springbootselfexercise
* @Author: pingmingbo
* @CreateTime: 2023-08-07 10:19
* @Description: easyexcel读操作监听器
* @Version: 1.0
*/
@Slf4j
public class UserListener implements ReadListener<TempData> {
/**
* 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 100;
/**
* 缓存的数据
*/
private List<TempData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
/**
* 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
*/
private UserService userService;
/**
* 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
*
* @param demoDAO
*/
public UserListener(UserService userService) {
this.userService = userService;
}
/**
* 这个每一条数据解析都会来调用
*
* @param data one row value. Is is same as {@link AnalysisContext#readRowHolder()}
* @param context
*/
@Override
public void invoke(TempData 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());
// this.s(cachedDataList);
// userService.saveBatch(cachedDataList);
log.info("存储数据库成功!");
}
}
//userService业务service,也可以是dao,如果是多个mapper或者service,通过构造方法或者set方法传递
也可以使用工具类getBean获取,不需要通过构造方法,这种方式主要是多个mapper或者service进行业务操作
@Override
public void uploadFileByEasyExcel(MultipartFile file) {
try {
EasyExcel.read(file.getInputStream(), TempData.class, new UserListener(userService)).sheet().doRead();
} catch (IOException e) {
log.error("error msg 【{}】", e);
throw new IllegalArgumentException(e);
}
}
名称 | 默认值 | 备注 |
---|---|---|
value | 空 | 用于匹配excel中的头,必须全匹配,如果有多行头,会匹配最后一行头 |
order | Integer.MAX_VALUE | 优先级高于value ,会根据order 的顺序来匹配实体和excel中数据的顺序 |
index | -1 | 优先级高于value 和order ,会根据index 直接指定到excel中具体的哪一列 |
converter | 自动选择 | 指定当前字段用什么转换器,默认会自动选择。读的情况下只要实现com.alibaba.excel.converters.Converter#convertToJavaData(com.alibaba.excel.converters.ReadConverterContext>) 方法即可 |
ExcelIgnore
默认所有字段都会和excel去匹配,加了这个注解会忽略该字段
ExcelIgnore
默认所有字段都会和excel去匹配,加了这个注解会忽略该字段
package com.geekmice.springbootselfexercise.domain;
import cn.afterturn.easypoi.excel.annotation.Excel;
import cn.afterturn.easypoi.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
/**
* @BelongsProject: spring-boot-self-exercise
* @BelongsPackage: com.geekmice.springbootselfexercise.domain
* @Author: pingmingbo
* @CreateTime: 2023-08-07 09:53
* @Description: TODO
* @Version: 1.0
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class TempData {
/**
* 忽略这个字段
*/
@ExcelIgnore
private Long id;
/**
* 用户名
*/
@ExcelProperty(value = "用户名")
private String userName;
/**
* 生日
*/
@ExcelProperty(value ="生日",format = "yyyy-MM-dd")
private Date birthday;
/**
* 性别
*/
@ExcelProperty(value ="性别")
private String sex;
/**
* 地址
*/
@ExcelProperty(value = "地址")
private String address;
}
@Override
public void downloadFileByEasyExcel() {
EasyExcel.write("D:\\easyexcel.xls", TempData.class)
.sheet()
.doWrite(data());
}
/**
* 根据参数指定列导出
*/
@Test
public void t2(){
List<String> list = new ArrayList(16);
list.add("sex");
list.add("userName");
EasyExcel.write("D://easyexcel_by_columns.xlsx", TempData.class)
.excludeColumnFieldNames(null)
.sheet()
.includeColumnFieldNames(list)
.doWrite(data());
log.info("导出结束 [{}]", DateFormatUtils.format(new Date(), DateUtils.DATE_FORMAT_19));
}
使用index属性,index=2空余出来,这样一来第二列为空
/**
* 用户名
*/
@ExcelProperty(value = {"父级","用户名"},index = 0)
private String userName;
/**
* 生日
*/
@ExcelProperty(value ={"父级","生日"},format = "yyyy-MM-dd",index = 1)
private Date birthday;
/**
* 性别
*/
@ExcelProperty(value ={"父级","性别"},index = 2)
private String sex;
/**
List list = new ArrayList(16);
list.add("sex");
list.add("userName");
list.add("birthday");
EasyExcel.write("D://easyexcel_by_columns.xlsx", TempData.class)
.excludeColumnFieldNames(null)
.sheet()
.includeColumnFieldNames(list)
.doWrite(data());
log.info("导出结束 [{}]", DateFormatUtils.format(new Date(), DateUtils.DATE_FORMAT_19));
package com.geekmice.springbootselfexercise.domain;
import cn.afterturn.easypoi.excel.annotation.Excel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import com.alibaba.excel.annotation.format.NumberFormat;
import com.geekmice.springbootselfexercise.utils.CustomStringStringConverter;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
/**
* @BelongsProject: spring-boot-self-exercise
* @BelongsPackage: com.geekmice.springbootselfexercise.domain
* @Author: pingmingbo
* @CreateTime: 2023-08-07 14:04
* @Description: TODO
* @Version: 1.0
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ConverterData {
/**
* 生日
*/
@ExcelProperty(value = "生日")
private String birthday;
/**
* 性别
*/
@ExcelProperty(value = "性别")
private String sex;
/**
* 分数
*/
@ExcelProperty(value = "分数")
private String score;
}