文章目录
Excel导入导出的应用场景
1 、数据导入:减轻录入工作量
2 、数据导出:统计信息归档
3 、数据传输:异构系统之间数据传输
EasyExcel特点
- Java领域解析、生成Excel比较有名的框架有Apache poi、jxl等。但他们都存在一个严重的问题就是非常的耗内存。如果你的系统并发量不大的话可能还行,但是一旦并发上来后一定会OOM或者JVM频繁的full gc.
- EasyExcel是阿里巴巴开源的一个excel处理框架,以使用简单、节省内存著称。EasyExcel能大大减少占用内存的主要原因是在解析Excel时没有将文件数据一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析。
- EasyExcel采用一行一行的解析模式(读),并将一行的解析结果以观察者的模式通知处理(AnalysisEventListener)
EayExcel的底层是直接和磁盘交互,并且一行一行读取表格,内存占用很小,下方有测试结果.
EasyExcel配置
pom中引入xml相关依赖
<dependencies>
<!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.1.1</version>
</dependency>
</dependencies>
对照excel列属性创建实体类
设置表头和添加的数据字段和get,set方法
@Data
public class DemoData {
//设置excel表头名称,index第几列
@ExcelProperty(value = "学生编号",index = 0)
private Integer sno;
//设置表头名称
@ExcelProperty(value = "学生姓名",index = 1)
private String sname;
}
实现写操作(将数据写入excel)
//实现excel写的操作
//1 设置写入文件夹地址和excel文件名称
String filename = "F:\\write.xlsx";
//2 调用easyexcel里面的方法实现写操作
//write方法两个参数:第一个参数文件路径名称,第二个参数实体类.class=实体类的路径,sheet是excel文件的下标,文件流会自动关闭
EasyExcel.write(filename,DemoData.class).sheet("学生列表").doWrite(getData());
实现读操作(读出excel数据)
- 读excel操作,主要是在设置的监听类中的invoke函数中的处理实体类参数,实现读数据和处理数据等功能(使用传入的service参数进行处理)
- 读取excel内容,一行一行读取excel内容,不读取表头,然后使用invoke函数通过实体类参数实现一行一行处理(若excel有多行数据,invoke会被调用多次,传递多次实体类参数(存储每行内容))
- 因为读excel监听类AnalysisEventListener<实体类>不能交给spring进行管理(无法使用注解),需要自己new,不能注入其他对象,不能实现数据库操作,因为不能注入service,mapper等等,当然也可以直接自己用最原始的jdbc办法
- 实例化监听类的时候将service作为构造函数参数传递
new SubjectExcelListener(subjectService))
操作代码
//添加课程分类
//获取上传过来文件,把文件内容读取出来
@PostMapping("addSubject")
public R addSubject(MultipartFile file) {
//上传过来excel文件
subjectService.saveSubject(file,subjectService);
return R.ok();
}
try {
//文件输入流
InputStream in = file.getInputStream();
//调用方法进行读取传入的excel和service对象
EasyExcel.read(in, SubjectData.class,new SubjectExcelListener(subjectService)).sheet().doRead();
}catch(Exception e){
e.printStackTrace();
}
监听类
package com.qlugcl.eduservice.listener;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.qlugcl.eduservice.entity.EduSubject;
import com.qlugcl.eduservice.entity.excel.SubjectData;
import com.qlugcl.eduservice.service.EduSubjectService;
import com.qlugcl.servicebase.exceptionhandler.GCLException;
public class SubjectExcelListener extends AnalysisEventListener<SubjectData> {
//因为SubjectExcelListener不能交给spring进行管理(无法使用注解),需要自己new,不能注入其他对象
//不能实现数据库操作,因为不能注入service,mapper等等,带参构造方法,当然也可以直接自己用最原始jdbc的办法
public EduSubjectService subjectService;
public SubjectExcelListener() {}
// 此处通过传递service参数,从而实现对于数据库的操作
public SubjectExcelListener(EduSubjectService subjectService) {
this.subjectService = subjectService;
}
//读取excel内容,一行一行进行读取
@Override
public void invoke(SubjectData subjectData, AnalysisContext analysisContext) {
if(subjectData == null) {
throw new GCLException(20001,"文件数据为空");
}
//一行一行读取,每次读取有两个值,第一个值一级分类,第二个值二级分类
//判断一级分类是否重复,是否存在
EduSubject existOneSubject = this.existOneSubject(subjectService, subjectData.getOneSubjectName());
if(existOneSubject == null) { //没有相同一级分类,进行添加
existOneSubject = new EduSubject();
existOneSubject.setParentId("0");
existOneSubject.setTitle(subjectData.getOneSubjectName());//一级分类名称
subjectService.save(existOneSubject);
}
//获取一级分类id值
String pid = existOneSubject.getId();
//添加二级分类
//判断二级分类是否重复,是否存在
EduSubject existTwoSubject = this.existTwoSubject(subjectService, subjectData.getTwoSubjectName(), pid);
if(existTwoSubject == null) {
existTwoSubject = new EduSubject();
existTwoSubject.setParentId(pid);
existTwoSubject.setTitle(subjectData.getTwoSubjectName());//二级分类名称
subjectService.save(existTwoSubject);
}
}
//判断一级分类不能重复添加
private EduSubject existOneSubject(EduSubjectService subjectService,String name) {
QueryWrapper<EduSubject> wrapper = new QueryWrapper<>();
wrapper.eq("title",name);
wrapper.eq("parent_id","0");
EduSubject oneSubject = subjectService.getOne(wrapper);
return oneSubject;
}
//判断二级分类不能重复添加
private EduSubject existTwoSubject(EduSubjectService subjectService, String name, String pid) {
QueryWrapper<EduSubject> wrapper = new QueryWrapper<>();
wrapper.eq("title",name);
wrapper.eq("parent_id",pid);
EduSubject twoSubject = subjectService.getOne(wrapper);
return twoSubject;
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
}
}
EasyExcel读excel应用
读excel操作,主要是在设置的监听类中的invoke函数中实现读数据和处理数据等功能
package com.createcode.excel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import java.util.Date;
import java.util.Map;
//excel读的监听器,一旦产生读excel操作,那么就会触发读的对应配置
public class ExcelListener extends AnalysisEventListener<DemoData> {
//一行一行读取excel内容,不读取表头,在invoke函数中实现对于读取出来的数据进行操作
@Override
public void invoke(DemoData data, AnalysisContext analysisContext) {
System.out.println("****"+data+""+new Date());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//读取表头内容
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
System.out.println("表头:"+headMap);
}
//读取完成之后
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) { }
}
前端VUE界面
<template>
<div class="app-container">
<el-form label-width="120px">
<el-form-item label="信息描述">
<el-tag type="info">excel模版说明</el-tag>
<el-tag>
<i class="el-icon-download"/>
<a :href="'/static/01.xlsx'">点击下载模版</a>
</el-tag>
</el-form-item>
<el-form-item label="选择Excel">
<el-upload
ref="upload"
:auto-upload="false"
:on-success="fileUploadSuccess"
:on-error="fileUploadError"
:disabled="importBtnDisabled"
:limit="1"
:action="BASE_API+'/eduservice/subject/addSubject'"
name="file"
accept="application/vnd.ms-excel">
<el-button slot="trigger" size="small" type="primary">选取文件</el-button>
<el-button
:loading="loading"
style="margin-left: 10px;"
size="small"
type="success"
@click="submitUpload">上传到服务器</el-button>
</el-upload>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default {
data() {
return {
BASE_API: process.env.BASE_API, // 接口API地址
importBtnDisabled: false, // 按钮是否禁用,
loading: false
}
},
created() {
},
methods:{
//点击按钮上传文件到接口里面
submitUpload() {
this.importBtnDisabled = true
this.loading = true
if(this.$refs.upload.uploadFiles.length != 0)
// js: document.getElementById("upload").submit()
this.$refs.upload.submit()
else {
alert("文件不可为空")
//重新加载
location.reload()
}
},
//上传成功
fileUploadSuccess(response) {
//提示信息
this.loading = false
this.$message({
type: 'success',
message: '添加课程分类成功'
})
//跳转课程分类列表
//路由跳转
this.$router.push({path:'/subject/list'})
},
//上传失败
fileUploadError() {
this.loading = false
this.$message({
type: 'error',
message: '添加课程分类失败'
})
}
}
}
</script>
总结
读操作:读出excel数据在监听类的invoke函数中进行处理逐行实体类参数并存入数据库等操作
如图所示:easyexcel就是处理excel和数据库之间数据交互,格式转化的
注意 invoke读取excel的时候第一行是默认不读取的(表头不读取)