EasyExcel 介绍
简介
Excel导入导出的应用场景
1、数据导入:减轻录入工作量
2、数据导出:统计信息归档
3、数据传输:异构系统之间数据传输
EasyExcel的特点
- Java 领域解析、生成 Excel 比较有名的框架有 Apache poi、jxl 等。但他们都存在一个严重的问题就是非常的耗内存。如果你的系统并发量不大的话可能还行,但是一旦并发上来后一定会 OOM 或者 JVM 频繁的 full gc。
- EasyExcel 是阿里巴巴开源的一个 excel 处理框架,以使用简单、节省内存著称。EasyExcel 能大大减少占用内存的主要原因是在解析 Excel 时没有将文件数据一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析。
- EasyExcel 采用一行一行的解析模式,并将一行的解析结果以观察者的模式通知处理 (AnalysisEventListener)。
EasyExcel 读写操作案例
1、创建项目引入依赖
在service模块下创建 easy_excel_demo 模块,并在其 pom 文件中引入依赖:
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.1.1</version>
</dependency>
</dependencies>
2、创建实体类
@Data
public class DemoData {
// 设置表头名称
@ExcelProperty(value = "学生编号",index = 0)
private Integer sno;
@ExcelProperty(value = "学生姓名",index = 1)
private String sname;
}
3、实现写操作
创建类 TestEasyExcel,创建方法循环设置要添加到 Excel 的数据
public class TestEasyExcel {
public static void main(String[] args) {
// 实现Excel写的操作
// 1、设置写入文件夹地址和excel文件名
String filename = "/gulixy/write.xlsx";
// 2、调用easyexcel里面的方法实现写操作
// write方法两个参数,第一个参数文件路径名称,第二个参数实体类class
EasyExcel.write(filename, DemoData.class).sheet("学生列表").doWrite(getData());
}
// 创建方法返回list集合
private static List<DemoData> getData() {
List<DemoData> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
DemoData data = new DemoData();
data.setSno(i);
data.setSname("lucy"+i);
list.add(data);
}
return list;
}
}
运行main方法,结果:
4、实现读操作
(1)创建监听器
public class ExcelListener extends AnalysisEventListener<DemoData> {
// 一行一行读取Excel内容
@Override
public void invoke(DemoData data, AnalysisContext analysisContext) {
System.out.println("****" + data);
}
// 读取表头内容
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
System.out.println("表头:" + headMap);
}
// 读取完成之后
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {}
}
(2)读取操作
//main方法里面添加:
EasyExcel.read(filename, DemoData.class, new ExcelListener()).sheet().doRead();
课程分类功能
后端实现
1、引入依赖与代码生成器
在 service_edu 模块中引入依赖
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.1.1</version>
</dependency>
</dependencies>
将之前用过的代码生成器换一下表名为edu_subject即可。
注意添加自动创建时间的注解:
2、创建实体类
entity下建立excel包,然后创建类:
@Data
public class SubjectData {
@ExcelProperty(index = 0)
private String oneSubjectName;
@ExcelProperty(index = 1)
private String twoSubjectName;
}
3、controller层
@Api(description = "课程分类管理")
@RestController
@RequestMapping("/eduservice/subject")
@CrossOrigin
public class EduSubjectController {
@Autowired
private EduSubjectService subjectService;
// 添加课程分类
// 获取上传过来的文件,把文件内容读取出来
@ApiOperation(value = "Excel导入")
@PostMapping("addSubject")
public R addSubject(MultipartFile file) {
// 上传过来Excel文件
subjectService.saveSubject(file, subjectService);
return R.ok();
}
}
4、service层
(1)接口
public interface EduSubjectService extends IService<EduSubject> {
// 添加课程分类文件
void saveSubject(MultipartFile file, EduSubjectService subjectService);
}
(2)实现类
@Service
public class EduSubjectServiceImpl extends ServiceImpl<EduSubjectMapper, EduSubject> implements EduSubjectService {
@Override
public void saveSubject(MultipartFile file, EduSubjectService subjectService) {
try {
// 获取文件输入流
InputStream is = file.getInputStream();
// 调用方法进行读取
EasyExcel.read(is, SubjectData.class, new SubjectExcelListener(subjectService)).sheet().doRead();
} catch (Exception e) {
e.printStackTrace();
throw new GuliException(20001, "添加课程分类失败");
}
}
}
5、创建监听器
创建包 listener,然后创建类:SubjectExcelListener
public class SubjectExcelListener extends AnalysisEventListener<SubjectData> {
// SubjectExcelListener不能交给spring管理,需要手动new
// 提供有参和无参的构造器
private EduSubjectService subjectService;
public SubjectExcelListener() {
}
public SubjectExcelListener(EduSubjectService subjectService) {
this.subjectService = subjectService;
}
// 读取Excel文件内容,一行一行的读取
@Override
public void invoke(SubjectData subjectData, AnalysisContext analysisContext) {
if (subjectData == null) {
throw new GuliException(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) {}
}
6、Swagger 测试
先准备一张表:
课程分类.xlsx
启动 EduApplication,访问 http://localhost:8001/swagger-ui.html,点击选择文件,添加刚刚制作的表格:
查看数据库:
前端实现
1、添加路由
{
path: '/subject',
component: Layout,
redirect: '/subject/list',
name: '课程分类管理',
meta: { title: '课程分类管理', icon: 'example' },
children: [
{
path: 'list',
name: '课程分类列表',
component: () => import('@/views/edu/subject/list'),
meta: { title: '课程分类列表', icon: 'table' }
},
{
path: 'save',
name: '添加课程分类',
component: () => import('@/views/edu/subject/save'),
meta: { title: '添加课程分类', icon: 'tree' }
}
]
},
2、添加组件
在 views/edu 目录下新建 subject 目录,然后创建 list.vue 和 save.vue,然后在 save.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="xls,xlsx,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,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
// js: document.getElementById('upload').submit()
this.$refs.upload.submit()
},
// 上传成功
fileUploadSuccess() {
// 提示信息
this.loading = false
this.$message({
type: 'success',
message: '添加课程分类成功'
})
// 跳转课程分类列表
// 路由跳转
this.$router.push({ path: '/subject/list' })
},
// 上传失败
fileUploadError() {
// 提示信息
this.loading = false
this.$message({
type: 'error',
message: '添加课程分类失败'
})
}
}
}
</script>
3、测试
先清掉数据表 edu_subject 里面的数据,然后启动前后端项目,先点击选取文件,再点击上传到服务器:
成功:
课程列表功能
后端实现
1、创建实体类
在 entity 包下建立 subject 包,然后创建两个类:
// 一级分类
@Data
public class OneSubject {
private String id;
private String title;
// 一个一级分类有多个二级分类
private List<TwoSubject> children = new ArrayList<>();
}
// 二级分类
@Data
public class TwoSubject {
private String id;
private String title;
}
2、controller 层
在 EduSubjectController 类中添加方法:
// 课程分类列表(树形)
@ApiOperation(value = "获取课程分类列表")
@GetMapping("getAllSubject")
public R getAllSubject() {
// list集合泛型是一级分类
List<OneSubject> list = subjectService.getAllOneTwoSubject();
return R.ok().data("list", list);
}
3、service 层
在EduSubjectService接口中增加方法:
// 课程分类列表(树形)
List<OneSubject> getAllOneTwoSubject();
在EduSubjectServiceImpl类中增加方法:
// 课程分类列表(树形)
@Override
public List<OneSubject> getAllOneTwoSubject() {
// 1、查询所有一级分类
QueryWrapper<EduSubject> wrapperOne = new QueryWrapper<>();
wrapperOne.eq("parent_id", "0");
List<EduSubject> oneSubjectList = baseMapper.selectList(wrapperOne);
// 2、查询所有二级分类
QueryWrapper<EduSubject> wrapperTwo = new QueryWrapper<>();
wrapperTwo.ne("parent_id", "0");
List<EduSubject> twoSubjectList = baseMapper.selectList(wrapperTwo);
// 3、创建list集合,用于存储最终封装数据
List<OneSubject> finalSubjectList = new ArrayList<>();
// 4、封装一级分类
for (int i = 0; i < oneSubjectList.size(); i++) {
EduSubject eduSubject = oneSubjectList.get(i);
OneSubject oneSubject = new OneSubject();
// oneSubject.setId(eduSubject.getId());
// oneSubject.setTitle(eduSubject.getTitle());
// 快速封装属性,相当于上面两行的内容
BeanUtils.copyProperties(eduSubject, oneSubject);//注意这里是Spring包下的BeanUtils工具类
finalSubjectList.add(oneSubject);
// 5、在一级分类循环遍历查询所有的二级分类
List<TwoSubject> twoFinalSubjectList = new ArrayList<>();
for (int j = 0; j < twoSubjectList.size(); j++) {
EduSubject subject = twoSubjectList.get(j);
if (subject.getParentId().equals(eduSubject.getId())) {
TwoSubject twoSubject = new TwoSubject();
BeanUtils.copyProperties(subject, twoSubject);
twoFinalSubjectList.add(twoSubject);
}
}
// 把一级下面所有二级分类放到一级分类里面
oneSubject.setChildren(twoFinalSubjectList);
}
return finalSubjectList;
}
4、Swagger 测试
启动项目,访问:http://localhost:8001/swagger-ui.html
点击 Try it out!,成功:
前端实现
1、js
在src/api/edu目录下建立subject.js:
import request from '@/utils/request'
export default {
// 课程分类列表
getSubjectList() {
return request({
url: '/eduservice/subject/getAllSubject',
method: 'get'
})
}
}
2、vue
views/edu/subject/list.vue文件:
<template>
<div class="app-container">
<el-input v-model="filterText" placeholder="Filter keyword" style="margin-bottom:30px;" />
<el-tree
ref="tree2"
:data="data2"
:props="defaultProps"
:filter-node-method="filterNode"
class="filter-tree"
default-expand-all
/>
</div>
</template>
<script>
import subject from '@/api/edu/subject'
export default {
data() {
return {
filterText: '',
data2: [], // 返回所有分类数据
defaultProps: {
children: 'children',
label: 'title'
}
}
},
watch: {
filterText(val) {
this.$refs.tree2.filter(val)
}
},
created() {
this.getAllSubjectList()
},
methods: {
getAllSubjectList() {
subject
.getSubjectList()
.then(response => {
this.data2 = response.data.list
})
},
filterNode(value, data) {
if (!value) return true
return data.title.toLowerCase().indexOf(value.toLowerCase()) !== -1
}
}
}
</script>
效果:
输入数据还可以自动检索: