目录
4.实体类:复杂表头的情况,给@ExcelProperty的value赋值时,需要具备所有行的表头;使用注解@ExcelIgnore忽略导出的字段是否包含
EasyExcel特点
Java领域解析,生成Excel比较有名的框架有Apache poi,jxl等,但他们都存在一个严重的问题就是非常的耗内存,如果你的系统并发量不大的话可能还行,但是一旦并发上来后一定会OOM或者JVM频繁的full gc.
EasyExcel是阿里巴巴开源的一个excel处理框架,以使用简单,节省内存著称,EasyExcel能大大减少占用内存的主要原因是在解析Excel时没有将文件数据一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析。
EasyExcel采用一行一行的解析模式,并将一行的解析结果以观察者的模式通知处理(AnalysisEventListener)
如果在项目中已经使用了POI,再引入easyexcel可能会存在冲突,要注意对应的版本
<!-- https://mvnrepository.com/artifact/org.apache.poi/poi -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.17</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.17</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>3.17</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.1.1</version>
</dependency>
一.导入excel案例
需求:校验导入excel表头是否正确,excel行转成实体对象出错时,获取错误信息
1.前端:使用bootstrap的fileinput控件,设置上传的链接及上传结束后的提示信息
$("#file").fileinput({
language : 'zh',
uploadUrl :basePath+"roadData/uploadExcelFileNew",
maxFileCount : 10,//表示允许同时上传的最大文件个数
showCaption : true,//是否显示标题
browseClass : "btn btn-primary", //按钮样式
showPreview:true,
allowedFileExtensions: ["xls", "xlsx"],
uploadAsync:true,
msgFilesTooLess:'您必须至少选择{n}个文件才能上传,请点击“浏览”选择您需要上传的文件!',
msgFilesTooMany:'超过了单次上传最大文件数{m}个,请分批次上传!',
previewFileIcon: '<i class="fa fa-file"></i>',
browseLabel : "浏览",
msgSelected:'添加了{n}个文件',
dropZoneTitle:'拖拽文件到这里 …<br/>最多支持10个文件上传<br/>',
uploadExtraData:function (previewId, index) {
var data = {
"isClearFlag": mini.get("isClearFlag").getValue()
};
return data;
},
previewFileIconSettings: {
'doc': '<i class="fa fa-file-word-o text-primary"></i>',
'xls': '<i class="fa fa-file-excel-o text-success"></i>',
'ppt': '<i class="fa fa-file-powerpoint-o text-danger"></i>',
'jpg': '<i class="fa fa-file-photo-o text-warning"></i>',
'pdf': '<i class="fa fa-file-pdf-o text-danger"></i>',
'zip': '<i class="fa fa-file-archive-o text-muted"></i>',
'htm': '<i class="fa fa-file-code-o text-info"></i>',
'txt': '<i class="fa fa-file-text-o text-info"></i>',
'mov': '<i class="fa fa-file-movie-o text-warning"></i>',
'mp3': '<i class="fa fa-file-audio-o text-warning"></i>',
},
layoutTemplates:{ //是否显示预览下的上传按钮
actionUpload:''
},
previewFileExtSettings: {
'doc': function(ext) {
return ext.match(/(doc|docx)$/i);
},
'xls': function(ext) {
return ext.match(/(xls|xlsx)$/i);
},
'ppt': function(ext) {
return ext.match(/(ppt|pptx)$/i);
},
'zip': function(ext) {
return ext.match(/(zip|rar|tar|gzip|gz|7z)$/i);
},
'htm': function(ext) {
return ext.match(/(php|js|css|htm|html)$/i);
},
'txt': function(ext) {
return ext.match(/(txt|ini|md)$/i);
},
'mov': function(ext) {
return ext.match(/(avi|mpg|mkv|mov|mp4|3gp|webm|wmv)$/i);
},
'mp3': function(ext) {
return ext.match(/(mp3|wav)$/i);
},
}
});
$("#file").on('fileuploaded', function(event, data, previewId, index) {
var message = mini.decode(data);
if(message.response.type == 200){
showMsg("文件上传成功","success",1800,"center","center",closeWindow());
}else{
showMsg(message.response.msg,"danger",3000,"center","center");
}
});
function closeWindow(action) {
if (window.CloseOwnerWindow) return window.CloseOwnerWindow(action);
else window.close();
}
2.controller控制层:文件上传调用的方法
/**
* @Description 上传文件--使用easyexcel方式解析
* @author qingyun
* @Date 2021年5月19日 上午10:26:03
*/
@RequestMapping("/uploadExcelFileNew")
public String uploadExcelFileNew(@RequestParam("file") MultipartFile file, HttpServletRequest request,HttpServletResponse response) {
return roadDataService.uploadExcelFileNew(file,request,response);
}
3.serice层:处理文件上传逻辑,这里定义一个工具类EasyExcelUtils进行处理,对处理结果根据Message标识判断是否解析正常,正常则设置其他字段值,把数据入库,不正常,则把message返回给前端
/**
* @Description 上传文件--使用easyexcel方式解析
* @author qingyun
* @Date 2021年5月19日 上午10:26:03
*/
public String uploadExcelFileNew(MultipartFile file, HttpServletRequest request, HttpServletResponse response) {
Message message = new Message();
EasyExcelUtils easyExcelUtils = new EasyExcelUtils(RoadData.class); //创建工具类时传递class,用于后面比对表头使用
try {
request.setCharacterEncoding("utf-8");
response.setCharacterEncoding("utf-8");
InputStream fileInput = file.getInputStream(); // 输入流
EasyExcel.read(fileInput,RoadData.class,easyExcelUtils).sheet().doRead();
message = easyExcelUtils.getMessage();
if(Message.OK == message.getType()) { //解析完成没有错误
List<Object> list = easyExcelUtils.getList(); //使用Object类型存放集合
if (null != list && list.size() > 0) {
List<GjArea> areaList = getGjArea(); //获取到所有的政区集合
Date nowDate = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String nowDateStr = sdf.format(nowDate);
List<RoadData> roadDataList = new ArrayList<RoadData>();
for (int i = 0;i < list.size();i++) { //设置其他非excel字段的值
RoadData roadData = (RoadData) list.get(i);
roadData.setId(this.getUUID());
roadData.setCreateTime(nowDate);
roadData.setCreateUserCode(this.getUserCode());
roadData.setCreateUserName(this.getUserEntity().getName());
roadData.setUnit(formatUnit(roadData.getUnit(),areaList));
roadData.setPci_score(RoadImportUtils.getPCIScore(roadData.getRoadGradeName(), String.valueOf(roadData.getPci()), String.valueOf(roadData.getPciAfter()),String.valueOf(roadData.getRepYear()),roadData.getRectificaFlag()));
roadData.setRqi_score(RoadImportUtils.getRQIScore(roadData.getPaveTypeName(), String.valueOf(roadData.getRqi()), String.valueOf(roadData.getRqiAfter()),String.valueOf(roadData.getRepYear()),roadData.getRectificaFlag()));
roadData.setRdi_score(RoadImportUtils.getRDIScore(roadData.getRoadGradeName(), roadData.getPaveTypeName(),String.valueOf(roadData.getRdi()), String.valueOf(roadData.getRdiAfter()),String.valueOf(roadData.getRepYear()),roadData.getRectificaFlag()));
roadDataList.add(roadData);
}
String isClearFlag = request.getParameter("isClearFlag");
if("true".equals(isClearFlag)) { //先清除数据再插入,只清楚在这次之前添加的数据
String hql = "delete FROM score.road_data WHERE DATE_FORMAT(create_time, '%Y-%m-%d %H:%i:%s' ) < '"+nowDateStr+"' ";
this.getDao().saveOrUpdateBySql(hql, null);
}
roadDataRepository.save(roadDataList);
message.setType(Message.OK);
message.setMsg("文件上传解析成功");
}
}
} catch (Exception e) {
e.printStackTrace();
Message messageListener = easyExcelUtils.getMessage();
if(null == messageListener || null == messageListener.getType()) {
message.setType(Message.ERROR);
message.setMsg("上传解析文件错误");
}else {
message = messageListener;
}
}
return message.toJson();
}
4.EasyExcelUtils类:使用@Component注解修饰,使类加载进spring容器中进行管理;数据的类型使用Object进行定义,定义一个Mesage类用于记录解析和验证表头是否与模板一致的信息;创建EasyExcelUtils时,传递一个接收解析excel类的class对象,使用注解ExcelProperty获取到类的字段,并与excel中的进行比对,验证导入的excel是否符合要求。
/**
* @Description 使用easyexcel方式解析数据的工具类
* @author qingyun
* @Date 2021年5月19日 上午10:35:58
*/
@Component
public class EasyExcelUtils extends AnalysisEventListener<Object> {
List<Object> list = new ArrayList<Object>();
Message message = new Message();
Class clazz;
public EasyExcelUtils() {
super();
// TODO Auto-generated constructor stub
}
public EasyExcelUtils(Class clazz) {
super();
this.clazz = clazz;
}
/**
* @Description invoke方法为一行一行读取excel内容
* @author qingyun
* @Date 2021年5月19日 上午10:43:17
*/
@Override
public void invoke(Object data, AnalysisContext context) {
list.add(data);
}
/**
* @Description invokeHeadMap读取excel表头,校验表头是否正确
* @author qingyun
* @Date 2021年5月19日 上午10:44:43
*/
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
Map<Integer, String> head = new HashMap<>();
try {
head = getIndexNameMap(clazz); //通过class获取到使用@ExcelProperty注解配置的字段
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
Set<Integer> keySet = head.keySet(); //解析到的excel表头和实体配置的进行比对
for (Integer key : keySet) {
if (StringUtils.isEmpty(headMap.get(key))) {
message.setType(Message.ERROR);
message.setMsg("表头第"+key+1+"列为空,请参照模板填写");
throw new ExcelAnalysisException("解析excel出错,请传入正确格式的excel");
}
if (!headMap.get(key).equals(head.get(key))) {
message.setType(Message.ERROR);
message.setMsg("表头第"+key+1+"列【"+headMap.get(key)+"】与模板【"+head.get(key)+"】不一致,请参照模板填写");
throw new ExcelAnalysisException("解析excel出错,请传入正确格式的excel");
}
}
}
/**
* @Description 读取完成之后进行的处理
* @author qingyun
* @Date 2021年5月19日 上午10:45:13
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
if (null == message || message.getType() == null) {
message.setMsg("数据解析完成");
message.setType(Message.OK);
}
}
/**
* @Description 通过class获取类字段信息
* @author qingyun
* @Date 2021年5月19日 下午1:41:47
*/
public Map<Integer, String> getIndexNameMap(Class clazz) throws NoSuchFieldException {
Map<Integer, String> result = new HashMap<>();
Field field;
Field[] fields = clazz.getDeclaredFields(); //获取类中所有的属性
for (int i = 0; i < fields.length; i++) {
field = clazz.getDeclaredField(fields[i].getName());
field.setAccessible(true);
ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);//获取根据注解的方式获取ExcelProperty修饰的字段
if (excelProperty != null) {
int index = excelProperty.index(); //索引值
String[] values = excelProperty.value(); //字段值
StringBuilder value = new StringBuilder();
for (String v : values) {
value.append(v);
}
result.put(index, value.toString());
}
}
return result;
}
/**
* @Description 在转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。
* @author qingyun
* @Date 2021年5月19日 下午3:02:49
*/
@Override
public void onException(Exception exception, AnalysisContext context) {
// 如果是某一个单元格的转换异常 能获取到具体行号
// 如果要获取头的信息 配合invokeHeadMap使用
if (exception instanceof ExcelDataConvertException) {
ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception;
message.setType(Message.ERROR);
message.setMsg("第"+(excelDataConvertException.getRowIndex()+1)+"行,第"+(excelDataConvertException.getColumnIndex()+1)+"列解析异常,请参照模板填写");
}
}
public List<Object> getList() {
return list;
}
public void setList(List<Object> list) {
this.list = list;
}
public Message getMessage() {
return message;
}
public void setMessage(Message message) {
this.message = message;
}
}
5.RoadData实体类:使用@ExcelProperty注解标识字段,指定index和value,用于接收excel的值,以及进行表头的比对
public class RoadData implements Serializable {
private static final long serialVersionUID = 1L;
private String id; //id
@ExcelProperty(value = "年份",index = 0)
private String repYear; //年份
@ExcelProperty(value = "养管单位",index = 1)
private String unit; //养管单位
@ExcelProperty(value = "路线",index = 2)
private String roadCode; //路线编码
@ExcelProperty(value = "上下行",index = 3)
private String roadDirectName; //行车方向
@ExcelProperty(value = "路段起点",index = 4)
private BigDecimal startStake; //起点桩号
@ExcelProperty(value = "路段终点",index = 5)
private BigDecimal endStake; //终点桩号
@ExcelProperty(value = "长度km",index = 6)
private BigDecimal roadLength; //长度
@ExcelProperty(value = "技术等级",index = 7)
private String roadGradeName; //技术等级
@ExcelProperty(value = "路面类型",index = 8)
private String paveTypeName; //路面类型
@ExcelProperty(value = "PQI",index = 9)
private BigDecimal pqi; //PQI
@ExcelProperty(value = "PCI",index = 10)
private BigDecimal pci; //PCI
@ExcelProperty(value = "RQI",index = 11)
private BigDecimal rqi; //rqi
@ExcelProperty(value = "RDI",index = 12)
private BigDecimal rdi; //rdi
@ExcelProperty(value = "DR",index = 13)
private BigDecimal dr; //dr
@ExcelProperty(value = "IRI",index = 14)
private BigDecimal iri; //iri
@ExcelProperty(value = "RD",index = 15)
private BigDecimal rd; //rd
private BigDecimal pci_score; //PCI得分
private BigDecimal rqi_score; //rqi得分
private BigDecimal rdi_score; //rdi得分
@ExcelProperty(value = "PQI分级",index = 16)
private String pqiGrade; //PQI分级
@ExcelProperty(value = "PCI分级",index = 17)
private String pciGrade; //PCI分级
@ExcelProperty(value = "RQI分级",index = 18)
private String rqiGrade; //RQI分级
@ExcelProperty(value = "RDI分级",index = 19)
private String rdiGrade; //RDI分级
@ExcelProperty(value = "区域",index = 20)
private String theAreaName; //区域
@ExcelProperty(value = "抽检性质",index = 21)
private String randomNature; //抽检性质
@ExcelProperty(value = "PCI整改后得分",index = 22)
private BigDecimal pciAfter; //PCI整改后得分
@ExcelProperty(value = "RQI整改后得分",index = 23)
private BigDecimal rqiAfter; //RQI整改后得分
@ExcelProperty(value = "RDI整改后得分",index = 24)
private BigDecimal rdiAfter; //RDI整改后得分
@ExcelProperty(value = "整改标识",index = 25)
private String rectificaFlag; //整改标识(未完成/已完成)
private String createUserCode;
private String createUserName;
private Date createTime;
}
二.读取excel的相关技术点
1.读取excel的方式
(1)源码按路径path的方式
public static ExcelReaderBuilder read(String pathName, Class head, ReadListener readListener) {
ExcelReaderBuilder excelReaderBuilder = new ExcelReaderBuilder();
excelReaderBuilder.file(pathName);
if (head != null) {
excelReaderBuilder.head(head);
}
if (readListener != null) {
excelReaderBuilder.registerReadListener(readListener);
}
return excelReaderBuilder;
}
(2)按文件输入流的方式读
public static ExcelReaderBuilder read(InputStream inputStream, Class head, ReadListener readListener) {
ExcelReaderBuilder excelReaderBuilder = new ExcelReaderBuilder();
excelReaderBuilder.file(inputStream);
if (head != null) {
excelReaderBuilder.head(head);
}
if (readListener != null) {
excelReaderBuilder.registerReadListener(readListener);
}
return excelReaderBuilder;
}
(3)按file读取
public static ExcelReaderBuilder read(File file, Class head, ReadListener readListener) {
ExcelReaderBuilder excelReaderBuilder = new ExcelReaderBuilder();
excelReaderBuilder.file(file);
if (head != null) {
excelReaderBuilder.head(head);
}
if (readListener != null) {
excelReaderBuilder.registerReadListener(readListener);
}
return excelReaderBuilder;
}
2.读取sheet数量
(1)只能读取到最前面的一个sheet
EasyExcel.read(fileInput,RoadData.class,easyExcelUtils).sheet().doRead()
(2)读取所有的sheet:
EasyExcel.read(fileInput,RoadData.class,easyExcelUtils).doReadAll()
(3)读取指定的sheet:
ExcelReader excelReader = EasyExcel.read(fileInput).build();
// 这里为了简单 所以注册了 同样的head 和Listener 自己使用功能必须不同的Listener
ReadSheet readSheet1 =
EasyExcel.readSheet(0).head(RoadData.class).registerReadListener(easyExcelUtils).build();
ReadSheet readSheet2 =
EasyExcel.readSheet(1).head(RoadData.class).registerReadListener(easyExcelUtils).build();
// 这里注意 一定要把sheet1 sheet2 一起传进去,不然有个问题就是03版的excel 会读取多次,浪费性能
excelReader.read(readSheet1, readSheet2);
// 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的
excelReader.finish();
3.指定从第几行开始读数据
EasyExcel.read(fileInput,RoadData.class,easyExcelUtils).headRowNumber(1).doReadAll();
三.导出excel
1.前端发起请求
function download(){ //导出报表
var zqCode = "";
var year = "";
var params = {"zqCode":zqCode,"year":year};
var url = basePath + "roadData/download";
downloadFile(url, params); //组织和发送下载请求
}
//组织和发送下载请求
function downloadFile(path, params) {
$("#downloadform").remove();
var form = $("<form>"); //定义一个form表单
form.attr("id", "downloadform");
form.attr("style", "display:none");
form.attr("target", "");
form.attr("method", "post");
form.attr("action", path);
for(var key in params) {
var input1 = $("<input>");
input1.attr("type", "hidden");
input1.attr("name", key);
input1.attr("value", params[key]);
form.append(input1);
}
$("body").append(form); //将表单放置在web中
form.submit(); //表单提交()
}
2.controller控制层
@RequestMapping(value = { "/download" }, produces = { "text/html;charset=UTF-8" })
@ResponseBody
public void download(HttpServletRequest request, HttpServletResponse response) {
response.setCharacterEncoding("UTF-8");
//String id = request.getParameter("id");
roadDataService.download(response);
}
3.service层
public void download(HttpServletResponse response) {
String fileName;
try {
fileName = "文件名称.xlsx";
fileName = new String(URLEncoder.encode(fileName, "UTF-8").getBytes(), "ISO-8859-1");
response.addHeader("Content-Disposition", " attachment;filename=" + fileName);
response.setContentType("application/octet-stream");
ServletOutputStream out = response.getOutputStream();
EasyExcel.write(out,RoadDataNew.class).sheet("学生列表").doWrite(getData());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
4.实体类:复杂表头的情况,给@ExcelProperty的value赋值时,需要具备所有行的表头;使用注解@ExcelIgnore忽略导出的字段是否包含
/**
* @Description 添加此类是处理复杂表头的情况
* @author qingyun
* @Date 2021年5月19日 下午4:51:26
*/
public class RoadDataNew implements Serializable {
private static final long serialVersionUID = 1L;
@ExcelIgnore
private String id; //id
@ExcelProperty(value = {"年份","年份"},index = 0)
private String repYear; //年份
@ExcelProperty(value = {"养管单位","养管单位"},index = 1)
private String unit; //养管单位
@ExcelProperty(value = {"路线情况","路线"},index = 2)
private String roadCode; //路线编码
@ExcelProperty(value = {"路线情况","上下行"},index = 3)
private String roadDirectName; //行车方向
@ExcelProperty(value = {"路段起点","路段起点"},index = 4)
private BigDecimal startStake; //起点桩号
@ExcelProperty(value = {"路段终点","路段终点"},index = 5)
private BigDecimal endStake; //终点桩号
@ExcelProperty(value = {"长度km","长度km"},index = 6)
private BigDecimal roadLength; //长度
@ExcelProperty(value = {"路面情况","技术等级"},index = 7)
private String roadGradeName; //技术等级
@ExcelProperty(value = {"路面情况","路面类型"},index = 8)
private String paveTypeName; //路面类型
@ExcelProperty(value = {"PQI","PQI"},index = 9)
private BigDecimal pqi; //PQI
@ExcelProperty(value = {"PCI","PCI"},index = 10)
private BigDecimal pci; //PCI
@ExcelProperty(value = {"RQI","RQI"},index = 11)
private BigDecimal rqi; //rqi
@ExcelProperty(value = {"RDI","RDI"},index = 12)
private BigDecimal rdi; //rdi
@ExcelProperty(value = {"DR","DR"},index = 13)
private BigDecimal dr; //dr
@ExcelProperty(value = {"IRI","IRI"},index = 14)
private BigDecimal iri; //iri
@ExcelProperty(value = {"RD","RD"},index = 15)
private BigDecimal rd; //rd
@ExcelIgnore
private BigDecimal pci_score; //PCI得分
@ExcelIgnore
private BigDecimal rqi_score; //rqi得分
@ExcelIgnore
private BigDecimal rdi_score; //rdi得分
@ExcelProperty(value = {"PQI分级","PQI分级"},index = 16)
private String pqiGrade; //PQI分级
@ExcelProperty(value = {"PCI分级","PCI分级"},index = 17)
private String pciGrade; //PCI分级
@ExcelProperty(value = {"RQI分级","RQI分级"},index = 18)
private String rqiGrade; //RQI分级
@ExcelProperty(value = {"RDI分级","RDI分级"},index = 19)
private String rdiGrade; //RDI分级
@ExcelProperty(value = {"区域","区域"},index = 20)
private String theAreaName; //区域
@ExcelProperty(value = {"抽检性质","抽检性质"},index = 21)
private String randomNature; //抽检性质
@ExcelProperty(value = {"PCI整改后得分","PCI整改后得分"},index = 22)
private BigDecimal pciAfter; //PCI整改后得分
@ExcelProperty(value = {"RQI整改后得分","RQI整改后得分"},index = 23)
private BigDecimal rqiAfter; //RQI整改后得分
@ExcelProperty(value = {"RDI整改后得分","RDI整改后得分"},index = 24)
private BigDecimal rdiAfter; //RDI整改后得分
@ExcelProperty(value = {"整改标识","整改标识"},index = 25)
private String rectificaFlag; //整改标识(未完成/已完成)
@ExcelIgnore
private String createUserCode;
@ExcelIgnore
private String createUserName;
@ExcelIgnore
private Date createTime;
}
导出截图
5.设置导出excel的表头高宽:加在类上或者字段上
@HeadRowHeight(40) //设置投行高
@ContentRowHeight(20)//设置文本行高
@ColumnWidth(10) //设置列宽