Spring?Boot项目如何优雅实现Excel导入与导出功能

news2024/12/30 3:40:27
目录
  • 背景
  • EasyExcel 问题
  • 分析与解决
  • Spring Boot Excel 导入与导出
    • 依赖引入
    • Excel 导入
      • 基本导入功能
      • 进阶导入功能
    • Excel 导出
  • Excel 导入参数校验
    • 开启校验
    • 校验规则定义
      • Bean Validation 定义校验规则
      • ExcelValidator 接口定义校验规则
    • 校验结果接收
      • 异常捕获接收校验结果
      • controller 方法参数接收校验结果
  • 总结

背景

Excel 导入与导出是项目中经常用到的功能,在 Java 中常用 poi 实现 Excel 的导入与导出。由于 poi 占用内存较大,在高并发下很容易发生 OOM 或者频繁 fullgc,阿里基于 poi 开源了 EasyExcel 项目。

除了节约内存,EasyExcel 还简化了 API,通过注解映射 Excel 单元格与对象字段之间的关系,简单的几行代码就能搞定复杂的导入导出功能了。

EasyExcel 问题

看似一切美好,不过经常做 Excel 导入与导出就会发现,EasyExcel 还是没那么完美的。

首先,导入与导出 Excel 本质是上将 Excel 文件内容与 Java 对象之间做一个映射,EasyExcel 做的只是在这两者之间转换。如果项目中的 Excel 导入与导出功能比较多,会产生大量的样板式代码,使用体验类似于 JDBC。

另外,导入往往还伴随着校验,这是 EasyExcel 没有支持的功能。如果需要校验,要么写代码手动判断,要么调用 Java Validation 规范 定义的 API 判断,这又会产生大量样板式代码。

而且,当前 spring boot 已经成了必备的 Java 开发框架,easyexcel 也没有进行整合。

分析与解决

导入与导出通常发生在 Web 环境,对于 Spring MVC 来说,可以将请求信息转换为任意类型的 contoller 方法参数,将 controller 方法返回值转换为客户端支持的内容。

如果能够使用自定义的 controller 方法参数接收 Excel 文件内容,将 controller 方法返回值转换为 Excel 文件响应,可以直接消除 Excel 导入与导出时的样板式代码。

另外在将请求内容转换为 controller 方法参数时还可以加入自定义的校验逻辑。

由于 Excel 导入与导出样板式代码、校验问题与具体的业务逻辑无关,可以单独抽象出来,我这里在 EasyExcel 的基础上封装了一个 easyexcel-spring-boot-starter 的项目,大大降低了 EasyExcel 上手的门槛,对用户来说只需要使用 EasyExcel 定义的注解提供映射关系就可以了,适用于简单场景的导入导出。

项目代码已上传 github easyexcel-spring-boot-starter 仓库,点击链接即可查阅。下面就来看看怎样使用吧。

Spring Boot Excel 导入与导出

依赖引入

首先需要引入依赖,坐标如下。

?

1

2

3

4

5

<dependency>

    <groupId>com.zzuhkp</groupId>

    <artifactId>easyexcel-spring-boot-starter</artifactId>

    <version>1.0-SNAPSHOT</version>

</dependency>

不过很不幸的是目前还没传至中央仓库,需要的小伙伴可自行上传到私有仓库或直接把代码嵌入自己的项目。

Excel 导入

首先看下要导入的 Excel 内容吧。

为了接收 Excel 文件内容,我们需要定义一个对应的 Model 类。

?

1

2

3

4

5

6

7

8

9

10

11

@Data

public class DemoData {

    @ExcelProperty(index = 0)

    private Integer integer;

    @ExcelProperty(index = 1)

    private String string;

    @ExcelProperty(index = 2)

    private Date date;

}

基本导入功能

然后使用 List<T> 参数接收即可。

?

1

2

3

4

@PostMapping("/list/obj")

public List<DemoData> listObj(@ExcelParam List<DemoData> list) {

    return list;

}

注意参数前添加了 @ExcelParam 注解,用来标识 Excel 文件参数。这样,一个导入功能实现了,是不是很简单呢?

默认情况下接收名称为 file 的表单字段作为 Excel 文件,如果不满足还可以修改。

?

1

@ExcelParam(value = "file", required = true)

进阶导入功能

有时候,我们可能比较关心对象对应 Excel 的元数据,例如这个对象是第几行记录产生的,这个对象的字段对应 Excel 第几列,这个时候我们可以使用 ReadRows<T> 参数接收 Excel。

?

1

2

3

4

@PostMapping("/list/rows")

public ReadRows<DemoData> readRows(@ExcelParam ReadRows<DemoData> readRows) {

    return readRows;

}

ReadRows 使用两个字段记录行映射关系与列映射关系。

?

1

2

3

4

5

6

public class ReadRows<T> {

    private ExcelReadHeadProperty excelReadHeadProperty;

    private List<ReadRow<T>> rows;

}

ExcelReadHeadProperty 是 EasyExcel 自带的类,表示列映射关系的元数据。ReadRow 是框架自定义的类,表示行映射关系的元数据。

看下 ReadRow 定义吧。

?

1

2

3

4

5

6

7

8

public class ReadRow<T> {

    // 行索引,从 0 开始

    private final Integer rowIndex;

    // 行记录对应对象

    private final T data;

}

使用 ExcelReadHeadProperty 获取字段对应列索引的示例代码如下。

?

1

2

3

// 对象字段名称 -> 从 0 开始的列索引

Map<String, Integer> fieldColumnIndexMap = readRows.getExcelReadHeadProperty().getHeadMap().values()

        .stream().collect(Collectors.toMap(Head::getFieldName, Head::getColumnIndex));

Excel 导出

这里对 Excel 的导出进行了简单的支持。将 List<T> 定义为 controller 方法返回值即可。

?

1

2

3

4

5

@ExcelResponse

@GetMapping("/list/download")

public List<DemoData> downloadList() {

    return Arrays.asList(new DemoData(1, "hello", new Date()), new DemoData(2, "excel", new Date()));

}

需要注意的是使用 @ExcelResponse 注解表示响应内容为 Excel 文件。默认情况,下载的文件名称为 default.xlxs,写入到名称为 Sheet1 的工作表中。如果不满足需求可以修改。

?

1

@ExcelResponse(fileName = "测试文件", sheetName = "工作表1")

Excel 导入参数校验

参数校验是 Excel 导入常用的功能,这里进行了强有力的支持,使用体验如原生 spring boot 校验般顺滑。

开启校验

与 spring boot 原生使用方式一样,将 @Validated 或 @Valid 注解添加到 @ExcelParam 参数上即可。

?

1

2

3

4

@PostMapping("/list/obj")

public List<DemoData> listObj(@ExcelParam @Validated List<DemoData> list) {

    return list;

}

校验规则定义

Bean Validation 定义校验规则

默认情况下框架使用 JSR-303 Bean Validation 规范定义的校验注解校验,需要手动引入 spring-boot-starter-validation,可通过设置环境变量 easyexcel.validator.default.enable=false 关闭。

?

1

2

3

4

5

6

7

8

9

@Data

public class DemoData {

    @NotNull(message = "参数不能为空")

    private Integer integer;

    private String string;

    private Date date;

}

另外还可以自定义注解对对象校验。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

... 省略其他元注解

@Constraint(validatedBy = {DemoDataValid.DemoDataValidator.class})

public @interface DemoDataValid {

        ... 省略注解属性

         

    class DemoDataValidator implements ConstraintValidator<DemoDataValid, DemoData> {

        @Override

        public boolean isValid(DemoData value, ConstraintValidatorContext context) {

            context.disableDefaultConstraintViolation();

            context.buildConstraintViolationWithTemplate("测试对象校验").addConstraintViolation();

            return false;

        }

    }

}

?

1

2

3

4

@DemoDataValid

public class DemoData {

    ... 省略属性

}

ExcelValidator 接口定义校验规则

Bean Validation 注解只能校验单个字段或对象,如果需要对所有的对象进行校验,可以实现框架定义的 ExcelValidator 接口,然后将实现定义为 Spring Bean。

这个接口定义如下。

?

1

2

3

public interface ExcelValidator<T> {

    ExcelValidErrors validate(ReadRows<T> readRows);

}

ExcelValidErrors 用于接收校验的错误信息,分别使用接口 ExcelValidObjectError 和 ExcelValidFieldError 接口定义行错误信息和单元格错误信息。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

public class ExcelValidErrors {

    // 行错误信息或单元格错误信息列表

    private final List<ExcelValidObjectError> errors;

}

public interface ExcelValidObjectError {

    // 获取行号,从 1 开始

    Integer getRow();

    // 获取错误消息

    String getMessage();

}

public interface ExcelValidFieldError extends ExcelValidObjectError {

    // 获取列,从 1 开始

    Integer getColumn();

}

例如,如果需要对所有的 DemoData 校验 integer 字段的值不能重复,可以使用如下的代码。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

@Component

public class CustomExcelValidator implements ExcelValidator<DemoData> {

    @Override

    public ExcelValidErrors validate(ReadRows<DemoData> readRows) {

        ExcelValidErrors errors = new ExcelValidErrors();

        Map<Integer, List<ReadRow<DemoData>>> group = readRows.getRows().stream()

                .collect(Collectors.groupingBy(item -> item.getData().getInteger()));

        for (Map.Entry<Integer, List<ReadRow<DemoData>>> entry : group.entrySet()) {

            if (entry.getValue().size() > 1) {

                for (ReadRow<DemoData> readRow : entry.getValue()) {

                    errors.addError(new DefaultExcelObjectError(readRow.getRowIndex() + 1, "参数重复"));

                }

            }

        }

        return errors;

    }

}

校验结果接收

与 Spring MVC 设计类似,这里也提供了两种接收校验结果的方式。

异常捕获接收校验结果

开启校验后,如果校验结果中包含错误,会将错误信息封装到 ExcelValidException,并抛出异常,可以通过全局异常捕获的方式收集错误信息。

?

1

2

3

4

5

6

7

8

@RestControllerAdvice

public class GlobalExceptionControllerAdvice {

    @ExceptionHandler(ExcelValidException.class)

    public String handleException(ExcelValidException e) {

        ExcelValidErrors errors = e.getErrors();

        return JSON.toJSONString(errors);

    }

}

controller 方法参数接收校验结果

如果不想通过异常捕获的方式接收校验的错误信息,还可以将错误信息添加到 @ExcelParam 参数的后面,示例代码如下。

?

1

2

3

4

5

6

7

8

@PostMapping("/list/obj")

public List<DemoData> listObj(@ExcelParam @Validated List<DemoData> list, ExcelValidErrors errors) {

    if (errors.hasErrors()) {

        String messages = errors.getAllErrors().stream().map(ExcelValidObjectError::getMessage).collect(Collectors.joining(" | "));

        throw new RuntimeException("发现异常:" + messages);

    }

    return list;

}

总结

easyexcel-spring-boot-starter 综合应用了前面文章介绍的各种 Spring 知识,代码量并不大,对实现感兴趣的小伙伴可自行查阅代码。由于这个框架是把 Excel 中所有的行数据收集到内存,因此只适合一些比较简单的场景。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/825631.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

无涯教程-Lua - 垃圾回收

Lua使用自动内存管理&#xff0c;该管理使用基于Lua内置的某些算法的垃圾回收。 垃圾收集器暂停 垃圾收集器暂停用于控制垃圾收集器之前需要等待多长时间&#xff1b; Lua的自动内存管理再次调用它。值小于100意味着Lua将不等待下一个周期。同样&#xff0c;此值的较高值将导…

《吐血整理》高级系列教程-吃透Fiddler抓包教程(21)-如何使用Fiddler生成Jmeter脚本-上篇

1.简介 我们知道Jmeter本身可以录制脚本&#xff0c;也可以通过BadBoy&#xff0c;BlazeMeter等工具进行录制&#xff0c;其实Fiddler也可以录制Jmter脚本&#xff08;而且有些页面&#xff0c;由于安全设置等原因&#xff0c;使用Jmeter直接无法打开录制时&#xff0c;这时就…

PyQT模块、类、控件介绍

最近在搞一些基于PyQT的开发&#xff0c;开发过程中一直对PyQT相关模块、类、控件比较模糊&#xff0c;于是花了一些力气&#xff0c;去收集和整理了一下PyQT的一些基础&#xff0c;希望对大家有帮助&#xff01; PyQT模块 QtCore模块涵盖了包的核心的非GUI功能&#xff0c;此模…

windows 安装 Mysql 5.7 和8.0

下载链接 https://dev.mysql.com/downloads/mysql/

【echarts饼图】legend显示data中的name和value

效果图&#xff1a; legend自定义显示格式&#xff1a; legend: {formatter: function (name) {let v;optionCheck.series.data.forEach((item) > {if (item.name name) {v item.value;}});return name v;},},全部配置项&#xff1a; const optionCheck reactive(…

Shell脚本学习-MySQL单实例和多实例启动脚本

已知MySQL多实例启动命令为&#xff1a; mysqld_safe --defaults-file/data/3306/my.cnf & 停止命令为&#xff1a; mysqladmin -uroot -pchang123 -S /data/3306/mysql.sock shutdown 请完成mysql多实例的启动脚本的编写&#xff1a; 问题分析&#xff1a; 要想写出脚…

BES 平台 SDK之提示音的添加

本文章是基于BES2700 芯片&#xff0c;其他BESxxx 芯片可做参考&#xff0c;如有不当之处&#xff0c;欢迎评论区留言指出。仅供参考学习用&#xff01; BES 平台 SDK之按键的配置_谢文浩的博客-CSDN博客 关于系统按键简介可参考上一篇文章。链接如上所示&#xff01; 一&am…

工作流管理软件的好处:提升效率、优化流程的利器

一旦您投资了工作流管理系统&#xff0c;就没有回头路了。这是让您的员工满意的完美公式&#xff0c;同时确保所有流程以最高效率和及时完成。事实证明&#xff0c;这些实用的工作流程管理工具对为其客户提供基于知识的专业服务的组织有益&#xff08;在我们的专业服务指南中了…

【3维视觉】3D空间常用算法(点到直线距离、面法线、二面角)

3D空间点到直线的距离 3D空间点到直线的距离 3D空间的曲率 三维空间有三个基本元素&#xff0c;点&#xff0c;线&#xff0c;面。那么曲率是如何定义的呢&#xff1f; 点的曲率&#xff1f; 线的曲率&#xff1f; 面的曲率&#xff1f; 法曲率 设曲面上的曲线在某一点处的切…

【Spring Boot】请求参数传json数组,后端采用(pojo)新增案例(103)

请求参数传json数组&#xff0c;后端采用&#xff08;pojo&#xff09;接收的前提条件&#xff1a; 1.pom.xml文件加入坐标依赖&#xff1a;jackson-databind 2.Spring Boot 的启动类加注解&#xff1a;EnableWebMvc 3.Spring Boot 的Controller接受参数采用&#xff1a;Reque…

第一个 vue-cli 项目

一、什么是 vue-cli vue-cli 官方提供的一个脚手架&#xff0c;用于快速生成一个 vue 的项目模板&#xff1b;预先定义好的目录结构及基础代码&#xff0c;就好比咱们在创建 Maven 项目时可以选择创建一个骨架项目&#xff0c;这个骨架项目就是脚手架&#xff0c;我们的开发更加…

为什么数字孪生和GIS高度互补?它们是如何实现互补的?

在数字化时代&#xff0c;数字孪生和GIS作为两项重要技术&#xff0c;它们的融合正日益受到人们的关注和认可。数字孪生是将实体世界与数字世界紧密结合的技术&#xff0c;可以创建实时的虚拟副本&#xff0c;对物理系统进行模拟、优化和预测。而GIS则是用于收集、管理、分析和…

js将当前时间转换成标准的年月日

直接上代码了&#xff1a; /*** * param e 转换成标准的年月日进行拆分* returns */changeCreationtime(e:any) {let year e.getFullYear(),month (e.getMonth() 1) > 9 ? (e.getMonth() 1) : 0 (e.getMonth() 1),day e.getDate() > 9 ? e.getDate() : 0 e.get…

__block的深入研究

__block可以用于解决block内部无法修改auto变量值的问题 __block不能修饰全局变量、静态变量&#xff08;static&#xff09; 编译器会将__block变量包装成一个对象 调用的是&#xff0c;从__Block_byref_a_0的指针找到 a所在的内存&#xff0c;然后修改值 第一层拷贝&…

VLAN介绍

目录 VLAN的特点: VLAN的好处: VLAN的实现原理 VLAN标签 VLAN的划分方式 接口划分VLAN--接口类型 Access接口 Trunk接口 Hybrid接口 实现VLAN之间通信 使用路由器物理接口 使用子接口 使用三层交换机的VLANIF接口 配置 VLANIF的转发流程 三层交换机参与下的三层…

【图解】Mask R-CNN 架构

Mask R-CNN 是一种自顶向下&#xff08;top-down&#xff09;的姿态估计模型&#xff0c;它是在 Faster R-CNN [44] 这个目标检测框架的基础上扩展而来的。目标检测是指从图像中检测出不同类别的物体&#xff0c;并且输出它们的边界框&#xff08;bounding box&#xff09;。 …

exp/imp选项说明

1、exp选项 2、imp选项 3、举例 (1)、imp system/manager filetank logtank fromuser(seapark,amy) touser(seapark1, amy1);(2)、imp system/manager file(paycheck_1,paycheck_2,paycheck_3,paycheck_4) logpaycheck.log filesize1G fully;(3)、imp system/manager fileseap…

【css】解决元素浮动溢出问题

如果一个元素比包含它的元素高&#xff0c;并且它是浮动的&#xff0c;它将“溢出”到其容器之外&#xff1a;然后可以向包含元素添加 overflow: auto;&#xff0c;来解决此问题&#xff1a; 代码&#xff1a; <!DOCTYPE html> <html> <head> <style>…

如何克服学习和工作中的焦虑和迷茫

如何克服学习和工作中的焦虑和迷茫 &#x1f607;博主简介&#xff1a;我是一名正在攻读研究生学位的人工智能专业学生&#xff0c;我可以为计算机、人工智能相关本科生和研究生提供排忧解惑的服务。如果您有任何问题或困惑&#xff0c;欢迎随时来交流哦&#xff01;&#x1f6…

落实《中国人民银行业务领域数据安全管理办法》,极盾科技是怎么做的?

“软标准”变成“硬规范”&#xff01; 近日&#xff0c;央行发布《中国人民银行业务领域数据安全管理办法》征求意见稿&#xff08;以下称《管理办法》&#xff09;&#xff0c;以部门规范性文件的方式&#xff0c;全面衔接《数据安全法》&#xff0c;细化明确中国人民银行业…