数据导入导出意义
后台管理系统是管理、处理企业业务数据的重要工具,在这样的系统中,数据的导入和导出功能是非常重要的,其主要意义包括以下几个方面:
1、提高数据操作效率:手动逐条添加或修改数据不仅费时费力,而且容易出错,此时就可以将大量数据从Excel等表格软件中导入到系统中时,通过数据导入功能,可以直接将表格中的数
据批量导入到系统中,提高了数据操作的效率。
2、实现数据备份与迁移:通过数据导出功能,管理员可以将系统中的数据导出为 Excel 或其他格式的文件,以实现数据备份,避免数据丢失。同时,也可以将导出的数据文件用于数据迁
移或其他用途。
3、方便企业内部协作:不同部门可能会使用不同的系统或工具进行数据处理,在这种情况下,通过数据导入和导出功能,可以方便地转换和共享数据,促进企业内部协作。
2.2 EasyExcel简介
官网地址:EasyExcel官方文档 - 基于Java的Excel处理工具 | Easy Excel 官网
EasyExcel 的主要特点如下:
1、高性能:EasyExcel 采用了异步导入导出的方式,并且底层使用 NIO 技术实现,使得其在导入导出大数据量时的性能非常高效。
2、易于使用:EasyExcel 提供了简单易用的 API,用户可以通过少量的代码即可实现复杂的 Excel 导入导出操作。
3、增强的功能“EasyExcel 支持多种格式的 Excel 文件导入导出,同时还提供了诸如合并单元格、数据校验、自定义样式等增强的功能。
4、可扩展性好:EasyExcel 具有良好的扩展性,用户可以通过自定义 Converter 对自定义类型进行转换,或者通过继承 EasyExcelListener 来自定义监听器实现更加灵活的需求。
2.3 入门案例demo
2.3.1 解析Excel数据
需求:对资料中的excel数据进行解析,将其存储到对应的List集合中,并遍历List集合
步骤:
1、在spzx-model的pom.xml文件中添加如下依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.1.0</version>
</dependency>
2、定义一个实体类来封装每一行的数据,如下所示:
package com.atguigu.spzx.manager.test;
import lombok.Data;
/**
* @version: java version 1.8
* @Author: Mr Orange
* @description:
* @date: 2025-02-08 15:15
*/
@Data
public class Student {
private String name;
private int age;
}
3、定义一个监听器,监听解析到的数据,如下所示:
package com.atguigu.spzx.manager.test;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import java.util.ArrayList;
import java.util.List;
/**
* @version: java version 1.8
* @Author: Mr Orange
* @description: 这是一个Excel读取监听器,用于逐行读取学生数据,并在达到一定数量后进行处理(例如,批量插入数据库)。
* @date: 2025-02-08 15:33
*/
// 该类继承自 AnalysisEventListener,泛型为 Student,
// 意味着该监听器关注的是 Student 类型的数据。
public class StudentReadListener extends AnalysisEventListener<Student> {
// 为了存储读取的学生数据,使用 List 集合。
List<Student> list = new ArrayList<>();
// 每读取一行数据时,该方法会被调用。
@Override
public void invoke(Student student, AnalysisContext analysisContext) {
// 将读取的 Student 对象添加到 list 中。
list.add(student);
// 判断当前 list 中的数据量是否超过 100 条。
// 如果超过,则进行处理(例如,批量插入到数据库中)。
if (list.size() > 100) {
// 数据处理的模拟输出,可以根据实际需要进行数据库操作。
System.out.println("当前读取的学生数量达到 100,准备进行数据库插入。");
System.out.println(list); // 输出当前读取的学生数据。
System.out.println("开始插入数据库...");
// 在这里添加你的数据库插入逻辑。
// 清空 list,以便接收后续的数据。
list.clear();
}
}
// 该方法在所有数据读取完后调用,用于处理剩余的数据。
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
// 输出所有已读取的数据。
System.out.println("所有行都读取完毕");
System.out.println(list); // 输出剩余未插入数据库的学生数据。
// 如果还有未处理的数据,进行处理操作(例如,插入数据库)。
if (!list.isEmpty()) {
System.out.println("处理剩余的数据...");
// 在这里添加你的数据库插入逻辑。
list.clear(); // 清空 list。
}
}
}
4、编写测试方法
// 声明使用的包
package com.atguigu.spzx.manager.test;
// 导入EasyExcel库以便处理Excel文件
import com.alibaba.excel.EasyExcel;
// 导入ArrayList和List类
import java.util.ArrayList;
import java.util.List;
/**
* @version: java version 1.8
* @Author: Mr Orange
* @description: 该类用于生成一个包含学生信息的Excel文件
* @date: 2025-02-08 15:15
*/
public class Test {
public static void main(String[] args) {
// 创建一个学生对象列表
List<Student> studentList = new ArrayList<>();
// 使用循环生成10个学生对象并添加到列表中
for (int i = 0; i < 10; i++) {
Student student = new Student(); // 创建一个新的学生对象
student.setAge(i); // 设置学生年龄为当前索引值i
student.setName("张三" + i); // 设置学生姓名为"张三"加上当前索引值i
studentList.add(student); // 将学生对象添加到列表中
}
// 使用EasyExcel库写入Excel文件
EasyExcel.write("D:\\student.xlsx", Student.class) // 指定输出文件路径及数据类型
.sheet("学生信息") // 指定Excel表单名称
.doWrite(studentList); // 将学生列表写入Excel文件
}
}
2.3.2 存储数据到Excel
需求:将如下的集合数据存储到Excel中文件中
List<CategoryExcelVo> list = new ArrayList<>() ; list.add(new CategoryExcelVo(1L , 0L , "数码办公" , 1 , 1)) ; list.add(new CategoryExcelVo(2L , 1L , "手机通讯" , 1 , 1)) ; list.add(new CategoryExcelVo(3L , 2L , "手机" , 1 , 0)) ;
代码实现:
@Test public void saveDataToExcel() { List<CategoryExcelVo> list = new ArrayList<>() ; list.add(new CategoryExcelVo(1L , 0L , "数码办公" , 1 , 1)) ; list.add(new CategoryExcelVo(2L , 1L , "手机通讯" , 1 , 1)) ; list.add(new CategoryExcelVo(3L , 2L , "手机" , 1 , 0)) ; EasyExcel.write("D://分类数据.xlsx" , CategoryExcelVo.class).sheet("分类数据").doWrite(list); }
2.4 导出功能
2.4.1 需求说明
当用户点击导出按钮的时候,此时将数据库中的所有的分类的数据导出到一个excel文件中,如下所示:
2.4.2 后端接口
CategoryController
表现层代码实现:
@Operation(summary = "导出文件")
@GetMapping(value = "/exportData")
public void exportData(HttpServletResponse response) {
categoryService.exportData(response);
}
CategoryService
业务层代码实现:
@SneakyThrows // 自动处理异常,避免手动捕获和处理
@Override // 表示重写父类或接口的方法
public void exportData(HttpServletResponse response) {
// 创建一个列表用于存放要导出的CategoryExcelVo对象
List<CategoryExcelVo> categoryExcelVos = new ArrayList<>();
// 从数据库中查询所有的Category对象
List<Category> categories = categoryMapper.selectAll();
// 将从数据库中查询到的Category对象转换成CategoryExcelVo对象
for (Category category : categories) {
// 创建一个新的CategoryExcelVo对象
CategoryExcelVo categoryExcelVo = new CategoryExcelVo();
// 复制Category对象的属性到CategoryExcelVo对象中
// 第三个参数用来指定忽略属性(这里是CategoryExcelVo.class,通常是传递到这个参数的需要忽略的属性,文中的使用可能不太标准,需按实际情况修改)
BeanUtils.copyProperties(category, categoryExcelVo, CategoryExcelVo.class);
// 将转换后的CategoryExcelVo对象添加到列表中
categoryExcelVos.add(categoryExcelVo);
}
// 使用EasyExcel将数据写入HTTP响应的输出流中
// 设置导出的Excel表格标题为"分类数据"
EasyExcel.write(response.getOutputStream(), CategoryExcelVo.class)
.sheet("分类数据") // 设置工作表名称
.doWrite(categoryExcelVos); // 执行写入操作,将数据写入Excel文件
}
CategoryMapper
持久层代码实现:
public interface CategoryMapper {
List<Category> selectAll();
}
CategoryMapper.xml
在映射文件中添加如下sql语句:
<select id="selectAll" resultMap="categoryMap">
select <include refid="columns" />
from category
where is_deleted = 0
order by id
</select>
2.4.3 前端对接
category.js
在src/api文件夹下创建一个category.js文件,文件的内容如下所示:
// 导出方法
export const ExportCategoryData = () => {
return request({
url: `${api_name}/exportData`,
method: 'get',
responseType: 'blob' // // 这里指定响应类型为blob类型,二进制数据类型,用于表示大量的二进制数据
})
}
category.vue
修改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>
2.5 导入功能
2.5.1 需求说明
当用户点击导入按钮的时候,此时会弹出一个对话框,让用户选择要导入的excel文件,选择完毕以后将文件上传到服务端,服务端通过easyExcel解
析文件的内容,然后将解析的结果存储到category表中。如下所示:
2.5.2 后端接口
CategoryController
表现层代码实现:
@Operation(summary = "导入文件")
@PostMapping("importData")
public Result importData(MultipartFile file) {
categoryService.importData(file);
return Result.ok(null);
}
CategoryService
业务层代码实现:
package com.atguigu.spzx.manager.service.imp;
import com.alibaba.excel.EasyExcel;
import com.atguigu.spzx.manager.listener.CategoryReadListener;
import com.atguigu.spzx.manager.mapper.CategoryMapper;
import com.atguigu.spzx.manager.service.CategoryService;
import com.atguigu.spzx.model.entity.product.Category;
import com.atguigu.spzx.model.vo.product.CategoryExcelVo;
import jakarta.servlet.http.HttpServletResponse;
import lombok.SneakyThrows;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
/**
* @version: java version 1.8
* @Author: Mr Orange
* @description: CategoryServiceImpl是CategoryService接口的实现类,用于处理分类数据的导入。
* @date: 2025-02-08 10:48
*/
@Service // 该注解标记此类为服务层组件,Spring会自动扫描并注册此类为Bean
public class CategoryServiceImpl implements CategoryService {
@Autowired // 自动注入CategoryMapper,用于数据库操作
CategoryMapper categoryMapper;
/**
* 导入分类数据的方法
* @param multipartFile 上传的Excel文件
*/
@Override
@SneakyThrows // 该注解用于隐藏检查异常,简化代码
public void importData(MultipartFile multipartFile) {
// 获取上传文件的输入流
InputStream inputStream = multipartFile.getInputStream();
// 使用EasyExcel库读取Excel文件,指定模板类为CategoryExcelVo和自定义监听器CategoryReadListener
EasyExcel.read(inputStream, CategoryExcelVo.class, new CategoryReadListener(categoryMapper))
.sheet("分类数据") // 指定Excel工作表名称为“分类数据”
.doRead(); // 执行读取操作
}
}
CategoryReadListener (自定义监听器)
package com.atguigu.spzx.manager.listener;
// 导入所需的类
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.atguigu.spzx.manager.mapper.CategoryMapper;
import com.atguigu.spzx.model.vo.product.CategoryExcelVo;
import lombok.NoArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.ArrayList;
import java.util.List;
/**
* @version: java version 1.8
* @Author: Mr Orange
* @description: 此类是用于处理 Excel 文件中的分类数据的监听器
* @date: 2025-02-08 15:59
*/
@NoArgsConstructor
public class CategoryReadListener extends AnalysisEventListener<CategoryExcelVo> {
// 存储读取的分类数据的列表
List<CategoryExcelVo> list = new ArrayList<>();
// 注入的CategoryMapper用于数据库操作
@Autowired
CategoryMapper categoryMapper;
// 构造函数,接收一个CategoryMapper实例
public CategoryReadListener(CategoryMapper categoryMapper) {
this.categoryMapper = categoryMapper;
}
// 处理每一行的解析数据
@Override
public void invoke(CategoryExcelVo categoryExcelVo, AnalysisContext analysisContext) {
// 将解析得到的分类数据添加到列表
list.add(categoryExcelVo);
// 每当列表的大小达到100条时进行批量插入
if (list.size() >= 100) {
System.out.println("批处理,每100条数据插入一次数据库");
System.out.println(list);
// 调用mapper的批量插入方法,将数据插入数据库
categoryMapper.batchInsert(list);
// 清空当前列表以准备下次的插入
list.clear();
}
}
// 所有数据解析完成后调用
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
System.out.println("全部数据读取完毕");
// 打印最后未插入的数据
System.out.println(list);
// 插入剩余的数据
categoryMapper.batchInsert(list);
// 清空列表
list.clear();
}
}
CategoryMapper
持久层代码实现:
public interface CategoryMapper {
void batchInsert(List<CategoryExcelVo> categoryExcelVos);
}
CategoryMapper.xml
映射文件中添加如下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>
2.5.3 前端对接
修改category.vue文件,内容如下所示:
<div class="tools-div">
<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:8503/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>