前言:在工作中遇到了关于excel导入的一个场景,原本采取的方案只是暴力获取每一行的数据后插入数据库,后来发现数据量上来的时候这样写效率很低,在网上学习了各种关于excel导入优化的案例,发现easyexcel 这样一个工具非常有趣,特做一个学习总结
1、什么是EasyExcel?
我们可以看一下官方文档:EasyExcel官方文档
EasyExcel 是阿里巴巴开源的一个基于 Java 的简单、快速、小巧的 Excel 处理库。
首先,让我们了解一下 EasyExcel 是什么。根据官方文档,EasyExcel 是阿里巴巴开源的一个基于 Java 的轻量级 Excel 处理库,旨在简化 Excel 文件的读写操作。它不仅能够提高开发效率,还能显著减少内存的消耗,是处理大量 Excel 数据时的理想选择。EasyExcel 通过流式处理(streaming)来高效地读取数据,避免了一次性将整个文件加载到内存中的问题,这使得它在面对大数据量时非常高效。
EasyExcel 的主要特点总结:
- 高性能:适合处理大规模 Excel 数据。
- 低内存消耗:通过流式读取避免内存溢出。
- 易用性:简化了 Excel 的读取、写入操作,开发者可以方便地进行配置和操作。
2、如何使用EasyExcel?
1.Maven 的依赖
接下来,我们来看看如何使用 EasyExcel。首先,我们需要在项目中加入 EasyExcel 的 Maven 依赖。以下是 Maven 的依赖配置:
<!--EasyExcel-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.0.5</version>
</dependency>
2. 自定义读取器:实现 ReadListener
在实际开发过程中,通常需要自定义监听器来处理 Excel 文件读取的数据。这里自定义了一个 DataReadListener 类,它实现了 ReadListener<User> 接口,并在监听器中处理每一行读取到的数据。我们可以批量插入数据库,以提高性能。
以下是我自定义监听器的实现:
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;
import com.sdata.demo.entity.User;
import com.sdata.demo.dao.UserMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
//自定义监听器,处理读取到的Excel数据
@Component
@Slf4j
public class DataReadListener implements ReadListener<User> {
@Autowired
private UserMapper userMapper;
/**
* 每次批量插入数据的数量
*/
private static final int batchSize = 1000;
/**
* 用于暂存数据的集合,直到数量等于batchSize时就会进行插入操作并清空集合
*/
private List<User> batchList = new ArrayList();
int i=1;
//EasyExcel每读取一行数据就会执行一次
@Override
public void invoke(User user, AnalysisContext analysisContext) {
log.info("读取到的第"+ i++ +"行数据:{}", user);
batchList.add(user);
//如果集合数量大于设置的批量数量,那么就插入数据并清空集合
if (batchList.size() >= batchSize) {
userMapper.batchInsert(batchList);
batchList.clear();
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
log.info("Excel读取完成!");
//如果还有数据就一起插入到数据库中
if (!batchList.isEmpty()) {
userMapper.batchInsert(batchList);
}
}
}
代码分析:
batchList:用于缓存读取到的数据,每当数据量达到一定数量时进行批量插入。invoke方法:每读取到一行数据就调用该方法,将数据添加到batchList中。如果数据量达到批量插入的条件(BATCH_SIZE),则执行一次批量插入。doAfterAllAnalysed方法:当所有数据读取完毕后,执行剩余数据的插入操作。
通过这种方式,可以大大减少了逐行插入数据库的操作,避免了频繁的数据库连接和操作,提高了系统的性能。
3. 业务逻辑实现 :Excel 数据导入
接下来,需要在服务层实现 Excel 数据的导入功能。我将通过多线程来处理多个 sheet 的读取,进一步提升性能。
User 实体:
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
import java.util.Date;
@Data
@ExcelIgnoreUnannotated()
public class User {
@ExcelProperty("编号")
private String code;
@ExcelProperty("姓名")
private String name;
@ExcelProperty("日期")
private Date date;
}
UserMapper 内容:
import com.sdata.demo.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface UserMapper {
void batchInsert(@Param("mapList")List<User> mapList);
}
以下是 ImportExcelService 类的实现:
import com.alibaba.excel.EasyExcel;
import com.sdata.demo.entity.User;
import com.sdata.demo.listener.DataReadListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@Service
@Slf4j
public class ImportExcelService {
/**
* 导入Excel数据实现
*/
public static void importExcel() {
long startTime = System.currentTimeMillis();
//Excel路径
String path = "C:\Users\Dell\Downloads\备份\demo文件\EasyExcel导入测试.xlsx";
//读取sheet的数量
int numberSheet = 2 ;
//创建一个固定大小的线程池,大小和sheet数量一样
ExecutorService executor = Executors.newFixedThreadPool(numberSheet);
//遍历所有sheet
for (int i = 0; i < numberSheet; i++) {
//lambda表达式中的变量必须是final的
int sheetNumber = i;
//向线程池提交任务
executor.submit(() -> {
try{
//使用EasyExcel获取相对于sheet数据
EasyExcel.read(path, User.class, new DataReadListener())
.sheet(sheetNumber)//sheet数
.doRead();//开始读取数据
}catch (Exception e){
log.error(e.getMessage(),e);
}
});
}
//线程池关闭
executor.shutdown();
//等待所以任务完成读取操作
try {
executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
long endTime = System.currentTimeMillis();
System.out.println("导入时长:" + String.valueOf(endTime - startTime) + " ms");
}
//直接使用main方法运行
public static void main(String[] args) {
importExcel();
}
}
代码分析:
ExecutorService:通过线程池管理多个任务,每个任务负责读取一个 Sheet 的数据。executor.submit():为每个 Sheet 提交一个读取任务,使用 EasyExcel 异步读取数据。awaitTermination:等待所有线程任务完成后再进行后续操作,确保所有数据都已经处理完。
通过这种方式,能够充分利用多核 CPU 来并行处理多个 Sheet,大大提升了数据导入的效率,特别是在面对大数据量时表现尤为突出。
3、总结与优化
通过采用 EasyExcel 和批量插入的策略,我们成功地将 Excel 导入操作的效率提升到了一个新的高度。尤其是通过自定义 ReadListener 和并发处理多个 sheet,我们能够在处理大数据量时有效减少内存消耗并提高数据导入的速度。
对于 Excel 导入的优化,除了 EasyExcel 本身的高效读写,还可以通过控制每次批量插入的数量、合理使用线程池等手段进一步提升性能。总的来说,EasyExcel 是一个非常适合大规模数据导入的工具,尤其是在需要高效、节省内存的场景下,表现尤为突出。
优化建议:
- 合理控制批量插入的大小:通过调整批量插入的数量(如
BATCH_SIZE),可以进一步优化性能。批量过大会增加内存压力,批量过小则会增加数据库操作的次数。 - 线程池的大小:线程池的大小可以根据服务器的 CPU 核心数来调整,过多的线程会带来线程切换的开销,过少则不能充分利用多核 CPU 的优势。
希望我的学习总结能对大家在使用 EasyExcel 时有所帮助。如果你在项目中也有类似的需求,不妨试试这种方案。