EasyExcel介绍
EasyExcel是阿里巴巴开源的一个excel处理框架,以使用简单、节省内存著称。EasyExcel能大大减少占用内存的主要原因是在解析Excel时没有将文件数据一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析。
EasyExcel特点
-
Java领域解析、生成Excel比较有名的框架有Apache poi、jxl等。但他们都存在一个严重的问题就是非常的耗内存。如果你的系统并发量不大的话可能还行,但是一旦并发上来后一定会OOM或者JVM频繁的full gc。
-
EasyExcel采用一行一行的解析模式,并将一行的解析结果以观察者的模式通知处理(AnalysisEventListener)
-
EasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目。在尽可能节约内存的情况下支持读写百M的Excel。
开始使用:
1.pom中引入xml相关依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.1.1</version>
</dependency>
先在Java内进行测试
2.创建实体-设置表头和添加的数据字段
这里的ExcelProperty表示在Excle表格中第一行,表头的列名称
@Data
public class Stu {
//设置表头名称
@ExcelProperty("学生编号")
private int sno;
//设置表头名称
@ExcelProperty("学生姓名")
private String sname;
}
3.写入用EasyExcel写入表格数据Excle
package com.atguigu.excel;
import com.alibaba.excel.EasyExcel;
import java.util.ArrayList;
import java.util.List;
public class TestWrite {
public static void main(String[] args) {
//设置文件名称和路径
String fileName="D:\\桌面内容迁移\\测试创建excel.xlsx";
EasyExcel.write(fileName, User.class)
.sheet("写操作")
.doWrite(data());
}
//循环设置要添加的数据,最终封装到list集合中
private static List<User> data() {
List<User> list = new ArrayList<User>();
for (int i = 0; i < 10; i++) {
User user = new User();
user.setId(i+1);
user.setName("张三");
list.add(user);
}
return list;
}
}
4.EasyExcel读取Excle表格数据
先实现一个EasyExcel提供的类AnalysisEventListener
package com.atguigu.excel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.util.ConverterUtils;
import java.util.Map;
public class ExcelListener extends AnalysisEventListener<User> {
//一行一行去读取excel内容并封装到User中
//默认从第二行读取数据,因为第一行一般都是表头
@Override
public void invoke(User user, AnalysisContext context) {
System.out.println(user.toString());
}
//读取表头内容
@Override
public void invokeHead(Map<Integer, CellData> headMap, AnalysisContext context) {
//System.out.println("表头:"+headMap);
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
}
}
5.编写读取测试类并执行
package com.atguigu.excel;
import com.alibaba.excel.EasyExcel;
public class TestRead {
public static void main(String[] args) {
String fileName="D:\\桌面内容迁移\\测试创建excel.xlsx";
EasyExcel.read(fileName, User.class,new ExcelListener())
.sheet()
.doRead();
}
}
与前端页面进行集成
1.编写业务逻辑层与控制器
注:这里使用的是Mybatis-plus
先写两个实体类,第一个是数据库对应的实体类(EasyExcel中要求实体类属性个数与表中表头列的名称个数一致,我们一般实体类还会自己添加一些其他属性,比如说多表联查还需要嵌入一个属性是另一个表的实体类,那么这就不符合EasyExcel的规范,所以要写一个实体类属性与表格完全一致的实体类),第二个是你业务中需要的实体类(可能添加了一些其他的属性值,一般我们开发中都是用的这个,不需要添加)
这里第一个是我重新写的实体类,第二个是原本的实体类,被我自己加了属性,大家可以进行对比
package com.atguigu.ggkt.vo.vod;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
/**
* <p>
* Dict
* </p>
*
* @author qy
*/
@Data
public class SubjectEeVo {
@ExcelProperty(value = "id" ,index = 0)
private Long id;
@ExcelProperty(value = "课程分类名称" ,index = 1)
private String title;
@ExcelProperty(value = "上级id" ,index = 2)
private Long parentId;
@ExcelProperty(value = "排序" ,index = 3)
private Integer sort;
}
package com.atguigu.ggkt.model.vod;
import com.atguigu.ggkt.model.base.BaseEntity;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Data
@ApiModel(description = "Subject")
@TableName("subject")
public class Subject {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "id")
private Long id;
@ApiModelProperty(value = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@TableField("create_time")
private Date createTime;
@ApiModelProperty(value = "更新时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@TableField("update_time")
private Date updateTime;
@ApiModelProperty(value = "逻辑删除(1:已删除,0:未删除)")
@JsonIgnore
@TableLogic
@TableField("is_deleted")
private Integer isDeleted;
@ApiModelProperty(value = "其他参数")
@TableField(exist = false)
private Map<String,Object> param = new HashMap<>();
@ApiModelProperty(value = "类别名称")
@TableField("title")
private String title;
@ApiModelProperty(value = "父ID")
@TableField("parent_id")
private Long parentId;
@ApiModelProperty(value = "排序字段")
@TableField("sort")
private Integer sort;
@ApiModelProperty(value = "是否包含子节点")
@TableField(exist = false)
private boolean hasChildren;
}
2.Service接口与Impl
package com.atguigu.ggkt.vod.service;
import com.atguigu.ggkt.model.vod.Subject;
import com.baomidou.mybatisplus.extension.service.IService;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
/**
* <p>
* 课程科目 服务类
* </p>
*
* @author atguigu
* @since 2023-04-19
*/
public interface SubjectService extends IService<Subject> {
void exportData(HttpServletResponse response);
void importDictData(MultipartFile file);
}
package com.atguigu.ggkt.vod.service.impl;
import com.alibaba.excel.EasyExcel;
import com.atguigu.ggkt.model.vod.Subject;
import com.atguigu.ggkt.vo.vod.SubjectEeVo;
import com.atguigu.ggkt.vod.listener.SubjectListener;
import com.atguigu.ggkt.vod.mapper.SubjectMapper;
import com.atguigu.ggkt.vod.service.SubjectService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
/**
* <p>
* 课程科目 服务实现类
* </p>
*
* @author atguigu
* @since 2023-04-19
*/
@Service
public class SubjectServiceImpl extends ServiceImpl<SubjectMapper, Subject> implements SubjectService {
@Resource
SubjectService subjectService;
@Resource
SubjectListener subjectListener;
//课程分类导出功能
@Override
public void exportData(HttpServletResponse response) {
try{
//设置从数据库下载值的信息
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
// 这里URLEncoder.encode可以防止中文乱码 当然和EasyExcel没有关系
String fileName = URLEncoder.encode("课程分类", "UTF-8");
response.setHeader("Content-disposition", "attachment;filename="+ fileName + ".xlsx");
List<Subject> subjects = subjectService.list();
List<SubjectEeVo> subjectEeVos = new ArrayList<>(subjects.size());
for(Subject dict : subjects) {
SubjectEeVo dictVo = new SubjectEeVo();
// dictVo.setId(dict.getId());
// dictVo.setParentId(dict.getParentId());
// 以上的赋值写法较为复杂,Java提供一个方法让一个对象的属性赋值给另一个对象
BeanUtils.copyProperties(dict,dictVo);
subjectEeVos.add(dictVo);
}
EasyExcel.write(response.getOutputStream(), SubjectEeVo.class)
.sheet("课程分类")
.doWrite(subjectEeVos);
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public void importDictData(MultipartFile file) {
try {
EasyExcel.read(file.getInputStream(),SubjectEeVo.class,subjectListener)
.sheet().doRead();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
3.Controller
没有使用swagger的记得把@ApiOperation注解删掉
package com.atguigu.ggkt.vod.controller;
import com.atguigu.ggkt.model.vod.Subject;
import com.atguigu.ggkt.result.Result;
import com.atguigu.ggkt.vod.service.SubjectService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* <p>
* 课程科目 前端控制器
* </p>
*
* @author atguigu
* @since 2023-04-19
*/
@RestController
@RequestMapping("/admin/vod/subject")
@CrossOrigin(origins = "*")
public class SubjectController {
@Resource
SubjectService subjectService;
@ApiOperation("课程分类导出")
@GetMapping("/exportData")
public void exportData(HttpServletResponse response){
subjectService.exportData(response);
}
@ApiOperation(value = "导入")
@PostMapping("importData")
public Result importData(MultipartFile file) {
subjectService.importDictData(file);
return Result.ok(null);
}
}
4.前端页面
导出按钮与对应的方法
<div class="el-toolbar">
<div class="el-toolbar-body" style="justify-content: flex-start;">
<el-button type="text" @click="exportData"><i class="fa fa-plus"/> 导出</el-button>
</div>
</div>
exportData() {
window.open("http://localhost:8301/admin/vod/subject/exportData")
}
导入按钮、点击按钮弹出的界面、弹出层的data值与对应的导入方法
按钮
<el-button type="text" @click="importData"><i class="fa fa-plus"/> 导入</el-button>
点击对应的弹出层
<el-dialog title="导入" :visible.sync="dialogImportVisible" width="480px">
<el-form label-position="right" label-width="170px">
<el-form-item label="文件">
<el-upload
:multiple="false"
:on-success="onUploadSuccess"
:action="'http://localhost:8333/admin/vod/subject/importData'"
class="upload-demo">
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">只能上传xls文件,且不超过500kb</div>
</el-upload>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogImportVisible = false">取消</el-button>
</div>
</el-dialog>
data中添加弹出层的属性
data() {
return {
dialogImportVisible: false,
list:[] //数据字典列表数组
}
}
添加导入方法
importData() {
this.dialogImportVisible = true
},
onUploadSuccess(response, file) {
this.$message.info('上传成功')
this.dialogImportVisible = false
this.getSubList(0)
},
这里的this.getSubList(0)只是导入数据后调用重新加载页面数据的一个方法,可以自行编写
测试!!
准备好我们要导入到数据库的表格
然后打开树形菜单,可以发现表格中的数据已经被渲染上去了
对service中的sersheet方法和doWrite、doRead方法有疑问的这里解答一下,sersheet是excel表格中底端的那个名称,doRead通过监听器每次读取一行,这也是EasyExcel的优点,一次一行,读完这行再下一行不会全部读取出来,举个极端例子。比如说你内存仅剩100kb了,你表格有500kb,那是不是一下子就蹦了?那读一行完加载到内存中,再下一行就可以很好的解决了这种问题,doWrite就是写入的操作了
以上就是 EasyExcel的读写操作并且与Element-ui进行整合实现导入导出功能