【EasyExcel详细步骤】(内附源码)
页面预览
数据导出
数据导入
第01章-Alibaba EasyExcel
1、EasyExcel介绍
1.1、EasyExcel的作用
- 数据导入:减轻录入工作量
- 数据导出:统计信息归档
- 数据传输:异构系统之间数据传输
1.2、EasyExcel的特点
-
快速:快速的读取excel中的数据。
-
简洁:映射excel和实体类,让代码变的更加简洁。
-
大文件:在读写大文件的时候使用磁盘做缓存,更加的节约内存。
2、快速开始
参考文档地址:https://easyexcel.opensource.alibaba.com/index.html
service-cmn模块添加依赖
<!--easyexcel-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
</dependency>
2.1、最简单的写
在测试目录下创建以下文件
实体类:
package com.atguigu.syt.cmn.easyexcel;
@Getter
@Setter
@EqualsAndHashCode
public class DemoData {
@ExcelProperty("字符串标题")
private String string;
@ExcelProperty("日期标题")
private Date date;
@ExcelProperty("数字标题")
private Double doubleData;
/**
* 忽略这个字段
*/
@ExcelIgnore
private String ignore;
}
测试用例:
package com.atguigu.syt.cmn.easyexcel;
public class ExcelWriteTest {
/**
* 最简单的写
* <p>
* 1. 创建excel对应的实体对象 参照{@link DemoData}
* <p>
* 2. 直接写即可
*/
@Test
public void simpleWrite07() {
// 注意 simpleWrite在数据量不大的情况下可以使用(5000以内,具体也要看实际情况),数据量大参照 重复多次写入
// 写法2
String fileName = "d:/simpleWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
// 如果这里想使用03 则 传入excelType参数即可
EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());
}
@Test
public void simpleWrite03() {
String fileName = "d:/simpleWrite" + System.currentTimeMillis() + ".xls";
EasyExcel.write(fileName, DemoData.class).excelType(ExcelTypeEnum.XLS).sheet("模板").doWrite(data());
}
private List<DemoData> data() {
List<DemoData> list = ListUtils.newArrayList();
//算上标题,最多可写65536行(.xls)
//算上标题,最多可写1048576行(.xlsx)
//java.lang.IllegalArgumentException: Invalid row number (65536) outside allowable range (0..65535)
for (int i = 0; i < 10; i++) {
DemoData data = new DemoData();
data.setString("字符串" + i);
data.setDate(new Date());
data.setDoubleData(0.56);
list.add(data);
}
return list;
}
}
2.2、最简单的读
在测试目录下创建以下文件
监听器:
package com.atguigu.syt.cmn.easyexcel;
// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
@Slf4j
public class DemoDataListener implements ReadListener<DemoData> {
/**
* 每隔3条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 3;
/**
* 缓存的数据
*/
private List<DemoData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
/**
* 这个每一条数据解析都会来调用
*/
@Override
public void invoke(DemoData data, AnalysisContext context) {
log.info("解析到一条数据:{}", 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());
// demoDAO.save(cachedDataList);
log.info("存储数据库成功!");
}
}
测试用例:
package com.atguigu.syt.cmn.easyexcel;
public class ExcelReadTest {
/**
* 最简单的读
* <p>
* 1. 创建excel对应的实体对象 参照{@link DemoData}
* <p>
* 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener}
* <p>
* 3. 直接读即可
*/
@Test
public void simpleRead() {
// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
// 写法3:
String fileName = "d:/demo.xlsx";
// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();
}
}
第02章-数据导出
1、后端接口
参考文档:写Excel | Easy Excel (alibaba.com)
1.1、Controller
AdminRegionController
/**
* 文件下载并且失败的时候返回json(默认失败了会返回一个有部分数据的Excel)
*
* @since 2.1.1
*/
@ApiOperation(value="导出")
@GetMapping(value = "/exportData")
public void downloadFailedUsingJson(HttpServletResponse response) throws IOException {
// 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
try {
//int i = 9/0;
List<RegionExcelVo> regionExcelVoList = regionService.findRegionExcelVoList();
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(), RegionExcelVo.class)
.sheet("数据字典")
.doWrite(regionExcelVoList);
} catch (Exception e) {
log.error(ExceptionUtils.getStackTrace(e));
// 重置response
response.reset();
response.setContentType("text/html");
response.setCharacterEncoding("utf-8");
response.getWriter().println("导出失败");
}
}
1.2、Service
接口:RegionService
/**
* 获取地区VO列表
* @return
*/
List<RegionExcelVo> findRegionExcelVoList();
实现:RegionServiceImpl
@Override
public List<RegionExcelVo> findRegionExcelVoList() {
List<Region> regionList = this.list();
List<RegionExcelVo> regionExcelVoList = ListUtils.newArrayListWithCapacity(regionList.size());
for(Region region : regionList) {
RegionExcelVo regionExcelVo = new RegionExcelVo();
BeanUtils.copyProperties(region, regionExcelVo);
regionExcelVoList.add(regionExcelVo);
}
return regionExcelVoList;
}
2、前端页面
在cmn/region/list.vue文件中添加如下代码
2.1、html
在表格上方添加如下代码
<div style="margin-bottom:5px;">
<el-button type="success" icon="el-icon-top" size="mini" @click="exportData"><i class="fa fa-plus" />导出</el-button>
</div>
2.2、脚本
在脚本中添加如下methods
引入模块:
import store from '@/store'
import axios from 'axios'
定义脚本:
exportData() {
//此种方式无法传递token
//window.open(process.env.VUE_APP_BASE_API + '/admin/cmn/region/exportData')
//使用当前方式传递token
const config = {
method: 'get',
url: '/dev-api/admin/cmn/region/exportData',
headers: {
'token': store.getters.token //
},
responseType: 'blob'
}
axios(config).then(response => {
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a')
link.href = url
link.setAttribute('download', '数据字典.xlsx')
document.body.appendChild(link)
link.click()
})
},
第03章-数据导入
1、后端接口
1.1、Controller
AdminRegionController
@ApiOperation(value = "导入")
@ApiImplicitParam(name = "file", value = "文件", required = true)
@PostMapping("/importData")
public Result importData(MultipartFile file) {
regionService.importData(file);
return Result.ok();
}
1.2、Service
接口:RegionService
/**
* 地区数据导入Excel
* @param file
*/
void importData(MultipartFile file);
实现:RegionServiceImpl
@Override
public void importData(MultipartFile file) {
try {
long b = System.currentTimeMillis();
EasyExcel.read(file.getInputStream(),RegionExcelVo.class, new RegionExcelListener(baseMapper)).sheet().doRead();
long e = System.currentTimeMillis();
log.info("用时:" + (e - b) + "ms" );
} catch (IOException e) {
throw new GuiguException(ResultCodeEnum.FAIL, e);
}
}
1.3、监听器
RegionExcelListener
package com.atguigu.syt.cmn.listener;
// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
@Slf4j
public class RegionExcelListener implements ReadListener<RegionExcelVo> {
/**
* 每隔3条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 100;
/**
* 缓存的数据
*/
private List<RegionExcelVo> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
/**
* 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
*/
private RegionMapper regionMapper;
/**
* 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
*
* @param regionMapper
*/
public RegionExcelListener(RegionMapper regionMapper) {
this.regionMapper = regionMapper;
}
/**
* 这个每一条数据解析都会来调用
*/
@Override
public void invoke(RegionExcelVo data, AnalysisContext context) {
log.info("解析到一条数据:{}", 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());
regionMapper.saveBatch(cachedDataList);
log.info("存储数据库成功!");
}
}
1.4、Mapper
接口:RegionMapper
/**
* 根据RegionExcelVo列表批量保存数据
* @param cachedDataList
*/
void saveBatch(@Param("list") List<RegionExcelVo> cachedDataList);
实现:RegionMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.syt.cmn.mapper.RegionMapper">
<insert id="saveBatch">
INSERT INTO region ( id, code, parent_code, name, level )
VALUES
<foreach collection="list" item="region" separator=",">
(
#{region.id},
#{region.code},
#{region.parentCode},
#{region.name},
#{region.level}
)
</foreach>
</insert>
</mapper>
1.5、service的pom.xml配置
程序发布时,默认情况下java目录下的xml文件不会被发布,需要进行以下配置
<build>
<!-- 项目打包时会将java目录中的*.xml文件也进行打包 -->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.yml</include>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
2、前端页面
在cmn/region/list.vue文件中添加如下代码
2.1、添加导入按钮
<el-button type="primary" icon="el-icon-bottom" size="mini" @click="importData"><i class="fa fa-plus" />导入</el-button>
2.2、添加导入弹出层
<el-dialog title="导入" :visible.sync="dialogImportVisible" width="480px">
<el-form label-position="right" label-width="100px">
<el-form-item label="请选择文件">
<!-- 注意:这里使用headers属性传递了token -->
<el-upload
v-loading="uploadLoading"
element-loading-text="数据导入中"
:headers="{ token }"
:multiple="false"
:on-progress="onUploadProgress"
:on-success="onUploadSuccess"
:on-error="onUploadError"
:action="VUE_APP_BASE_API + '/admin/cmn/region/importData'">
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">
只能上传xls文件,且不超过1M
</div>
</el-upload>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogImportVisible = false">取消</el-button>
</div>
</el-dialog>
2.3、添加导入弹出层属性
data:
dialogImportVisible: false,
uploadLoading: false, //文件上传状态
VUE_APP_BASE_API: process.env.VUE_APP_BASE_API, //接口地址
token: store.getters.token,
2.4、添加导入方法
methods:
//显示一个文件选择对话框
importData() {
this.dialogImportVisible = true
},
//文件上传中
onUploadProgress(){
this.uploadLoading = true
},
onUploadSuccess(response) {
this.uploadLoading = false
//文件导入成功
if (response.code === 200) {
this.$message.success(response.message)
this.dialogImportVisible = false
this.getDictList(1)
} else {
//导入失败
this.$message.error(response.message)
this.dialogImportVisible = false
}
},
//导入失败
onUploadError() {
this.uploadLoading = false
this.$message.error('系统错误')
this.dialogImportVisible = false
},
本文来自博客园,作者:自律即自由-,转载请注明原文链接:https://www.cnblogs.com/deyo/p/17475899.html