EasyExcel是一个基于Java的、快速、简洁、解决大文件内存溢出的Excel处理工具。他能让你在不用考虑性能、内存的等因素的情况下,快速完成Excel的读、写等功能。
EasyExcel 的主要特点如下:
1、高性能:EasyExcel 采用了异步导入导出的方式,并且底层使用 NIO 技术实现,使得其在导入导出大数据量时的性能非常高效。
2、易于使用:EasyExcel 提供了简单易用的 API,用户可以通过少量的代码即可实现复杂的 Excel 导入导出操作。
3、增强的功能“EasyExcel 支持多种格式的 Excel 文件导入导出,同时还提供了诸如合并单元格、数据校验、自定义样式等增强的功能。
4、可扩展性好:EasyExcel 具有良好的扩展性,用户可以通过自定义 Converter 对自定义类型进行转换,或者通过继承 EasyExcelListener 来自定义监听器实现更加灵活的需求。
EasyExcel官网
1.在pom文件中加入依赖
<dependency>
<groupId>com.alibabagroupId>
<artifactId>easyexcelartifactId>
<version>3.1.0version>
dependency>
2.定义一个实体类来封装每一行的数据
@Data
public class CategoryExcelVo {
@ExcelProperty(value = "id" ,index = 0)
private Long id;
@ExcelProperty(value = "名称" ,index = 1)
private String name;
@ExcelProperty(value = "图片url" ,index = 2)
private String imageUrl ;
@ExcelProperty(value = "上级id" ,index = 3)
private Long parentId;
@ExcelProperty(value = "状态" ,index = 4)
private Integer status;
@ExcelProperty(value = "排序" ,index = 5)
private Integer orderNum;
}
3.定义一个监听器,监听解析到的数据
public class ExcelListener<T> extends AnalysisEventListener<T> {
private List<T> data = new ArrayList<>();
/**
* 读取excel内容 从第二行开始读取,把每行读取内容封装到t对象里
* @param t
* @param analysisContext
*/
@Override
public void invoke(T t, AnalysisContext analysisContext) {
data.add(t);
}
public List<T> getData(){
return data;
}
// excel解析完毕以后需要执行的代码
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
}
}
4.编写测试方法
public class EasyExcelTest {
public static void main(String[] args) {
// read();
write();
}
//读操作
public static void read(){
// 定义读取excel文件为止
String fileName = "E://资料//资料//01.xlsx";
// 调用方法
// 创建一个监听器对象
ExcelListener<CategoryExcelVo> excelListener = new ExcelListener();
// 解析excel表格
EasyExcel.read(fileName, CategoryExcelVo.class,excelListener).sheet().doRead();
List<CategoryExcelVo> data = excelListener.getData();
System.out.println(data);
}
//读操作
public static void write(){
List<CategoryExcelVo> list = new ArrayList<>() ;
list.add(new CategoryExcelVo(1L , "数码办公" , "",0L, 1, 1)) ;
list.add(new CategoryExcelVo(11L , "华为手机" , "",1L, 1, 2)) ;
EasyExcel.write("E://资料//资料//01.xlsx" , CategoryExcelVo.class).sheet("分类数据1").doWrite(list);
}
}
当用户点击导出按钮的时候,此时将数据库中的所有的分类的数据导出到一个excel文件中。
CategoryController
@GetMapping(value = "/exportData")
public void exportData(HttpServletResponse response) {
categoryService.exportData(response);
}
CategoryService
void exportData(HttpServletResponse response);
CategoryServiceImpl
@Override
public void exportData(HttpServletResponse response) {
try {
// 设置响应头信息和其他信息
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
//URLEncoder.encode可以防止中文乱码
String fileName = URLEncoder.encode("分类数据","UTF-8");
// 设置响应头信息
response.setHeader("Content-disposition","attachment;filename="+fileName+".xlsx");
// 调用mapper方法查询所有分类,返回list集合
List<Category> categoryList = categoryMapper.findAll();
List<CategoryExcelVo> categoryExcelVoList = new ArrayList<>();
for(Category category : categoryList){
CategoryExcelVo categoryExcelVo = new CategoryExcelVo();
BeanUtils.copyProperties(category,categoryExcelVo,CategoryExcelVo.class);
categoryExcelVoList.add(categoryExcelVo);
}
// 调用EasyExcel的write方法完成写操作
EasyExcel.write(response.getOutputStream(), CategoryExcelVo.class).sheet("分类数据").doWrite(categoryExcelVoList);
} catch (Exception e){
e.printStackTrace();
throw new GuiguException(ResultCodeEnum.DATA_ERROR);
}
}
CategoryMapper
List<Category> findAll();
CategoryMapper
<select id="findAll" resultMap="categoryMap">
select <include refid="columns"/>
from category
where is_deleted=0
order by id
select>
category.js
// 导出方法
export const ExportCategoryData = () => {
return request({
url: `${api_name}/exportData`,
method: 'get',
responseType: 'blob' // // 这里指定响应类型为blob类型,二进制数据类型,用于表示大量的二进制数据
})
}
category.vue
<div class="tools-div">
<el-button type="success" size="small" @click="exportData">导出el-button>
div>
<script setup>
import { ExportCategoryData} from '@/api/category.js'
const exportData = () => {
// 调用 ExportCategoryData() 方法获取导出数据
ExportCategoryData().then(res => {
// 创建 Blob 对象,用于包含二进制数据
const blob = new Blob([res]);
// 创建 a 标签元素,并将 Blob 对象转换成 URL
const link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
// 设置下载文件的名称
link.download = '分类数据.xlsx';
// 模拟点击下载链接
link.click();
})
}
script>
用户选择要导入的excel文件,选择完毕以后将文件上传到服务端,服务端通过easyExcel解析文件的内容,然后将解析的结果存储到category表中。
CategoryController
/**
* 导入功能
* @param file
* @return
*/
@PostMapping("/importData")
public Result importData(MultipartFile file){
categoryService.importData(file);
return Result.build(null , ResultCodeEnum.SUCCESS) ;
}
CategoryService
void importData(MultipartFile file);
CategoryServiceImpl
@Override
public void importData(MultipartFile file) {
try {
//创建监听器对象,传递mapper对象
ExcelListener<CategoryExcelVo> excelListener = new ExcelListener<>(categoryMapper);
EasyExcel.read(file.getInputStream(),CategoryExcelVo.class,excelListener).sheet().doRead();
} catch (IOException e) {
e.printStackTrace();
throw new GuiguException(ResultCodeEnum.DATA_ERROR);
}
}
CategoryMapper
void batchInsert(List<CategoryExcelVo> categoryList);
CategoryMapper
<insert id="batchInsert" useGeneratedKeys="true" keyProperty="id">
insert into category (
id,
name,
image_url,
parent_id,
status,
order_num,
create_time ,
update_time ,
is_deleted
) values
<foreach collection="categoryList" item="item" separator="," >
(
#{item.id},
#{item.name},
#{item.imageUrl},
#{item.parentId},
#{item.status},
#{item.orderNum},
now(),
now(),
0
)
foreach>
insert>
创建包listener,创建监听器类ExcelListener
/**
* 监听器
*/
public class ExcelListener<T> implements ReadListener<T> {
/**
* 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 100;
/**
* 缓存的数据
*/
private List<T> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
//构造传递
private CategoryMapper categoryMapper;
public ExcelListener(CategoryMapper categoryMapper){
this.categoryMapper = categoryMapper;
}
@Override
public void invoke(T t, AnalysisContext analysisContext) {
cachedDataList.add(t);
// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (cachedDataList.size() >= BATCH_COUNT) {
saveData();
// 存储完成清理 list
cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
// excel解析完毕以后需要执行的代码
// 这里也要保存数据,确保最后遗留的数据也存储到数据库
saveData();
}
private void saveData() {
categoryMapper.batchInsert((List<CategoryExcelVo>)cachedDataList);
}
}
Category.vue
<div class="tools-div">
<el-button type="success" size="small" @click="exportData">导出el-button>
<el-button type="primary" size="small" @click="importData">导入el-button>
div>
<el-dialog v-model="dialogImportVisible" title="导入" width="30%">
<el-form label-width="120px">
<el-form-item label="分类文件">
<el-upload
class="upload-demo"
action="http://localhost:8501/admin/product/category/importData"
:on-success="onUploadSuccess"
:headers="headers"
>
<el-button type="primary">上传el-button>
el-upload>
el-form-item>
el-form>
el-dialog>
<script setup>
import { useApp } from '@/pinia/modules/app'
// 文件上传相关变量以及方法定义
const dialogImportVisible = ref(false)
const headers = {
token: useApp().authorization.token // 从pinia中获取token,在进行文件上传的时候将token设置到请求头中
}
const importData = () => {
dialogImportVisible.value = true
}
// 上传文件成功以后要执行方法
const onUploadSuccess = async (response, file) => {
ElMessage.success('操作成功')
dialogImportVisible.value = false
const { data } = await FindCategoryByParentId(0)
list.value = data ;
}
script>