一、说明:
众所周知,Excel文件分为03版本excel和07版本excel文件
03版最多容量为65535*256,而07版单张sheet为1048576*16384,总容量无限制
03版的后缀名为.xls,07版的后缀是.xlsx
二、对Excel文件处理
在Java领域中,对excel文件进行解析、生成比较闻名的框架是Apache poi和阿里的EasyExcel。但俩者有显著的区别。
POI技术在解析Excel文件时,是一次性将表里的数据加载到内存中,这很可能会导致内存溢出的问题,尤其是当excel的存在大数据量时,就很大可能会超出可用内存的内存限制。
所以POI技术非常消耗内存。
EasyExcel在解析文件时,是从磁盘上一行一行的进行读取数据,并通过设置监听器(观察者的模式)进行通知。因此,大大减少了占用内存。
三、使用EasyExcel
在了解的俩者区别后,我选择了使用EasyExcel来对excel文件进行处理
3.1导入依赖
在jdk版本为1.8及1.8以上时,导入EasyExcel的版本最好为3.xx的版本。
//通用版本
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.1.4</version>
</dependency>
//若依赖中存在导入或不使用Slf4j依赖则需要将其中封装的取消导入
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.1.4</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
3.2实体
/**
* 用户ID
*/
@TableId(type = IdType.AUTO)
@ExcelIgnore
private Long userId;
/**
* 用户名
*/
@ExcelProperty("用户名")
private String username;
/**
* 密码
*/
@ExcelIgnore
private String password;
/**
* 性别
*/
@ExcelProperty(value = "性别",converter = SexUtil.class)
private int gender;
/**
* 手机号
*/
@ExcelProperty("手机号")
/**
* 邮箱
*/
@ExcelProperty("邮箱")
private String email;
/**
* 真实姓名
*/
@ExcelProperty("姓名")
private String realName;
/**
* 状态 0:禁用 1:正常
*/
@ExcelIgnore
private Integer status;
/**
* 创建时间
*/
// @ExcelIgnore
@DateTimeFormat(value = "yyyy-MM-dd")
@ExcelProperty(value = "创建时间",converter = DateConverter.class )
private Date createTime;
@TableLogic(value = "0", delval = "1")
@ExcelIgnore
private int del;
对于导入导出的显示在excel字段 可以添加注解:@ExcelProperty、@ExcelIgnore
前者是需要将字段进行处理,后者是不需要显示在excel则添加。如果一个属性什么注解都没有,则将显示原本的如:属性为:email,则在excel也显示 email。添加了@ExcelProperty("邮箱") 则显示 邮箱。
3.3自定义转换类
在进行对性别属性的数据库存储时,我采用了存储数字(0:女、1:男)这种方式存储的原因在此我就不多说了,懂的都懂。那为什么我要在此说明这个属性呢?因为在进行文件的导入导出时,总不能对性别进行导入1、0或者导出1、0供用户查看吧?所以在此仍需写一个处理性别数据转换的类。在EasyExcel提供了Converter接口。在处理文件的导入导出时,此接口提供了俩个方法:convertToJavaData和convertToExcelData。具体细看如下
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
public class SexUtil implements Converter<Integer> {
/**
* 导入时,对数据的男、女进行转换
* @param cellData
* @param contentProperty
* @param globalConfiguration
* @return
* @throws Exception
*/
@Override
public Integer convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
String value = cellData.getStringValue();
switch (value){
case "男":
return 1;
case "女":
return 0;
default:
return -1;
}
}
/**
* 导出时 对数据的1、0进行转换
* @param value
* @param contentProperty
* @param globalConfiguration
* @return
* @throws Exception
*/
@Override
public WriteCellData<?> convertToExcelData(Integer value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
switch (value){
case 1:
return new WriteCellData<>("男");
case 0:
return new WriteCellData<>("女");
default:
return new WriteCellData<>("未知");
}
}
}
那怎么使用这个转换类?麻烦细看实体类中的性别字段
3.4IDEA中的导入实现
controller类
/**
* 文件上传
* @param file
* @return
* @throws IOException
*/
@PostMapping("/upload")
@ResponseBody
public R upload(MultipartFile file) throws IOException {
EasyExcel.read(file.getInputStream(), EmpEntity.class, new
UploadEmpListener(empService)).sheet().doRead();
return R.ok();
}
根据EasyExcel官网提供信息:使用read方法 并且设置一个监听类。
EasyExcel.read(file.getInputStream(), EmpEntity.class, new
UploadEmpListener(empService)).sheet().doRead();至于参数传service还是dao就看具体的用法
监听类
package com.wedu.modules.emp.listener;
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.wedu.modules.emp.entity.EmpEntity;
import com.wedu.modules.emp.service.EmpService;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
@Slf4j
public class UploadResignListener implements ReadListener<EmpEntity> {
/**
* 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 100;
/**
* 缓存的数据
*/
private List<EmpEntity> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
/**
* 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
*/
private EmpEntity empService;
/**
* 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
*
* @param empService
*/
public UploadResignListener(EmpEntity empService) {
this.empService= empService;
}
/**
* 这个每一条数据解析都会来调用
*
* @param data one row value. Is is same as {@link AnalysisContext#readRowHolder()}
* @param context
*/
@Override
public void invoke(ResignEntity data, AnalysisContext context) {
log.info("解析到一条数据:{}", JSON.toJSONString(data));
cachedDataList.add(data);
// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (cachedDataList.size() >= BATCH_COUNT) {
saveData();
log.info("准备清理!");
// 存储完成清理 list
cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
/**
* 所有数据解析完成了 都会来调用
*
* @param context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 这里也要保存数据,确保最后遗留的数据也存储到数据库
saveData();
log.info("所有数据解析完成!");
}
/**
* 加上存储数据库
*/
private void saveData() {
log.info("{}条数据,开始存储数据库!", cachedDataList.size());
empService.saveBatch(cachedDataList);
log.info("存储数据库成功!");
}
}
我使用的是SpringBoot+Mybatis-plus 所以我直接使用了mybatis-plus提供的批量插入方法
3.5Vue的导入实现
我使用的Element-UI提供的组件
<el-upload
action="http://localhost:8080/emp/info/upload"
:headers="tokenInfo"
style="display: inline-block"
:show-file-list="true"
:before-upload="headUpload"
:on-success="handleExcelImportSuccess"
name="file">
<el-button type="primary" style="margin: 10px " >
<template #icon>
<el-icon><Upload /></el-icon>
</template>
导入
</el-button>
</el-upload>
action:请求的路径
:headers 我使用了jwt令牌,携带了token往后端进行判断。看各位具体使用,不一定使用
:show-file-list="true" 为true时,则显示上传的文件
:before-upload="headUpload" 顾名思义是上传前的操作
:on-success="handleExcelImportSuccess" 则是成功后的操作
具体的我们可去Element-UI官网查看更多的使用。接着我们看看上传前需要什么操作
headUpload(file) {
const isExcel1 = file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
const isExcel2 = file.type === 'application/vnd.ms-excel';
const isLt10M = file.size / 1024 / 1024 < 10;
const filexlsx =!(file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
const filexls = !(file.type === 'application/vnd.ms-excel');
if(filexlsx && filexls) {
this.$message.error('上传Excel文件只能是xlsx/xls 格式!');
return false;
}
if (!isLt10M) {
this.$message.error('上传Excel文件大小不能超过 10MB!');
}
return (isExcel1 || isExcel2) && isLt10M;
}
先前我曾说明excel是有俩种版本,则分别对应了'application/vnd.ms-excel'、'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
则在此方法中,我进行了上传前的判断,是否为这俩种excel中的其中一种,还有文件的大小是否超过10M
然后就是上传后的处理,查看响应。由于后端我使用的是统一格式类进行返回。
handleExcelImportSuccess(response,file){
if(response.msg == 'success'){
alert("导入成功")
}
}
3.6IDEA中的导出实现
controller类
@PostMapping("/down")
public void create(@RequestBody Long[] userIds, HttpServletResponse response) throws IOException {
List<SysUserEntity> list = empService.getDown(Arrays.asList(userIds));
String fileName = "数据测试";
ExcelUtils.createExcel(response,fileName,list,SysUserEntity.class);
}
我是在前端选择要导出的数据,并只传回对应的id在到后端进行查询,查询在此就不展示了,相信对于各位看官都是轻松的事情。取到数据后,传到我自定义的工具类中。
工具类
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.wedu.config.CustomCellWriteWidthConfig;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.List;
public class ExcelUtils {
public static <T> void createExcel(HttpServletResponse response, String fileName, List<T> list,Class<T> clazz) throws IOException {
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
response.setCharacterEncoding("utf-8");
fileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+","%20");
response.setHeader("Content-disposition", "attachment;filename=" + fileName+".xlsx");
//定义ExcelWriterSheetBuilder
ExcelWriterSheetBuilder excelWriterSheetBuilder = EasyExcel
.write(response.getOutputStream())
.head(clazz)
.excelType(ExcelTypeEnum.XLSX)
.registerWriteHandler(new CustomCellWriteWidthConfig())
.sheet("sheet");
//设置头样式
WriteCellStyle headWriteCellStyle = new WriteCellStyle();
WriteFont headWriteFont = new WriteFont();
//设置表头字体
headWriteFont.setFontName("宋体");
//设置设置表头字体大小
headWriteFont.setFontHeightInPoints((short) 13);
//设置表头是否加粗
headWriteFont.setBold(true);
//填充到表头样式
headWriteCellStyle.setWriteFont(headWriteFont);
headWriteCellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
headWriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);
headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
//设置内容格式
WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
WriteFont contentWriteFont = new WriteFont();
// 设置内容样式
contentWriteFont.setFontName("宋体");
contentWriteFont.setFontHeightInPoints((short) 12);
contentWriteCellStyle.setWriteFont(contentWriteFont);
HorizontalCellStyleStrategy horizontalCellStyleStrategy =
new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
//设计内容居中
contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
contentWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
//设置内容自动换行
// contentWriteCellStyle.setWrapped(true);
excelWriterSheetBuilder.registerWriteHandler(horizontalCellStyleStrategy);
//调用doWrite方法
excelWriterSheetBuilder.doWrite(list);
}
}
无需返回值,在此后端的导出就结束了。
3.7Vue的导出实现
down(){
var userIds = this.dataListSelections.map(item => {
return item.userId
})
this.$http({
url: this.$http.adornUrl('/emp/info/down'),
method: 'post',
responseType: 'blob',
data: this.$http.adornData(userIds, false)
}).then(response => {
// 创建一个 URL 对象,指向返回的二进制数据
const url = window.URL.createObjectURL(new Blob([response.data]));
// 创建一个 <a> 元素,设置其属性,模拟点击下载
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', '员工信息.xlsx'); // 设置下载文件的默认名称
document.body.appendChild(link);
link.click();
// 清理创建的 URL 对象
window.URL.revokeObjectURL(url);
}).catch(error => {
console.error('下载失败', error);
});
}
首先我们应当遍历我们选中的id。我将选中的id都进行存储到dataListSelections的数组中
在<el-table>标签的属性中@selection-change="selectionChangeHandle"定义选中方法
然后 在return 中 定义 dataListSelections: [] 数组
selectionChangeHandle (val) {
this.dataListSelections = val
}
这样就能实现将id存储到数组中
导出时仍需对响应进行处理,处理的不是后端,因为后端没有返回值。而是处理导出的文件。
到此,导入导出就成功实现了。我不推荐各位只是cv工程,还是可以多看看官网提供的使用方法。若小弟存在错误的地方,各位也可以提出来一起学习!哈哈
本文介绍了在Java中处理Excel文件时,ApachePOI和EasyExcel的区别,重点讲解了EasyExcel的内存优化特性以及如何使用EasyExcel进行数据导入导出,包括自定义转换类和在SpringBoot项目中的实现方法。
1万+

被折叠的 条评论
为什么被折叠?



