Appearance
1、Java 中的流行库
Java 对 Excel 的常用操作通常涉及读取、创建、编辑和保存 Excel 文件。
这些操作主要通过 Apache POI 和 jExcelAPI 这两个流行的库来实现。Apache POI 支持老式的 Excel XLS 格式和较新的 XLSX 格式,而 jExcelAPI 主要用于处理 XLS 文件。
以下是一些常见的操作示例:
Apache POI
Apache POI 是处理 Microsoft Office 文档的流行 Java 库,特别是 Excel 文档(包括 XLS 和 XLSX 格式)。
POI 提供了不同的模型来处理 Excel 文档,主要包括 HSSF(用于 .xls 格式)、XSSF(用于 .xlsx 格式)以及 SXSSF(用于处理大型 .xlsx 文件的扩展版本)。在处理 .xlsx 格式文件时,主要使用的是 XSSF 和 SXSSF。
使用示例:
<!-- 基本依赖,仅操作 xls 格式只需引入此依赖 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.14</version>
</dependency>
<!-- 使用 xlsx 格式需要额外引入此依赖 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.14</version>
</dependency>演示代码
package com.ruoyi.luoqi.excel;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.*;
/**
* @author luoqi
* @File ApachePOITestOperation.java
* @Desc
* @Create 2024/4/3 15:18
* @ChangeList --------------------------------------------------------------------
* Date Editor ChangeReason
*/
public class ApachePOITestOperation {
public static void main(String[] args){
try {
writeExcel();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void readExcel() throws IOException {
FileInputStream file = new FileInputStream(new File("path/to/excel.xlsx"));
XSSFWorkbook workbook = new XSSFWorkbook(file);
//读取工作表
XSSFSheet sheet = workbook.getSheetAt(0);
//遍历行与单元格
for (Row row : sheet) {
for (Cell cell : row) {
switch (cell.getCellType()) {
case STRING: System.out.print(cell.getStringCellValue() + " ");
break;
case NUMERIC: System.out.print(cell.getNumericCellValue() + " ");
break;
case BOOLEAN: System.out.print(cell.getBooleanCellValue() + " ");
break;
default:
}
}
System.out.println();
}
}
public static void writeExcel() throws IOException {
//创建 Excel 文件
XSSFWorkbook workbook = new XSSFWorkbook();
XSSFSheet sheet = workbook.createSheet("Name of the Sheet");
//创建行和单元格
Row row = sheet.createRow(0);
Cell cell = row.createCell(0);
cell.setCellValue("Cell Value");
//保存 Excel 操作
FileOutputStream out = new FileOutputStream(new File("path/to/excel2.xlsx"));
workbook.write(out);
out.close();
workbook.close();
}
}jExcelAPI
<dependency>
<groupId>net.sourceforge.jexcelapi</groupId>
<artifactId>jxl</artifactId>
<version>2.6.12</version>
</dependency>演示代码
package com.ruoyi.luoqi.excel;
import jxl.Cell;
import jxl.Sheet;
import jxl.Workbook;
import jxl.read.biff.BiffException;
import jxl.write.Label;
import jxl.write.WritableSheet;
import jxl.write.WritableWorkbook;
import jxl.write.WriteException;
import java.io.File;
import java.io.IOException;
/**
* @author luoqi
* @File jExcelAPITestOpeartion.java
* @Desc
* @Create 2024/4/3 15:56
* @ChangeList --------------------------------------------------------------------
* Date Editor ChangeReason
*/
public class JExcelAPITestOpeartion {
public static void main(String[] args) {
try {
JExcelAPITestOpeartion jExcelAPITestOpeartion = new JExcelAPITestOpeartion();
jExcelAPITestOpeartion.readExcel();
} catch (IOException e) {
e.printStackTrace();
} catch (BiffException e) {
e.printStackTrace();
}
}
public void readExcel() throws IOException, BiffException {
//读取Excel 操作
Workbook workbook = Workbook.getWorkbook(new File("path/to/excel.xls"));
//读取工作表
Sheet sheet = workbook.getSheet(0);
//读取内容
Cell cell = sheet.getCell(0, 0);
String content = cell.getContents();
System.out.println(content);
}
public static void writeExcel() throws IOException, WriteException {
WritableWorkbook workbook = Workbook.createWorkbook(new File("path/to/excel.xls"));
WritableSheet sheet = workbook.createSheet("Sheet Name", 0);
Label label = new Label(0, 0, "Content");
sheet.addCell(label);
workbook.write();
workbook.close();
}
}2、Excel的存储格式
参考: https://albenw.github.io/posts/d093ca4e/

Excel的存储格式
- XLS
- XLS一个Sheet最多也只能有 65535 行数据
- 每个Sheet最多只能有 256 列(从A到IV)
- XLSX(底层使用xml存储)
- 一个Sheet最多可以有 1048576 行数据
- 每个Sheet最多可以有 16384 列(从A到XFD)
- 文件大小更小
导出优化
建议可以采用: SXSSFWorkbook 方式。
导入优化
官网示例: https://poi.apache.org/components/spreadsheet/how-to.html
POI对导入分为3种模式,用户模式 User Model,事件模式 Event Model,还有 Event User Model。
- POI对XLS支持 Event Model
- POI对 XLSX 支持 Event Model 和 Event User Model
默认采用:
Apache POI 在处理 .xlsx 文件时默认采用的是 User Model,它提供了更直观、更灵活的方式来处理 Excel 文件,但代价是较高的内存消耗。当处理大型文件或需要优化内存使用时,可以考虑使用 Event Model 或 Event User Model,尽管这需要更复杂的实现逻辑。
具体实现的话,可能会稍微复杂一点,需要自定义一些相关的配置和处理。
3、EasyExcel
对于导入的一些优化操作:
准备环境:
- 使用 .xlsx 文件
- 导入的时候需要进行数据校验
- 数据校验有两种:
- 字段长度、字段正则表达式校验等,内存内校验不存在外部数据交互。对性能影响较小。
- 数据重复性校验(需要查询数据库,十分影响性能)。
- 数据校验有两种:
- 校验完成后,进行数据导入操作。
- 这里的环境是:数据库使用 MySQL 5.7,未分库分表,连接池使用 Druid
迭代记录:
- POI + 逐行查询校对 + 逐行插入
- EasyPOI + 缓存数据库查询操作 + 批量插入
- EasyExcel + 缓存数据库查询操作 + 批量插入
优化建议
提升Excel导入速度的方法:
- 使用更快的 Excel 读取框架(推荐使用阿里 EasyExcel)。
- 对于需要与数据库交互的校验、按照业务逻辑适当的使用缓存。用空间换时间。
- 使用 values( ),( ),( ) 拼接长 SQL 一次插入多行数据。
- 使用多线程插入数据,利用掉网络IO等待时间(推荐使用并行流,简单易用)。
- 避免在循环中打印无用的日志。
这里是使用的 若依 分离版进行二次改造
单独新增了一个页面,用于导入操作
引入依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.1.3</version>
</dependency>具体操作
- 1、定义实体类
- 2、定义字典转换(如果需要,导出或者导入操作)
- 3、定义导入和导出接口(最简单)
- 4、考虑如何进行优化,并通过不同示例去掌握具体使用。
参考: https://zhuanlan.zhihu.com/p/641135306
实体类:
这里定义一个车辆订单的信息
先进行数据库表设计,再做实体类的映射关系,这里不使用前端,就使用后端接口进行测试操作。
订单号
订单状态
下单时间
排序号
VIN号创建表
CREATE TABLE IF NOT EXISTS orders (
order_id VARCHAR(32) NOT NULL,
order_status VARCHAR(8) NOT NULL,
order_time DATETIME NOT NULL,
sort_number INT NOT NULL,
vin_number VARCHAR(32) NOT NULL,
PRIMARY KEY (order_id)
);通过若以的代码生成,生成一下后端的代码
细节文件这里跳过
- Orders
- OrdersController
- IOrdersService
- OrdersServiceImpl
- OrdersMapper
- OrdersMapper.xml
测试批量插入操作
OrdersExcelOpertaion
package com.ruoyi.luoqi.excel;
import com.ruoyi.system.domain.Orders;
import com.ruoyi.system.service.IOrdersService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;
/**
* @author luoqi
* @File OrdersDataInsertOpertaion.java
* @Desc
* @Create 2024/4/5 10:08
* @ChangeList --------------------------------------------------------------------
* Date Editor ChangeReason
*/
@SpringBootTest
public class OrdersDataInsertOpertaion {
@Autowired
private IOrdersService ordersService;
@Test
public void testSomeServiceMethod() {
int startNum = 1;
int endNum = 1000;
//计算耗时
long startTime = System.currentTimeMillis();
for(int i = startNum;i<= endNum ;i ++){
Orders orders = new Orders();
orders.setOrderId(String.valueOf(i));
orders.setOrderStatus("20");
orders.setSortNumber((long) i);
orders.setVinNumber(getRandomSeventeenLength());
orders.setOrderTime(new Date());
ordersService.insertOrders(orders);
}
long endTime = System.currentTimeMillis();
System.out.println("方法执行耗时: " + (endTime - startTime) + " 毫秒");
}
@Test
public void testSomeServiceMethodBatch() {
// 创建一个订单列表用于批量插入
List<Orders> ordersList = new ArrayList<>();
int startNum = 2001;
int endNum = 10000;
//计算耗时
long startTime = System.currentTimeMillis();
for(int i = startNum;i<= endNum ;i ++){
Orders orders = new Orders();
orders.setOrderId(String.valueOf(i));
orders.setOrderStatus("20");
orders.setSortNumber((long) i);
orders.setVinNumber(getRandomSeventeenLength());
orders.setOrderTime(new Date());
ordersList.add(orders);
}
ordersService.insertOrdersList(ordersList);
long endTime = System.currentTimeMillis();
System.out.println("方法执行耗时: " + (endTime - startTime) + " 毫秒");
}
@Test
public void testSomeServiceMethodBatchUpdate() {
//MySQL默认的最大包大小是4MB(可通过max_allowed_packet参数调整),这意味着一次批量插入操作的数据总量不能超过这个限制
// 创建一个订单列表用于批量插入
List<Orders> ordersList = new ArrayList<>();
int startNum = 10001;
int endNum = 20000;
//计算耗时
long startTime = System.currentTimeMillis();
for(int i = startNum;i<= endNum ;i ++){
Orders orders = new Orders();
orders.setOrderId(String.valueOf(i));
orders.setOrderStatus("20");
orders.setSortNumber((long) i);
orders.setVinNumber(getRandomSeventeenLength());
orders.setOrderTime(new Date());
ordersList.add(orders);
}
//批量大小的控制看实际情况,一方面要考虑实际插入大小(包括数据复杂的程度),另一方面还要考虑网络、数据库等的影响
ordersService.batchInsert(ordersList,1000);
long endTime = System.currentTimeMillis();
System.out.println("方法执行耗时: " + (endTime - startTime) + " 毫秒");
}
public String getRandomSeventeenLength() {
Random random = new Random();
StringBuilder sb = new StringBuilder(17);
// 定义可能出现的字符集
String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (int i = 0; i < 17; i++) {
// 随机选择一个字符
int index = random.nextInt(characters.length());
sb.append(characters.charAt(index));
}
String randomString = sb.toString();
return randomString;
}
}特意说一下这个分配插入这里,这里是在 service 添加了一下事务控制
@Override
@Transactional
public void batchInsert(List<Orders> allOrders, int batchSize) {
// 分批处理数据
int totalSize = allOrders.size();
for (int i = 0; i < totalSize; i += batchSize) {
int end = Math.min(totalSize, i + batchSize);
List<Orders> batchList = allOrders.subList(i, end);
// 执行批量插入
ordersMapper.insertOrdersList(batchList);
}
}测试数据添加的情况
遍历插入1000条数据,方法执行耗时: 1465 毫秒
批量插入1000条数据,方法执行耗时: 205 毫秒
批量插入8000条数据,方法执行耗时: 699 毫秒
分批量插入 10000条数据,每次插入1000条,方法执行耗时: 650 毫秒
分批量插入 20000条数据,每次插入1000条,方法执行耗时: 875 毫秒分批量的情况下的确会好很多
自定义导出 Excel 注解
在实际使用到 若依 的分离版项目的时候,他这里并没有使用 easyexcel ,仍然使用的是 poi ,然后使用了 自定义注解的方式。
SysLogininfor 待分析
to be contined....
EasyExcel 的一些基础操作
常用的一些注解属性,一般在 domain 类上使用
- @ExcelProperty: 核心注解,value属性可用来设置表头名称,converter属性可以用来设置类型转换器;
- @ColumnWidth: 用于设置表格列的宽度;
- @DateTimeFormat: 用于设置日期转换格式;
- @NumberFormat: 用于设置数字转换格式。
建议看一下官网的示例: https://easyexcel.opensource.alibaba.com
Order
package com.ruoyi.system.domain;
import java.util.Date;
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.system.domain.converter.OrderStatusConverter;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.core.domain.BaseEntity;
/**
* @author luoqi
* @File Orders.java
* @Desc
* @Create 2024/4/5 9:47
* @ChangeList --------------------------------------------------------------------
* Date Editor ChangeReason
*/
public class Orders
{
private static final long serialVersionUID = 1L;
@ExcelProperty(value = "订单ID")
private String orderId;
//Excel的列描述, 字典转换:converter , converter = OrderStatusConverter.class
@ExcelProperty(value = "订单状态", converter = OrderStatusConverter.class)
//列宽
@ColumnWidth(20)
private String orderStatus;
@ExcelProperty("订单时间")
//日期转换格式
@DateTimeFormat("yyyy-MM-dd HH:mm:ss")
private Date orderTime;
@ExcelProperty("排序号")
private Long sortNumber;
@ExcelProperty("VIN号")
private String vinNumber;
public void setOrderId(String orderId)
{
this.orderId = orderId;
}
public String getOrderId()
{
return orderId;
}
public void setOrderStatus(String orderStatus)
{
this.orderStatus = orderStatus;
}
public String getOrderStatus()
{
return orderStatus;
}
public void setOrderTime(Date orderTime)
{
this.orderTime = orderTime;
}
public Date getOrderTime()
{
return orderTime;
}
public void setSortNumber(Long sortNumber)
{
this.sortNumber = sortNumber;
}
public Long getSortNumber()
{
return sortNumber;
}
public void setVinNumber(String vinNumber)
{
this.vinNumber = vinNumber;
}
public String getVinNumber()
{
return vinNumber;
}
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("orderId", getOrderId())
.append("orderStatus", getOrderStatus())
.append("orderTime", getOrderTime())
.append("sortNumber", getSortNumber())
.append("vinNumber", getVinNumber())
.toString();
}
}使用这个类导出的时候遇到一个坑,建议不要继承子类,导出是导出所有字段,除非你字段上显示忽略
OrderStatusConverter
package com.ruoyi.system.domain.converter;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.converters.ReadConverterContext;
import com.alibaba.excel.converters.WriteConverterContext;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.data.WriteCellData;
import java.util.HashMap;
import java.util.Map;
/**
* @author luoqi
* @File OrderStatusConverter.java
* @Desc
* @Create 2024/4/5 11:25
* @ChangeList --------------------------------------------------------------------
* Date Editor ChangeReason
*/
public class OrderStatusConverter implements Converter<String> {
private static final Map<String, String> statusToDescriptionMap = new HashMap<>();
private static final Map<String, String> descriptionToStatusMap = new HashMap<>();
static {
statusToDescriptionMap.put("00", "初始化");
statusToDescriptionMap.put("05", "排序");
statusToDescriptionMap.put("10", "VIN生成");
statusToDescriptionMap.put("15", "展开");
statusToDescriptionMap.put("20", "发布");
statusToDescriptionMap.put("25", "在制");
statusToDescriptionMap.put("30", "下线");
descriptionToStatusMap.put("初始化", "00");
descriptionToStatusMap.put("排序", "05");
descriptionToStatusMap.put("VIN生成", "10");
descriptionToStatusMap.put("展开", "15");
descriptionToStatusMap.put("发布", "20");
descriptionToStatusMap.put("在制", "25");
descriptionToStatusMap.put("下线", "30");
}
@Override
public Class<String> supportJavaTypeKey() {
return String.class;
}
@Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.STRING;
}
//导入时进行类型转换操作
@Override
public String convertToJavaData(ReadConverterContext<?> context) {
String description = context.getReadCellData().getStringValue();
return descriptionToStatusMap.getOrDefault(description, "未知状态码");
}
//导出时进行类型转换操作
@Override
public WriteCellData<?> convertToExcelData(WriteConverterContext<String> context) {
String description = statusToDescriptionMap.getOrDefault(context.getValue(), "未知状态");
return new WriteCellData<>(description);
}
}测试类
@SpringBootTest
public class OrdersExcelOpertaion {
@Autowired
private IOrdersService ordersService;
@Test
public void exportOrderExcel2() {
Orders orders = new Orders();
orders.setSortNumber(789L);
List<Orders> list = ordersService.selectOrdersList(orders);
String fileName = "path/to/orderExcel2.xlsx";
EasyExcel.write(fileName, Orders.class)
.head(Orders.class)
.sheet("用户列表")
.doWrite(list);
}
@Test
public void importOrderExcel2() {
try {
FileInputStream file = new FileInputStream(new File("path/to/orderExcel2.xlsx"));
List<Orders> ordersList = EasyExcel.read(file)
.head(Orders.class)
.sheet()
.doReadSync();
for(Orders orders:ordersList)
{
System.out.println(orders);
}
//读取后进行存放
ordersService.batchInsert(ordersList,100);
} catch (IOException e) {
System.out.println("执行异常");
}
}
}如果是在 controller 类上
@GetMapping("/export2/order")
public void exportOrderExcel(HttpServletResponse response) {
try {
Orders orders = new Orders();
orders.setSortNumber(789L);
//这个方法用于设置响应头,以便告知浏览器要下载一个文件,而不是直接在浏览器中打开这个响应。
this.setExcelResponseProp(response, "用户列表");
List<Orders> list = ordersService.selectOrdersList(orders);
EasyExcel.write(response.getOutputStream())
.head(Orders.class)
.excelType(ExcelTypeEnum.XLSX)
.sheet("用户列表")
.doWrite(list);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void setExcelResponseProp(HttpServletResponse response, String fileName) {
try {
// 防止中文乱码
fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name());
} catch (UnsupportedEncodingException e) {
// 日志记录错误或其他处理
throw new RuntimeException("文件名编码失败", e);
}
// 设置响应类型
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
// 设置文件名
response.setHeader("Content-disposition", "attachment;filename=" + fileName);
// 防止浏览器缓存
response.setHeader("Pragma", "no-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
}
@PostMapping("/import2/order")
public AjaxResult importOrderExcel(@RequestPart(value = "file") MultipartFile file) {
try {
List<Orders> ordersList = EasyExcel.read(file.getInputStream())
.head(Orders.class)
.sheet()
.doReadSync();
for(Orders orders:ordersList)
{
System.out.println(orders);
}
//读取后进行存放
ordersService.batchInsert(ordersList,100);
return success(ordersList);
} catch (IOException e) {
return error();
}
}本人使用的是 apifox
导出操作(选择发送并下载)

导入操作(主要是 body 这里注意一下)

小结
- 1、看官网,去看示例
- 2、主要是掌握一些优化手段
包括:
- 使用更快的 Excel 读取框架(推荐使用阿里 EasyExcel)。
- 对于需要与数据库交互的校验、按照业务逻辑适当的使用缓存。用空间换时间。
- 使用 values( ),( ),( ) 拼接长 SQL 一次插入多行数据。
- 使用多线程插入数据,利用掉网络IO等待时间(推荐使用并行流,简单易用)。
- 避免在循环中打印无用的日志。
讲一下第四点:
在处理大量数据导入数据库时,网络I/O等待时间(特别是在分布式数据库环境中)可能成为性能瓶颈。
为了减少这些等待时间并提高数据处理速度,可以采用多线程或并行处理技术来并发执行数据插入操作。Java 8引入的并行流(Parallel Streams)提供了一种简单而强大的方式来利用多核处理器的并行处理能力。
使用并行流进行数据插入
并行流利用了Java的ForkJoinPool,它将一个大任务拆分成多个小任务,这些小任务被分发到线程池中的不同线程上并行执行,最后将结果合并。这使得并行流特别适合进行批量数据处理和插入操作。
以下是一个使用并行流进行数据插入优化的示例:
@Test
public void testSomeServiceMethodBatchUpdateByForkJoin() {
//MySQL默认的最大包大小是4MB(可通过max_allowed_packet参数调整),这意味着一次批量插入操作的数据总量不能超过这个限制
// 创建一个订单列表用于批量插入
List<Orders> ordersList = new ArrayList<>();
int startNum = 40001;
int endNum = 60000;
//计算耗时
long startTime = System.currentTimeMillis();
for(int i = startNum;i<= endNum ;i ++){
Orders orders = new Orders();
orders.setOrderId(String.valueOf(i));
orders.setOrderStatus("20");
orders.setSortNumber((long) i);
orders.setVinNumber(getRandomSeventeenLength());
orders.setOrderTime(new Date());
ordersList.add(orders);
}
//批量大小的控制看实际情况,一方面要考虑实际插入大小(包括数据复杂的程度),另一方面还要考虑网络、数据库等的影响
importData(ordersList);
long endTime = System.currentTimeMillis();
System.out.println("方法执行耗时: " + (endTime - startTime) + " 毫秒");
}
// 自定义并行度
private static final int parallelism = 4;
public void importData1(List<Orders> dataList) {
int batchSize = 1000;
// 使用自定义的ForkJoinPool来控制并行流的并行度
ForkJoinPool customThreadPool = new ForkJoinPool(parallelism);
try {
customThreadPool.submit(() -> {
// 分批处理数据,使用并行流进行批量插入
dataList.stream()
.collect(Collectors.groupingBy(data -> dataList.indexOf(data) / batchSize))
.values()
.parallelStream()
.forEach(batchList -> ordersService.batchInsert(batchList,batchSize));
}).get(); // 等待所有任务完成
} catch (InterruptedException | ExecutionException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Failed to import data in parallel", e);
} finally {
customThreadPool.shutdown();
}
}
public void importData(List<Orders> dataList) {
int batchSize = 1000;
// 使用自定义的ForkJoinPool来控制并行流的并行度
ForkJoinPool customThreadPool = new ForkJoinPool(parallelism);
try {
customThreadPool.submit(() ->
//改进点:1、直接使用批次索引;2、避免使用 indexOf;3、数据分割考虑使用其他方式替代 groupingBy
IntStream.range(0, (dataList.size() + batchSize - 1) / batchSize)
.parallel()
.mapToObj(batchNum -> dataList.subList(batchNum * batchSize,
Math.min(dataList.size(), (batchNum + 1) * batchSize)))
.forEach(batchList -> ordersService.batchInsert(batchList, batchSize))
).get(); // 等待所有任务完成
} catch (InterruptedException | ExecutionException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Failed to import data in parallel", e);
} finally {
customThreadPool.shutdown();
}
}使用 importData 方法,可以在 分批量插入 20000条数据,每次插入1000条,方法执行耗时: 800 毫秒;的基础上提升到 500毫秒左右。
4、导出优化
上文虽然讲述了一下 EasyExcel 的基础使用,以及对于导入操作的一些常见的优化手段,但关于导入,其实还有一些内容需要去了解下,这里主要以 https://mp.weixin.qq.com/s/5Ytgq_B8nvgFpU3FNeiyOQ 这篇文章内容,然后实际去运行和跑,看一下实际的效果。
先过一下基本的一些思路,实现放一下后面
- 1、依然是使用 esayexcel 的开源框架,内部进行了很大的一个优化,比起 Poi 的方式来说;
- 2、基本处理方式
- 一个SHEET一次查询导出
- 数据量适中(100W以内):一个SHEET分批查询导出
- 数据里很大(几百万都行):多个SHEET分批查询导出
参考: https://www.cxyxiaowu.com/20723.html
参考:
- https://albenw.github.io/posts/d093ca4e/
- https://www.51cto.com/article/717718.html
- https://mp.weixin.qq.com/s/5Ytgq_B8nvgFpU3FNeiyOQ
- https://github.com/alibaba/easyexcel
- https://www.bilibili.com/video/BV13m4y1p7GJ
- https://blog.csdn.net/yangxiao_hui/article/details/103262704
- https://zhuanlan.zhihu.com/p/641135306