EasyExcel对大数据量表格操作导入导出

news2025/7/18 5:47:41

前言

最近有个项目里面中有大量的Excel文档导入导出需求,数据量最多的文档有上百万条数据,之前的导入导出都是用apache的POI,于是这次也决定使用POI,结果导入一个四十多万的文档就GG了,内存溢出...  于是找到EasyExcel的文档,学习了一番,解决了大数据量导入导出的痛点。

由于项目中很多接口都需要用到导入导出,部分文档都是根据日期区分,部分文档是需要全表覆盖,于是抽出一个工具类,简化下重复代码,在此把实现过程记录一下。

测试结果

数据量100W

导入 

测试了几次,读取完加保存到数据库总耗时都是在140秒左右

导出 

由于在业务中不涉及到大数据量的导出,最多只有10W+数据的导出,所以用的是最简单的写,测试二十万的数据量五十秒左右

依赖

官方文档:EasyExcel官方文档 - 基于Java的Excel处理工具 | Easy Excel

1

2

3

4

5

<dependency>

    <groupId>com.alibaba</groupId>

    <artifactId>easyexcel</artifactId>

    <version>3.1.2</version>

</dependency>

  

具体实现

实体类

@ExcelProperty注解对应Excel文档中的表头,其中默认属性是value,对应文字,也有index属性,可以对应下标。converter属性是指定一个转换器,这个转换器中实现了把Excel内容转换成java对象(导入使用),Java对象转Excel内容(导出使用),我这里实现的是LocalDateTime和文本的转换。

@ExcelIgnoreUnannotated注解的意思就是在导入导出的时候忽略掉未加@ExcelProperty注解的字段

 1 @Data
 2 @TableName("t_test_user")
 3 @ApiModel(value = "TestUserEntity对象", description = "测试表")
 4 @ExcelIgnoreUnannotated
 5 public class TestUserEntity implements Serializable {
 6 
 7     private static final long serialVersionUID = 1L;
 8 
 9     @TableId(value = "id", type = IdType.AUTO)
10     private Long id;
11 
12     @ExcelProperty("用户名")
13     @ApiModelProperty("用户名")
14     @TableField("user_name")
15     private String userName;
16 
17     @ExcelProperty("账号")
18     @ApiModelProperty("账号")
19     @TableField("account")
20     private String account;
21 
22     @ExcelProperty("性别")
23     @ApiModelProperty("性别")
24     @TableField("sex")
25     private String sex;
26 
27     @ExcelProperty(value = "注册时间", converter = StringToLocalDateTimeConverter.class)
28     @ApiModelProperty("注册时间")
29     @TableField("registered_time")
30     private LocalDateTime registeredTime;
31 
32     @ApiModelProperty("数据日期")
33     @TableField("data_date")
34     private Integer dataDate;
35 
36     @ApiModelProperty("创建人")
37     @TableField("create_user")
38     private String createUser;
39 
40     @ApiModelProperty("创建时间")
41     @TableField("create_time")
42     private LocalDateTime createTime;
43 }

转换器

在这里实现导入导出的数据格式转换

 1 /**
 2  * @author Tang
 3  * @describe easyExcel格式转换器
 4  * @date 2022年08月29日 09:41:03
 5  */
 6 public class StringToLocalDateTimeConverter implements Converter<LocalDateTime> {
 7     /**
 8      * 这里读的时候会调用
 9      */
10     @Override
11     public LocalDateTime convertToJavaData(ReadConverterContext<?> context) {
12         String stringValue = context.getReadCellData().getStringValue();
13         return StringUtils.isBlank(stringValue) ? null : DateUtil.stringToLocalDatetime(stringValue);
14     }
15 
16     /**
17      * @describe 写的时候调用
18      * @Param context
19      * @return com.alibaba.excel.metadata.data.WriteCellData<?>
20      * @date 2022年11月17日 16:03:39
21      * @author Tang
22      */
23     @Override
24     public WriteCellData<?> convertToExcelData(WriteConverterContext<LocalDateTime> context) {
25         return new WriteCellData<>(DateUtil.localDateToDayString(context.getValue()));
26     }
27 
28 }

工具类

由于项目中很多接口都有使用到导入导出,且持久层框架是Mybatis Plus,在此封装成通用的方法。

如果数据量不大,那么一行代码就可以解决了,直接用Mybatis Plus的批量插入:

EasyExcel.read(file.getInputStream(), TestUserEntity.class, new PageReadListener<TestUserEntity>(TestUserService::saveBatch)).sheet().doRead();

PageReadListener是默认的监听器,在此监听器中传入一个Consumer接口的实现,由此来保存数据。它具体实现原理是从文件中分批次读取,然后在此监听器中实现保存到数据库,当然也可以重写监听器,定义自己想要实现的业务,如数据校验等。BATCH_COUNT参数是每次读取的数据条数,3.1.2的版本默认是100条,建议修改为3000。

导出也是一行代码:EasyExcel.write(response.getOutputStream(), clazz).sheet().doWrite(() -> testUserService.list());

数据量大的话用Mybatis Plus的批量插入还是会很慢,因为这个批量插入实际上还是一条条数据插入的,需要把所有数据拼接成insert into table(field1,field2) values(value1,value2),(value1,value2),(value,value2)...,配合数据库的rewriteBatchedStatements=true参数配置,可以实现快速批量插入,在下文中的114行调用实现。

  1 /**
  2  * @author Tang
  3  * @describe EasyExcel工具类
  4  * @date 2022年11月02日 17:56:45
  5  */
  6 public class EasyExcelUtil {
  7 
  8     /**
  9      * @describe 封装成批量插入的参数对象
 10      * @Param clazz
 11      * @Param dataList
 12      * @date 2022年11月17日 18:00:31
 13      * @author Tang
 14      */
 15     public static DynamicSqlDTO dynamicSql(Class<?> clazz, List<?> dataList) {
 16         //字段集合  key=数据库列名  value=实体类get方法
 17         Map<String, Method> getMethodMap = new LinkedHashMap<>();
 18         //获取所有字段
 19         Field[] declaredFields = clazz.getDeclaredFields();
 20         for (Field field : declaredFields) {
 21             field.setAccessible(true);
 22             //获取注解为TableField的字段
 23             TableField annotation = field.getAnnotation(TableField.class);
 24             if (annotation != null && annotation.exist()) {
 25                 String column = annotation.value();
 26                 Method getMethod = getGetMethod(clazz, field.getName());
 27                 getMethodMap.put(column, getMethod);
 28             }
 29         }
 30 
 31         //value集合
 32         List<List<Object>> valueList = dataList.stream().map(v -> {
 33             List<Object> tempList = new ArrayList<>();
 34             getMethodMap.forEach((key, value) -> {
 35                 try {
 36                     tempList.add(value.invoke(v));
 37                 } catch (IllegalAccessException | InvocationTargetException e) {
 38                     tempList.add(null);
 39                 }
 40             });
 41             return tempList;
 42         }).collect(Collectors.toList());
 43 
 44         return DynamicSqlDTO.builder()
 45                 .tableName(clazz.getAnnotation(TableName.class).value())
 46                 .columnList(new ArrayList<>(getMethodMap.keySet()))
 47                 .valueList(valueList)
 48                 .build();
 49     }
 50 
 51 
 52     /**
 53      * @describe java反射bean的get方法
 54      * @Param objectClass
 55      * @Param fieldName
 56      * @date 2022年11月02日 17:52:03
 57      * @author Tang
 58      */
 59     private static Method getGetMethod(Class<?> objectClass, String fieldName) {
 60         StringBuilder sb = new StringBuilder();
 61         sb.append("get");
 62         sb.append(fieldName.substring(0, 1).toUpperCase(Locale.ROOT));
 63         sb.append(fieldName.substring(1));
 64         try {
 65             return objectClass.getMethod(sb.toString());
 66         } catch (NoSuchMethodException e) {
 67             throw new RuntimeException("Reflect error!");
 68         }
 69     }
 70 
 71 
 72     /**
 73      * @return boolean
 74      * @describe EasyExcel公用导入方法(按日期覆盖)
 75      * @Param file             excel文件
 76      * @Param date             数据日期
 77      * @Param function         数据日期字段的get方法  如传入了date,则需要设置
 78      * @Param setCreateDate    数据日期set方法       如传入了date,则需要设置
 79      * @Param mapper           实体类对应的mapper对象 如传入了date,则需要设置
 80      * @Param entityClass      实体类class
 81      * @date 2022年11月11日 15:10:19
 82      * @author Tang
 83      */
 84     public static <T> Boolean importExcel(MultipartFile file, Integer date, SFunction<T, Integer> getCreateDate, BiConsumer<T, Integer> setCreateDate, BaseMapper<T> mapper, Class<T> entityClass) {
 85         String userName = SecurityAuthorHolder.getSecurityUser().getUsername();
 86         LocalDateTime now = LocalDateTime.now();
 87         CustomSqlService customSqlService = ApplicationConfig.getBean(CustomSqlService.class);
 88 
 89         //根据date来判断  为null则需要删除全表数据  否则删除当天数据
 90         if (date == null) {
 91             customSqlService.truncateTable(entityClass.getAnnotation(TableName.class).value());
 92         } else {
 93             mapper.delete(Wrappers.lambdaQuery(entityClass).eq(getCreateDate, date));
 94         }
 95 
 96         try {
 97             Method setCreateUser = entityClass.getMethod("setCreateUser", String.class);
 98             Method setCreateTime = entityClass.getMethod("setCreateTime", LocalDateTime.class);
 99 
100             EasyExcel.read(file.getInputStream(), entityClass, new PageReadListener<T>(
101                     dataList -> {
102                         dataList.forEach(v -> {
103                             try {
104                                 setCreateUser.invoke(v, userName);
105                                 setCreateTime.invoke(v, now);
106                                 if (setCreateDate != null) {
107                                     setCreateDate.accept(v, date);
108                                 }
109                             } catch (IllegalAccessException | InvocationTargetException e) {
110                                 e.printStackTrace();
111                             }
112                         });
113                         if (CollectionUtil.isNotEmpty(dataList)) {
114                             customSqlService.executeCustomSql(dynamicSql(entityClass, dataList));
115                         }
116                     }
117             )).sheet().doRead();
118         } catch (Exception e) {
119             e.printStackTrace();
120             throw new ServerException("读取异常");
121         }
122         return true;
123     }
124 
125     /**
126      * @return boolean
127      * @describe EasyExcel公用导入方法(全表覆盖)
128      * @Param file
129      * @Param entityClass
130      * @date 2022年11月11日 15:33:07
131      * @author Tang
132      */
133     public static <T> Boolean importExcel(MultipartFile file, Class<T> entityClass) {
134         return importExcel(file, null, null, null, null, entityClass);
135     }
136 
137     /**
138      * @return void
139      * @describe EasyExcel公用导出方法
140      * @Param clazz
141      * @Param dataList
142      * @date 2022年11月11日 15:56:45
143      * @author Tang
144      */
145     public static <T> void exportExcel(Class<T> clazz, List<T> dataList) {
146         HttpServletResponse response = ServletRequestUtil.getHttpServletResponse();
147         try {
148             EasyExcel.write(response.getOutputStream(), clazz)
149                     .sheet()
150                     .doWrite(() -> dataList);
151         } catch (Exception e) {
152             e.printStackTrace();
153             throw new ServerException("导出失败");
154         }
155     }
156 }

DTO

 1 /**
 2  * @author Tang
 3  * @describe 生成批量插入sqlDTO
 4  * @date 2022年11月02日 17:53:33
 5  */
 6 @Data
 7 @Builder
 8 @AllArgsConstructor
 9 @NoArgsConstructor
10 public class DynamicSqlDTO {
11 
12     //表名
13     private String tableName;
14 
15     //列名集合
16     private List<String> columnList;
17 
18     //value集合
19     private List<List<Object>> valueList;
20 }

Mapper

根据业务实现了两个方法,一个是批量插入,一个是全表覆盖删除

 1 @Mapper
 2 public interface CustomSqlMapper {
 3 
 4     /**
 5      * @describe 执行动态批量插入语句
 6      * @Param dynamicSql
 7      * @date 2022年11月03日 09:59:22
 8      * @author Tang
 9      */
10     void executeCustomSql(@Param("dto") DynamicSqlDTO dto);
11 
12     /**
13      * @describe 快速清空表
14      * @Param tableName
15      * @date 2022年11月08日 17:47:45
16      * @author Tang
17      */
18     void truncateTable(@Param("tableName") String tableName);
19 }

XML

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 3 <mapper namespace="**.**.CustomSqlMapper">
 4     <insert id="executeCustomSql">
 5         insert into ${dto.tableName}
 6         <foreach collection="dto.columnList" item="item" separator="," open="(" close=")">
 7             `${item}`
 8         </foreach>
 9         values
10         <foreach collection="dto.valueList" item="item" separator=",">
11             (
12                 <foreach collection="item" item="value" separator=",">
13                     #{value}
14                 </foreach>
15             )
16         </foreach>
17     </insert>
18 
19 
20     <insert id="truncateTable">
21         truncate table ${tableName}
22     </insert>
23 
24 </mapper>

调用

 1 @RestController
 2 @Api(value = "测试-测试", tags = "测试-测试")
 3 @RequestMapping("/test")
 4 public class TestUserController {
 5 
 6     @Resource
 7     private TestUserMapper testUserMapper;
 8 
 9     @PostMapping(value = "/import", produces = BaseConstant.REQUEST_HEADERS_CONTENT_TYPE)
10     @ApiOperation(value = "测试-导入(全表覆盖)", notes = "测试-导入(全表覆盖)")
11     public RR<Boolean> testImport(@RequestParam(value = "file") @ApiParam("上传文件") MultipartFile file) {
12         return RR.success(
13                 EasyExcelUtil.importExcel(
14                         file,
15                         TestUserEntity.class
16                 )
17         );
18     }
19 
20     @PostMapping(value = "/import", produces = BaseConstant.REQUEST_HEADERS_CONTENT_TYPE)
21     @ApiOperation(value = "测试-导入(按日期覆盖)", notes = "测试-导入(按日期覆盖)")
22     public RR<Boolean> testImport(@RequestParam(value = "file") @ApiParam("上传文件") MultipartFile file, @ApiParam("日期 20110101") @RequestParam(value = "date") Integer date) {
23         return RR.success(
24                 EasyExcelUtil.importExcel(
25                         file,
26                         date,
27                         TestUserEntity::getDataDate,
28                         TestUserEntity::setDataDate,
29                         testUserMapper,
30                         TestUserEntity.class
31                 )
32         );
33     }
34 
35     @PostMapping(value = "/export", produces = BaseConstant.REQUEST_HEADERS_CONTENT_TYPE)
36     @ApiOperation(value = "测试-导出", notes = "测试-导出")
37     public void testExport() {
38         EasyExcelUtil.exportExcel(
39                 TestUserEntity.class,
40                 testUserMapper.selectList(null)
41         );
42     }
43 }

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

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

相关文章

上采样,下采样,卷积,反卷积,池化,反池化,双线性插值【基本概念分析】

上采样,下采样,卷积,反卷积,池化,反池化,双线性插值【基本概念分析】】一、上采样1.概念2.原理二、下采样1.概念2.原理三、卷积与反卷积四、池化五、反池化六、双线性插值1.意义2.作用3.单线性插值4.双线性插值的公式5.双线性插值的例子一、上采样 1.概念 上采样&#xff08;…

前端页面全新的写法(第七课)Vue中的组件

VueCli框架的实操内容(第七课)Vue中的组件 组件是可复用的 Vue 实例, 把一些公共的模块抽取出来&#xff0c;然后写成单独的的工具组件或者页面&#xff0c;在需要的页面中就直接引入即可那么我们可以将其抽出为一个组件进行复用。例如 页面头部、侧边、内容区&#xff0c;尾部…

电脑里重要文件用什么备份,电脑如何备份主要数据

保护好数据安全是很重要的&#xff0c;能够给我们减少很多麻烦或者说是损失&#xff0c;所以&#xff0c;我们是有必要通过一些手段来保护好重要数据的。电脑里重要文件用什么备份&#xff1f;提前对数据进行备份无疑是最好的方法之一。 一、如何备份数据&#xff1f; 我们可以…

数据结构-排序算法总结

排序算法总结插入排序直接插入排序&#xff08;稳定&#xff09;希尔排序交换排序冒泡排序&#xff08;稳定&#xff09;快速排序选择排序简单选择排序堆排序归并排序&#xff08;稳定&#xff09;基数排序&#xff08;稳定&#xff09;多路归并排序&#xff08;外排序&#xf…

Stream之flatMap用法

记录一下flatMap的用法 个人理解是将流中的流合并 Data AllArgsConstructor NoArgsConstructor public class WhiteIp {//idprivate Integer id;//域名private String domain;//ip,多个用;分隔private String ipaddress;public static void main(String[] args) {WhiteIp w1 …

Android未捕获异常监控原理

背景 本文仅探讨java层的未捕获异常的监控为什么我们自己的异常捕获总是比 Bugly 收到的信息少&#xff1f; Android未捕获异常的监控与收集 Java层未捕获异常监控的基本实现 先看看Java层未捕获异常监控的运行过程&#xff1a; public class MyUncaughtExceptionHandler …

企业虚拟网络管理

随着企业规模的扩大&#xff0c;其网络的规模和复杂性也会成比例地扩展。企业级组织和中小型企业需要大规模网络来满足不断增长的业务需求。然而&#xff0c;大规模网络需要大量的物理组件、定期维护和配置&#xff0c;所有这些都是有代价的。因此&#xff0c;为了规避这些额外…

Spring Boot面试题

什么是 Spring Boot&#xff1f; Spring Boot 是 Spring 开源组织下的子项目&#xff0c;其设计目的是专注于Spring应用的开发&#xff0c;开发人员可以把更多的精力放在业务代码上&#xff0c;而无需过多关注XML的配置&#xff0c;从而简化Spring应用开发&#xff0c;提高开发…

牛客小白月赛61-E

传送门 题意 给你一个长度为n的序列&#xff0c;它有n&#xff01;个排列方法 问在这n&#xff01;个排列方法中 逆序对的总数是多少 首先要知道 逆序对数n&#xff01;/2*&#xff08;不相等的数字对儿数&#xff09; 不相等的数组对儿数cn2c_{n}^{2}cn2​-Zcnum[a[i]2c_{n…

前端如何写进度条(上传或者下载文件的时候)

1.需求 在日常开发中&#xff0c;我们经常会遇到上传或者下载文件的需求&#xff0c;以下载为例&#xff1a; 如果后台文件是现成的&#xff0c;浏览器就会在底部出现下载的过程&#xff0c;如果点击下载后&#xff0c;有些业务是需要去打包&#xff0c;然后再下载文件的话&a…

【C++语法难点】 深拷贝和浅拷贝是什么,拷贝构造,拷贝赋值

文章目录1.开始&#xff1a;构造函数1.2 在栈区和堆区创建对象1.3 缺省构造函数1.4 类型转换构造函数1.5 拷贝构造函数1.6 缺省拷贝构造函数&#xff08;浅拷贝&#xff09;1.7 深拷贝构造函数 (深拷贝)1.8 拷贝赋值1.开始&#xff1a;构造函数 语法形式 class 类名{类名(形参…

UE5笔记【三】UE5材质Materials

材质&#xff1a;可以将材质看作是StaticMesh上面的绘画。这副绘画Paint是由图层组成的&#xff0c;这些图层形成了所谓的物理基础渲染&#xff08;Physically Based Rendering OR PBR&#xff09;。这些PBR的特殊之处在于&#xff1a;几乎可以让我们模拟显示世界中的任何材质。…

「Redis数据结构」QuickList

「Redis数据结构」QuickList 文章目录「Redis数据结构」QuickList一、前言二、概述三、结构四、小结一、前言 在前面一章&#xff0c;我们已经学习了ZipList压缩列表&#xff0c;ZipList虽然节省内存&#xff0c;但也引发了不少问题。 问题1&#xff1a;ZipList虽然节省内存&am…

【Bio】基础生物学 - 细胞 cell

文章目录1. 细胞2. 原核细胞 真核细胞3. 细胞器4. 细胞核5. 动物细胞5.1 细胞质5.2 核糖体5.3 内质网6. 植物细胞6.1 液泡6.2 线粒体6.3 叶绿体Ref1. 细胞 生命系统的结构层次依次为&#xff1a; 细胞 (cell)\blue{\text{细胞 (cell)}}细胞 (cell) →\rightarrow→ 组织 (tiss…

玩转MySQL:程序中的“田氏代齐”,InnoDB为何能替换MyISAM?

引言 MySQL是一款支持拔插式引擎的数据库&#xff0c;在开发过程中你可以根据业务特性&#xff0c;从支持的诸多引擎中选择一款适合的&#xff0c;例如MyISAM、InnoDB、Merge、Memory(HEAP)、BDB(BerkeleyDB)、Example、Federated、Archive、CSV、Blackhole..... 不过虽然各款…

[附源码]java毕业设计全国人口普查管理系统论文

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

LeetCode刷题复盘笔记—一文搞懂746. 使用最小花费爬楼梯(动态规划系列第二篇)

今日主要总结一下动态规划的一道题目&#xff0c;746. 使用最小花费爬楼梯 题目&#xff1a;746. 使用最小花费爬楼梯 题目描述&#xff1a; 给你一个整数数组 cost &#xff0c;其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用&#xff0c;即可选择…

Linux之关于Nginx

目录 1、什么是Nginx&#xff1f; 1.1、负载均衡&#xff1a;流量分摊​编辑 1. 2、反向代理 &#xff1a;处理外网访问内网问题 1.3、动静分离:判断动态请求还是静态请求&#xff0c;选择性的访问指定服务器 2、Nginx的使用 2.1.Nginx安装 2.1.1 添…

11月27日PMI认证才聚各考点防疫要求,PMP考生必看

11月27日深圳才聚、珠海才聚、东莞才聚、南宁才聚防疫要求及如下&#xff1a; 注意&#xff1a;由于疫情防控影响&#xff0c;以下城市的考试将延期举办&#xff0c;该考点的考生无需做任何操作。 北京、天津、石家庄、廊坊、保定、哈尔滨、大庆、呼和浩特、太原、郑州、兰州…

【王道计算机网络笔记】计算机网络体系结构-计算机网络概述

文章目录计算机网络的概念计算机网络的功能计算机网络的组成计算机网络的分类标准化工作及相关组织相关组织计算机网络的性能指标速率带宽吞吐量时延时延带宽积往返时延RTT利用率计算机网络的概念 计算机网络&#xff1a;是一个分散的、具有独立功能的计算机系统&#xff0c;通…