EasyExcel实现数据导入和导出
数据导入导出意义
后台管理系统是管理、处理企业业务数据的重要工具,在这样的系统中,数据的导入和导出功能是非常重要的,其主要意义包括以下几个方面:
- 提高数据操作效率:手动逐条添加或修改数据不仅费时费力,而且容易出错,此时就可以将大量数据从Excel等表格软件中导入到系统中时,通过数据导入功能,可以直接将表格中的数据批量导入到系统中,提高了数据操作的效率。
- 实现数据备份与迁移:通过数据导出功能,管理员可以将系统中的数据导出为 Excel或其他格式的文件,以实现数据备份,避免数据丢失。同时,也可以将导出的数据文件用于数据迁移或其他用途。
- 方便企业内部协作:不同部门可能会使用不同的系统或工具进行数据处理,在这种情况下,通过数据导入和导出功能,可以方便地转换和共享数据,促进企业内部协作。
EasyExcel简介
EasyExcel 的主要特点如下:
- 高性能:EasyExcel 采用了异步导入导出的方式,并且底层使用 NIO 技术实现,使得其在导入导出大数据量时的性能非常高效。
- 易于使用:EasyExcel 提供了简单易用的 API,用户可以通过少量的代码即可实现复杂的 Excel 导入导出操作。
- 增强的功能“EasyExcel 支持多种格式的 Excel文件导入导出,同时还提供了诸如合并单元格、数据校验、自定义样式等增强的功能。
- 可扩展性好:EasyExcel 具有良好的扩展性,用户可以通过自定义 Converter 对自定义类型进行转换,或者通过继承EasyExcelListener 来自定义监听器实现更加灵活的需求。
把数据导出为Excel
项目中加入依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.1.0</version>
</dependency>
表现层接口
@GetMapping(value = "/exportData")
public void exportData(HttpServletResponse response) {
categoryService.exportData(response);
}
业务层代码
@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");
//response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
// 查询数据库中的数据
List<Category> categoryList = categoryMapper.selectAll();
List<CategoryExcelVo> categoryExcelVoList = new ArrayList<>(categoryList.size());
// 将从数据库中查询到的Category对象转换成CategoryExcelVo对象
for(Category category : categoryList) {
CategoryExcelVo categoryExcelVo = new CategoryExcelVo();
BeanUtils.copyProperties(category, categoryExcelVo, CategoryExcelVo.class);
categoryExcelVoList.add(categoryExcelVo);
}
// 写出数据到浏览器端
EasyExcel.write(response.getOutputStream(), CategoryExcelVo.class).sheet("分类数据").doWrite(categoryExcelVoList);
} catch (IOException e) {
e.printStackTrace();
}
}
持久层代码
@Mapper
public interface CategoryMapper {
public abstract List<Category> selectAll();
}
xml文件的sql语句
<select id="selectAll" resultMap="categoryMap">
select <include refid="columns" />
from category
where is_deleted = 0
order by id
</select>
前段对接
// 导出方法
export const ExportCategoryData = () => {
return request({
url: `${api_name}/exportData`,
method: 'get',
responseType: 'blob' // // 这里指定响应类型为blob类型,二进制数据类型,用于表示大量的二进制数据
})
}
前段vue
<div class="tools-div">
<el-button type="success" size="small" @click="exportData">导出</el-button>
<el-button type="primary" size="small" >导入</el-button>
</div>
<script setup>
import { FindCategoryByParentId , 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>
实现导入功能
表现层接口
@PostMapping("importData")
public Result importData(MultipartFile file) {
categoryService.importData(file);
return Result.build(null , ResultCodeEnum.SUCCESS) ;
}
创建监听器
public class ExcelListener<T> extends AnalysisEventListener<T> {
/**
每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 100;
/**
* 缓存的数据
*/
private List cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
//获取mapper对象
private CategoryMapper categoryMapper;
public ExcelListener(CategoryMapper categoryMapper) {
this.categoryMapper = categoryMapper;
}
// 每解析一行数据就会调用一次该方法
@Override
public void invoke(T o, AnalysisContext analysisContext) {
CategoryExcelVo data = (CategoryExcelVo)o;
cachedDataList.add(data);
// 达到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(cachedDataList);
}
}
业务层代码
@Override
public void importData(MultipartFile file) {
try {
//创建监听器对象,传递mapper对象
ExcelListener<CategoryExcelVo> excelListener = new ExcelListener<>(categoryMapper);
//调用read方法读取excel数据
EasyExcel.read(file.getInputStream(),
CategoryExcelVo.class,
excelListener).sheet().doRead();
} catch (IOException e) {
throw new GuiguException(ResultCodeEnum.DATA_ERROR);
}
}
持久层代码
@Mapper
public interface CategoryMapper {
public abstract void batchInsert(List<Category> categoryList);
}
映射文件中添加如下sql语句:
<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>
前段对接
<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>