Spring Boot 集成 EasyExcel 3.x 优雅实现Excel导入导出

这篇具有很好参考价值的文章主要介绍了Spring Boot 集成 EasyExcel 3.x 优雅实现Excel导入导出。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Spring Boot 集成 EasyExcel 3.x

本章节将介绍 Spring Boot 集成 EasyExcel(优雅实现Excel导入导出)。

🤖 Spring Boot 2.x 实践案例(代码仓库)

介绍

EasyExcel 是一个基于 Java 的、快速、简洁、解决大文件内存溢出的 Excel 处理工具。它能让你在不用考虑性能、内存的等因素的情况下,快速完成 Excel 的读、写等功能。

EasyExcel文档地址:https://easyexcel.opensource.alibaba.com/

快速开始

引入依赖

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.1.3</version>
</dependency>

简单导出

以导出用户信息为例,接下来手把手教大家如何使用EasyExcel实现导出功能!

定义实体类

在EasyExcel中,以面向对象思想来实现导入导出,无论是导入数据还是导出数据都可以想象成具体某个对象的集合,所以为了实现导出用户信息功能,首先创建一个用户对象UserDO实体类,用于封装用户信息:

/**
 * 用户信息
 *
 * @author william@StarImmortal
 */
@Data
public class UserDO {
    @ExcelProperty("用户编号")
    @ColumnWidth(20)
    private Long id;

    @ExcelProperty("用户名")
    @ColumnWidth(20)
    private String username;

    @ExcelIgnore
    private String password;

    @ExcelProperty("昵称")
    @ColumnWidth(20)
    private String nickname;

    @ExcelProperty("生日")
    @ColumnWidth(20)
    @DateTimeFormat("yyyy-MM-dd")
    private Date birthday;

    @ExcelProperty("手机号")
    @ColumnWidth(20)
    private String phone;

    @ExcelProperty("身高(米)")
    @NumberFormat("#.##")
    @ColumnWidth(20)
    private Double height;

    @ExcelProperty(value = "性别", converter = GenderConverter.class)
    @ColumnWidth(10)
    private Integer gender;
}

上面代码中类属性上使用了EasyExcel核心注解:

  • @ExcelProperty:核心注解,value属性可用来设置表头名称,converter属性可以用来设置类型转换器;
  • @ColumnWidth:用于设置表格列的宽度;
  • @DateTimeFormat:用于设置日期转换格式;
  • @NumberFormat:用于设置数字转换格式。
自定义转换器

在EasyExcel中,如果想实现枚举类型到字符串类型转换(例如gender属性:1 -> 男2 -> 女),需实现Converter接口来自定义转换器,下面为自定义GenderConverter性别转换器代码实现:

/**
 * Excel 性别转换器
 *
 * @author william@StarImmortal
 */
public class GenderConverter implements Converter<Integer> {
    @Override
    public Class<?> supportJavaTypeKey() {
        return Integer.class;
    }

    @Override
    public CellDataTypeEnum supportExcelTypeKey() {
        return CellDataTypeEnum.STRING;
    }

    @Override
    public Integer convertToJavaData(ReadConverterContext<?> context) {
        return GenderEnum.convert(context.getReadCellData().getStringValue()).getValue();
    }

    @Override
    public WriteCellData<?> convertToExcelData(WriteConverterContext<Integer> context) {
        return new WriteCellData<>(GenderEnum.convert(context.getValue()).getDescription());
    }
}
/**
 * 性别枚举
 *
 * @author william@StarImmortal
 */
@Getter
@AllArgsConstructor
public enum GenderEnum {

    /**
     * 未知
     */
    UNKNOWN(0, "未知"),

    /**
     * 男性
     */
    MALE(1, "男性"),

    /**
     * 女性
     */
    FEMALE(2, "女性");

    private final Integer value;

    @JsonFormat
    private final String description;

    public static GenderEnum convert(Integer value) {
        return Stream.of(values())
                .filter(bean -> bean.value.equals(value))
                .findAny()
                .orElse(UNKNOWN);
    }

    public static GenderEnum convert(String description) {
        return Stream.of(values())
                .filter(bean -> bean.description.equals(description))
                .findAny()
                .orElse(UNKNOWN);
    }
}
定义接口
/**
 * EasyExcel导入导出
 *
 * @author william@StarImmortal
 */
@RestController
@RequestMapping("/excel")
public class ExcelController {

    @GetMapping("/export/user")
    public void exportUserExcel(HttpServletResponse response) {
        try {
            this.setExcelResponseProp(response, "用户列表");
            List<UserDO> userList = this.getUserList();
            EasyExcel.write(response.getOutputStream())
                    .head(UserDO.class)
                    .excelType(ExcelTypeEnum.XLSX)
                    .sheet("用户列表")
                    .doWrite(userList);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 设置响应结果
     *
     * @param response    响应结果对象
     * @param rawFileName 文件名
     * @throws UnsupportedEncodingException 不支持编码异常
     */
    private void setExcelResponseProp(HttpServletResponse response, String rawFileName) throws UnsupportedEncodingException {
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        String fileName = URLEncoder.encode(rawFileName, "UTF-8").replaceAll("\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
    }
    
    /**
     * 读取用户列表数据
     *
     * @return 用户列表数据
     * @throws IOException IO异常
     */
    private List<UserDO> getUserList() throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        ClassPathResource classPathResource = new ClassPathResource("mock/users.json");
        InputStream inputStream = classPathResource.getInputStream();
        return objectMapper.readValue(inputStream, new TypeReference<List<UserDO>>() {
        });
    }
}
测试接口

运行项目,通过 Postman 或者 Apifox 工具来进行接口测试

注意:在 Apifox 中访问接口后无法直接下载,需要点击返回结果中的下载图标才行,点击之后方可对Excel文件进行保存。

接口地址:http://localhost:8080/excel/export/user

easyexcel3,后端笔记,spring boot,excel,java
easyexcel3,后端笔记,spring boot,excel,java

复杂导出

由于 EasyPoi 支持嵌套对象导出,直接使用内置 @ExcelCollection 注解即可实现,遗憾的是 EasyExcel 不支持一对多导出,只能自行实现,通过此issues了解到,项目维护者建议通过自定义合并策略方式来实现一对多导出。

easyexcel3,后端笔记,spring boot,excel,java

解决思路:只需把订单主键相同的列中需要合并的列给合并了,就可以实现这种一对多嵌套信息的导出

自定义注解

创建一个自定义注解,用于标记哪些属性需要合并单元格,哪个属性是主键:

/**
 * 用于判断是否需要合并以及合并的主键
 *
 * @author william@StarImmortal
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExcelMerge {
    /**
     * 是否合并单元格
     *
     * @return true || false
     */
    boolean merge() default true;

    /**
     * 是否为主键(即该字段相同的行合并)
     *
     * @return true || false
     */
    boolean isPrimaryKey() default false;
}
定义实体类

在需要合并单元格的属性上设置 @ExcelMerge 注解,二级表头通过设置 @ExcelProperty 注解中 value 值为数组形式来实现该效果:

/**
 * @author william@StarImmortal
 */
@Data
public class OrderBO {
    @ExcelProperty(value = "订单主键")
    @ColumnWidth(16)
    @ExcelMerge(merge = true, isPrimaryKey = true)
    private String id;

    @ExcelProperty(value = "订单编号")
    @ColumnWidth(20)
    @ExcelMerge(merge = true)
    private String orderId;

    @ExcelProperty(value = "收货地址")
    @ExcelMerge(merge = true)
    @ColumnWidth(20)
    private String address;

    @ExcelProperty(value = "创建时间")
    @ColumnWidth(20)
    @DateTimeFormat("yyyy-MM-dd HH:mm:ss")
    @ExcelMerge(merge = true)
    private Date createTime;

    @ExcelProperty(value = {"商品信息", "商品编号"})
    @ColumnWidth(20)
    private String productId;

    @ExcelProperty(value = {"商品信息", "商品名称"})
    @ColumnWidth(20)
    private String name;

    @ExcelProperty(value = {"商品信息", "商品标题"})
    @ColumnWidth(30)
    private String subtitle;

    @ExcelProperty(value = {"商品信息", "品牌名称"})
    @ColumnWidth(20)
    private String brandName;

    @ExcelProperty(value = {"商品信息", "商品价格"})
    @ColumnWidth(20)
    private BigDecimal price;

    @ExcelProperty(value = {"商品信息", "商品数量"})
    @ColumnWidth(20)
    private Integer count;
}
数据映射与平铺

导出之前,需要对数据进行处理,将订单数据进行平铺,orderList为平铺前格式,exportData为平铺后格式:

easyexcel3,后端笔记,spring boot,excel,java

自定义单元格合并策略

当 Excel 中两列主键相同时,合并被标记需要合并的列:

/**
 * 自定义单元格合并策略
 *
 * @author william@StarImmortal
 */
public class ExcelMergeStrategy implements RowWriteHandler {

    /**
     * 主键下标
     */
    private Integer primaryKeyIndex;

    /**
     * 需要合并的列的下标集合
     */
    private final List<Integer> mergeColumnIndexList = new ArrayList<>();

    /**
     * 数据类型
     */
    private final Class<?> elementType;

    public ExcelMergeStrategy(Class<?> elementType) {
        this.elementType = elementType;
    }

    @Override
    public void afterRowDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Integer relativeRowIndex, Boolean isHead) {
        // 判断是否为标题
        if (isHead) {
            return;
        }
        // 获取当前工作表
        Sheet sheet = writeSheetHolder.getSheet();
        // 初始化主键下标和需要合并字段的下标
        if (primaryKeyIndex == null) {
            this.initPrimaryIndexAndMergeIndex(writeSheetHolder);
        }
        // 判断是否需要和上一行进行合并
        // 不能和标题合并,只能数据行之间合并
        if (row.getRowNum() <= 1) {
            return;
        }
        // 获取上一行数据
        Row lastRow = sheet.getRow(row.getRowNum() - 1);
        // 将本行和上一行是同一类型的数据(通过主键字段进行判断),则需要合并
        if (lastRow.getCell(primaryKeyIndex).getStringCellValue().equalsIgnoreCase(row.getCell(primaryKeyIndex).getStringCellValue())) {
            for (Integer mergeIndex : mergeColumnIndexList) {
                CellRangeAddress cellRangeAddress = new CellRangeAddress(row.getRowNum() - 1, row.getRowNum(), mergeIndex, mergeIndex);
                sheet.addMergedRegionUnsafe(cellRangeAddress);
            }
        }
    }

    /**
     * 初始化主键下标和需要合并字段的下标
     *
     * @param writeSheetHolder WriteSheetHolder
     */
    private void initPrimaryIndexAndMergeIndex(WriteSheetHolder writeSheetHolder) {
        // 获取当前工作表
        Sheet sheet = writeSheetHolder.getSheet();
        // 获取标题行
        Row titleRow = sheet.getRow(0);
        // 获取所有属性字段
        Field[] fields = this.elementType.getDeclaredFields();
        // 遍历所有字段
        for (Field field : fields) {
            // 获取@ExcelProperty注解,用于获取该字段对应列的下标
            ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
            // 判断是否为空
            if (null == excelProperty) {
                continue;
            }
            // 获取自定义注解,用于合并单元格
            ExcelMerge excelMerge = field.getAnnotation(ExcelMerge.class);
            // 判断是否需要合并
            if (null == excelMerge) {
                continue;
            }
            for (int i = 0; i < fields.length; i++) {
                Cell cell = titleRow.getCell(i);
                if (null == cell) {
                    continue;
                }
                // 将字段和表头匹配上
                if (excelProperty.value()[0].equalsIgnoreCase(cell.getStringCellValue())) {
                    if (excelMerge.isPrimaryKey()) {
                        primaryKeyIndex = i;
                    }
                    if (excelMerge.merge()) {
                        mergeColumnIndexList.add(i);
                    }
                }
            }
        }

        // 没有指定主键,则异常
        if (null == this.primaryKeyIndex) {
            throw new IllegalStateException("使用@ExcelMerge注解必须指定主键");
        }
    }
}
定义接口

将自定义合并策略 ExcelMergeStrategy 通过 registerWriteHandler 注册上去:

/**
 * EasyExcel导入导出
 *
 * @author william@StarImmortal
 */
@RestController
@RequestMapping("/excel")
public class ExcelController {

    @GetMapping("/export/order")
    public void exportOrderExcel(HttpServletResponse response) {
        try {
            this.setExcelResponseProp(response, "订单列表");
            List<OrderDO> orderList = this.getOrderList();
            List<OrderBO> exportData = this.convert(orderList);
            EasyExcel.write(response.getOutputStream())
                    .head(OrderBO.class)
                    .registerWriteHandler(new ExcelMergeStrategy(OrderBO.class))
                    .excelType(ExcelTypeEnum.XLSX)
                    .sheet("订单列表")
                    .doWrite(exportData);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 设置响应结果
     *
     * @param response    响应结果对象
     * @param rawFileName 文件名
     * @throws UnsupportedEncodingException 不支持编码异常
     */
    private void setExcelResponseProp(HttpServletResponse response, String rawFileName) throws UnsupportedEncodingException {
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        String fileName = URLEncoder.encode(rawFileName, "UTF-8").replaceAll("\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
    }
}
测试接口

运行项目,通过 Postman 或者 Apifox 工具来进行接口测试

注意:在 Apifox 中访问接口后无法直接下载,需要点击返回结果中的下载图标才行,点击之后方可对Excel文件进行保存。

接口地址:http://localhost:8080/excel/export/order

easyexcel3,后端笔记,spring boot,excel,java
easyexcel3,后端笔记,spring boot,excel,java

简单导入

以导入用户信息为例,接下来手把手教大家如何使用EasyExcel实现导入功能!

/**
 * EasyExcel导入导出
 *
 * @author william@StarImmortal
 */
@RestController
@RequestMapping("/excel")
@Api(tags = "EasyExcel")
public class ExcelController {
    
    @PostMapping("/import/user")
    public ResponseVO importUserExcel(@RequestPart(value = "file") MultipartFile file) {
        try {
            List<UserDO> userList = EasyExcel.read(file.getInputStream())
                    .head(UserDO.class)
                    .sheet()
                    .doReadSync();
            return ResponseVO.success(userList);
        } catch (IOException e) {
            return ResponseVO.error();
        }
    }
}

easyexcel3,后端笔记,spring boot,excel,java文章来源地址https://www.toymoban.com/news/detail-780210.html

参考资料

  • 项目地址:https://github.com/alibaba/easyexcel
  • 官方文档:https://www.yuque.com/easyexcel/doc/easyexcel
  • 一对多导出优雅方案:https://github.com/alibaba/easyexcel/issues/1780

到了这里,关于Spring Boot 集成 EasyExcel 3.x 优雅实现Excel导入导出的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 使用Spring Boot和EasyExcel的导入导出

    在当今信息化社会,数据的导入和导出在各种业务场景中变得越来越重要。为了满足复杂的导入导出需求,结合Java编程语言、Spring Boot框架以及EasyExcel库,我们可以轻松地构建出强大而灵活的数据处理系统。本文将引导您通过一个案例学习如何使用这些工具,实现一个复杂的

    2024年02月14日
    浏览(38)
  • EasyExcel实现Excel文件导入导出功能

    Java领域解析、生成Excel比较有名的框架有Apache poi、jxl等。但他们都存在一个严重的问题就是非常的耗内存。如果你的系统并发量不大的话可能还行,但是一旦并发上来后一定会OOM或者JVM频繁的full gc。 EasyExcel是阿里巴巴开源的一个excel处理框架,以使用简单、节省内存著称。

    2024年02月02日
    浏览(68)
  • 使用EasyExcel实现Excel的导入导出

    在真实的开发者场景中,经常会使用excel作为数据的载体,进行数据导入和导出的操作,使用excel的导入和导出有很多种解决方案,本篇记录一下EasyExcel的使用。 EasyExcel是一个开源的项目,是阿里开发的。EasyExcel可以简化Excel表格的导入和导出操作,使用起来简单快捷,易上手

    2023年04月15日
    浏览(51)
  • 使用EasyExcel实现Excel表格的导入导出

    Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。 easyexcel重

    2024年02月12日
    浏览(46)
  • springboot中使用EasyExcel实现Excel 导入导出

    EasyExcel 是一款基于 Java 的简单易用的 Excel 文件操作工具。它提供了丰富的 API,可以方便地读取、写入和操作 Excel 文件,支持常见的 Excel 操作,如读取/写入单元格数据、合并单元格、设置样式、处理大数据量等。EasyExcel能大大减少占用内存的主要原因是在解析Excel时没有将

    2024年02月12日
    浏览(67)
  • 使用POI和EasyExcel来实现excel文件的导入导出

    废话不多说咱们直接上干货!!!! 一.读取Excel表格 【1】使用POI读取excel表格中的数据 POI还可以操作我们这个word文档等等,他不仅仅只能弄Excel,而JXI只能操作excel 1.POI的结构,我们可以更具文件的类去选择 相关的对象我当前是使用的XLSX来操作的 HSSF - 提供读写Microsoft

    2024年02月05日
    浏览(59)
  • SpringBoot 集成 EasyExcel 3.x 实现 Excel 导出

    目录   EasyExcel官方文档 EasyExcel是什么? EasyExcel注解  springboot集成EasyExcel 简单入门导出 : 实体类  自定义转换类 测试一下 复杂表头一对多导出 :   自定义注解 定义实体类 自定义单元格合并策略  测试一下     EasyExcel官方文档 EasyExcel官方文档 - 基于Java的Excel处理工具

    2024年02月13日
    浏览(56)
  • Spring boot easyexcel 实现复合数据导出、按模块导出

    场景: 导出数据为1对多的复合数据 一个模块是一条数据,直接填充数据无法实现 如图: 红框内为一条数据(1对多),下方箭头指向为第二条数据 如果直接填充,只能填充第一条,第二条就没办法了。 由于多行都包含许多,固定表头,只能走填充路线,怎么实现呢 实现思路

    2024年02月07日
    浏览(43)
  • Spring Boot 引入 easyexcel 最新版本 3.3.2,实现读写 Excel

    EasyExcel是一个基于Java的、快速、简洁、解决大文件内存溢出的Excel处理工具。 他能让你在不用考虑性能、内存的等因素的情况下,快速完成Excel的读、写等功能 在 Spring Boot 环境中使用 easyexcel,需要完成以下几个步骤 pom.xml中引入easyexcel的依赖,此处版本号3.3.2 建立一个实体

    2024年02月11日
    浏览(35)
  • EasyExcel导出带下拉选数据的Excel数据导入模板

    #因为项目中需要导入一些信息,但是这些信息比较不常见,且在项目字典数据中维护有这些数据,所以在导出模板的时候,把这些数据一并导出,可以减少用户的编写,避免在导入的时候因为数据错误,发生一些业务问题 直接开始 1、以岗位类型为例,展示数据的实现方式

    2024年02月03日
    浏览(50)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包