【Spring Boot实战】EasyPoi神技:一篇搞定“一对多+图片”导入!

简介: 本文分享使用Spring Boot + EasyPoi实现复杂Excel导入(含图片和一对多数据)的实战经验。涵盖环境配置、实体注解、一对多处理、图片上传及大文件监听器优化,解决内存溢出问题。附替代官方文档链接,助力高效开发。

最近做了一个excel 导入相关的功能,因为导入的excel 格式比较复杂,并且还有图片,做的时候也是费了一点功夫,因此整理了这篇文章

推荐大家看官方文档,但是我在访问官方文档时报了权限不足
0a02888a78ab4aaf8f9eb13517efc79a_MD5.jpeg
搜了一下好像是说截至2024年11月18日已经无法打开了,但我又找到了个优化版在语雀上,这里分享给大家
EasyPoi官方文档优化版: https://www.yuque.com/guomingde/easypoi

读前须知

本文采用的是Spring boot + EasyPoi ,同时使用了lombak 来简化实体类配置。使用Spring 或者其他框架,对应依赖需要修改,代码部分是一样的

EasyPoi 是什么?

简单说,EasyPoi 是一个基于 Apache POI 的封装库,它的核心哲学就是:让Excel导入导出变得跟呼吸一样简单!

核心优势:

  • 注解驱动:一个注解搞定一个字段,告别繁琐的API。
  • 功能强大:轻松处理一对多、图片、公式、下拉框等。
  • 代码极简:比原生POI代码量减少90%!

使用步骤

导入表格示例:
68fb64ac7795d3032771b9ddb4208437_MD5.jpeg

第一步:Maven依赖

springboot整合EasyPoi

<!-- EasyPoi 核心依赖 -->
<dependency>
    <groupId>cn.afterturn</groupId>
    <artifactId>easypoi-spring-boot-starter</artifactId>
    <version>4.5.0</version>
</dependency>

<dependency>  
    <groupId>org.projectlombok</groupId>  
    <artifactId>lombok</artifactId>  
    <optional>true</optional>  
</dependency>

spring 下依赖包配置

<dependency>
    <groupId>cn.afterturn</groupId>
    <artifactId>easypoi-base</artifactId>
    <version>4.1.0</version>
</dependency>
<dependency>
    <groupId>cn.afterturn</groupId>
    <artifactId>easypoi-web</artifactId>
    <version>4.1.0</version>
</dependency>
<dependency>
    <groupId>cn.afterturn</groupId>
    <artifactId>easypoi-annotation</artifactId>
    <version>4.1.0</version>
</dependency>

第二步:创建实体类

这是最关键的一步!我们用注解来告诉EasyPoi数据结构。

1. 创建主实体(订单)

import cn.afterturn.easypoi.excel.annotation.Excel;
import cn.afterturn.easypoi.excel.annotation.ExcelCollection;
import cn.afterturn.easypoi.excel.annotation.ExcelTarget;
import java.util.List;

@Data
public class OrderDTO {
   

    @Excel(name = "订单编号", width = 20)
    private String orderNo;

    @Excel(name = "客户名称")
    private String customerName;

    // 👇 核心注解!处理一对多关系
    @ExcelCollection(name = "订单商品") // 这里的name在导入时没啥用,但导出时是表头
    private List<OrderItemDTO> items;

}

划重点:

  • @ExcelCollection: 就是处理一对多的关键配置! 它会自动将Excel中连续的多行数据,根据主实体的字段(如订单编号)进行分组,封装成一个List。

2. 创建子实体(商品项)

import cn.afterturn.easypoi.excel.annotation.Excel;
import cn.afterturn.easypoi.excel.annotation.ExcelTarget;

@Data
public class OrderItemDTO {
   

    @Excel(name = "商品名称") // 👈 对应Excel列名
    private String productName;

    @Excel(name = "商品数量")
    private Integer quantity;

    // 👇 核心注解!处理图片导入
    @Excel(name = "商品图片", type = 2, savePath = "uploads/images/")
    private String imagePath; // 字段存储的是图片保存后的路径
}

划重点:

  • @ExcelTarget: 标记这是一个Excel实体。
  • @Excel(name = "..."): 将字段与Excel列名绑定。
  • @Excel(type = 2, savePath = "..."): type = 2 表示这是一个图片类型。savePath 极其重要,它指定了图片上传后保存的服务器路径。

遇到的坑:
在导入图片时,一开始我在 Excel 中使用的是嵌入模式,在导入时一直读不到图片,后来改成浮动式,才能正常读到文件。官网上好像也没有特别说明,如果你也遇到这个问题,可以试着改成浮动试一下。

第三步:编写Controller

import cn.afterturn.easypoi.excel.ExcelImportUtil;
import cn.afterturn.easypoi.excel.entity.ImportParams;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
import java.util.Map;

@RestController
public class ExcelImportController {
   

    @PostMapping("/import/order")
    public String importOrder(MultipartFile file) throws Exception {
   

        // 1. 设置导入参数
        ImportParams params = new ImportParams();
        params.setTitleRows(0); // 标题行,从第0行开始
        params.setHeadRows(1); // 表头行,从第1行开始
        params.setDataRows(0); // 数据行,从第0行开始(相对于表头)

        // 👇 关键!开启一对多导入模式
        params.setNeedVerify(true); // 开启验证
        params.setGroupFieldIndex(0); // 👈 指定分组依据的列索引(订单编号在第0列)

        // 2. 执行导入
        // 注意:这里导入的是主实体 OrderDTO
        List<OrderDTO> orderList = ExcelImportUtil.importExcel(file.getInputStream(), OrderDTO.class, params);

        // 3. 处理结果
        for (OrderDTO order : orderList) {
   
            System.out.println("订单编号: " + order.getOrderNo());
            System.out.println("客户名称: " + order.getCustomerName());
            for (OrderItemDTO item : order.getItems()) {
   
                System.out.println("  - 商品: " + item.getProductName() + ", 数量: " + item.getQuantity() + ", 图片路径: " + item.getImagePath());
            }
            // ... 在这里进行你的数据库保存操作 ...
        }

        return "导入成功!共导入 " + orderList.size() + " 个订单。";
    }
}

代码解析:

  1. ImportParams 是导入的配置中心,可以设置标题、表头位置等。
  2. params.setGroupFieldIndex(0); 这是处理一对多的灵魂! 它告诉EasyPoi:“请看第0列(订单编号),如果这列的值不变,就认为是同一个订单,把后面的数据塞到它的 items 列表里!”
  3. ExcelImportUtil.importExcel(...) 一行代码,搞定所有解析和封装!图片会自动保存到你在DTO里设置的 savePath,路径赋值给 imagePath 字段。

大量数据导入

如果导入的数据量很大的话,直接使用上面的代码,很可能导入后直接内存溢出!这时候就可以使用监听器模式进行优化

创建自定义监听器

/**
 *  订单导入监听器
 * 核心:实现 IExcelReadListener 接口
 * 作用:流式处理每一行数据
 */
@Component
@Slf4j
public class OrderImportListener implements IExcelReadListener<OrderDTO> {
   

    @Autowired
    private OrderService orderService;

    private List<OrderDTO> orderCache = new ArrayList<>();
    private int batchSize = 1000; // 🎯 批处理大小
    private int totalCount = 0;

    /**
     * 核心方法:处理每一行数据
     */
    @Override
    public void invoke(OrderDTO order, AnalysisContext context) {
   
        totalCount++;

        //  数据校验
        if (isValidOrder(order)) {
   
            orderCache.add(order);

            //  批量保存
            if (orderCache.size() >= batchSize) {
   
                saveBatch();
                orderCache.clear();
            }
        } else {
   
            log.warn("数据校验失败,行号: {}", context.getCurrentRowNum());
        }

        //  进度反馈(每1000行日志一次)
        if (totalCount % 1000 == 0) {
   
            log.info(" 已处理: {} 行数据", totalCount);
        }
    }

    /**
     *  所有数据解析完成
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
   
        // 处理最后一批数据
        if (!orderCache.isEmpty()) {
   
            saveBatch();
        }
        log.info("🎉 导入完成!总计处理: {} 行", totalCount);
    }

    /**
     * 🎯批量保存到数据库
     */
    private void saveBatch() {
   
        try {
   
            // 这里可以处理图片和一对多关系
            orderService.batchSaveOrders(orderCache);
            log.info("✅ 批量保存成功,数量: {}", orderCache.size());
        } catch (Exception e) {
   
            log.error("❌ 批量保存失败", e);
            throw new RuntimeException("批量保存异常");
        }
    }

    /**
     *  数据校验
     */
    private boolean isValidOrder(OrderDTO order) {
   
        return order != null && 
               StringUtils.isNotBlank(order.getOrderNo()) &&
               order.getItems() != null && !order.getItems().isEmpty();
    }
}

Controller层调用

调用的修改也很简单,调用导入方式时,传入监听器即可

@RestController
@RequestMapping("/api/order")
@Slf4j
public class OrderImportController {
   

    @Autowired
    private OrderImportListener orderImportListener;

    /**
     *  大文件导入接口
     */
    @PostMapping("/import-large")
    public ResponseEntity<?> importLargeOrder(@RequestParam("file") MultipartFile file) {
   

        try {
   
            //  创建导入参数
            ImportParams params = new ImportParams();
            params.setTitleRows(0);
            params.setHeadRows(1);

            //  关键配置:使用监听器模式
            ExcelImportUtil.importExcel(
                file.getInputStream(), 
                OrderDTO.class, 
                params, 
                orderImportListener  // 👈 传入监听器
            );

            return ResponseEntity.ok("大文件导入已启动,请查看日志获取进度");

        } catch (Exception e) {
   
            log.error("大文件导入失败", e);
            return ResponseEntity.badRequest().body("导入失败: " + e.getMessage());
        }
    }
}

总结

EasyPoi让复杂Excel导入变得如此简单:

  • ✅ 一对多关系:@ExcelCollection + 分组配置
  • ✅ 图片导入:type=2 + savePath
  • ✅ 代码简洁:注解驱动,告别繁琐API
  • ✅ 大量数据时使用监听器

从此告别加班,准时下班不是梦!

相关文章
|
云栖大会 开发者
收到阿里云【乘风者计划】博主证书和奖励
收到阿里云【乘风者计划】博主证书和奖励 2023年2月对我来说是一个很好的开端,因为我在1号就收到了阿里云寄给我的【乘风者计划】博主证书和奖励。好兆头啊! 我收到的是我获得的【技术博主】【星级博主】【专家博主】三个的奖品和证书,一快给我寄过来哒!
3046 2
收到阿里云【乘风者计划】博主证书和奖励
|
1月前
|
Java 关系型数据库 MySQL
Spring Boot自动配置:魔法背后的秘密
Spring Boot 自动配置揭秘:只需简单配置即可启动项目,背后依赖“约定大于配置”与条件化装配。核心在于 `@EnableAutoConfiguration` 注解与 `@Conditional` 系列条件判断,通过 `spring.factories` 或 `AutoConfiguration.imports` 加载配置类,实现按需自动装配 Bean。
|
2月前
|
缓存 Java Spring
Spring循环依赖:当两个Bean陷入鸡生蛋死循环时...
Spring中循环依赖问题常见于Bean相互依赖时,尤其在单例模式下。文章深入解析了循环依赖的成因及Spring的三级缓存解决方案,帮助理解Bean生命周期与依赖管理。
IDEA 使用 lombak 时的一个小警告:Generating equals/hashCode implementation but without a call to superclass
今日在IDEA中使用Lombok时遇到@Data注解的黄色警告,提示equals和hashCode未调用父类方法。虽不影响运行,但影响美观。可通过@EqualsAndHashCode(callSuper = true)或在lombok.config中配置默认行为解决,推荐后者统一管理,彻底消除警告。
|
8月前
|
存储 算法 Java
JVM: 内存、类与垃圾
分代收集算法将内存分为新生代和老年代,分别使用不同的垃圾回收算法。新生代对象使用复制算法,老年代对象使用标记-清除或标记-整理算法。
107 6
|
23天前
|
存储 消息中间件 NoSQL
Redis数据结构:别小看这5把“瑞士军刀”,用好了性能飙升!
Redis提供5种基础数据结构及多种高级结构,如String、Hash、List、Set、ZSet,底层通过SDS、跳表等实现高效操作。灵活运用可解决缓存、计数、消息队列、排行榜等问题,结合Bitmap、HyperLogLog、GEO更可应对签到、UV统计、地理位置等场景,是高性能应用的核心利器。
|
23天前
|
存储 缓存 NoSQL
Redis持久化深度解析:数据安全与性能的平衡艺术
Redis持久化解决内存数据易失问题,提供RDB快照与AOF日志两种机制。RDB恢复快、性能高,但可能丢数据;AOF安全性高,最多丢1秒数据,支持多种写回策略,适合不同场景。Redis 4.0+支持混合持久化,兼顾速度与安全。根据业务需求选择合适方案,实现数据可靠与性能平衡。(238字)
|
5月前
|
缓存 Linux 数据安全/隐私保护
Linux环境下如何通过手动调用drop_caches命令释放内存
总的来说,记录住“drop_caches” 命令并理解其含义,可以让你在日常使用Linux的过程中更加娴熟和自如。
1063 23
|
1月前
|
存储 消息中间件 缓存
Redis 简介:打造快速数据存储的利器
Redis 是一款开源的内存数据结构服务器,支持字符串、哈希、列表等多种数据结构,具备高性能、持久化、高可用及分布式特性,适用于缓存、会话管理、实时统计等场景。
|
10月前
|
存储 缓存 NoSQL
解决Redis缓存数据类型丢失问题
解决Redis缓存数据类型丢失问题
395 85