導(dǎo)入excel作為日常開發(fā)中最最最常見的需求,可以簡單做、也可以復(fù)雜做,也有很多很多的成形框架可以用,比如easyexcel、easypoi、jxls等等,各有優(yōu)劣,大家可以根據(jù)業(yè)務(wù)要求進行選擇。本文給出一個大數(shù)據(jù)量下讀取excel的示例,若有需要,可以取源碼參考。
造一個100w+的示例文件

image.png
引入pom
另外使用了hutool,主打一個懶人,通通加上。
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>4.0.3</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.22</version>
</dependency>
定義實體類
實體類很簡單,主要@ExcelProperty和excel文件中屬性對應(yīng)即可
@Data
public class StudentInfo implements Serializable {
private static final long serialVersionUID = 1L;
private String id;
@ExcelProperty("姓名")
private String name;
@ExcelProperty("年齡")
private int age;
@ExcelProperty("性別")
private String gender;
@ExcelProperty("班級")
private String grade;
private String fileName;
}
編寫讀取監(jiān)聽
注意:
- 注釋里的TODO,需要根據(jù)自己的業(yè)務(wù)進行補充,本文只寫了讀取excel,沒有做數(shù)據(jù)存儲
- 全部讀取完的回調(diào)方法里需要再保存一次,否則最后一次讀取的會漏存;
- invoke方法里可以執(zhí)行自己對讀取數(shù)據(jù)的其他操作,比如補充其他屬性、對象轉(zhuǎn)換等等
@Slf4j
public class StudentReaderListener implements ReadListener<StudentInfo> {
/**
* 每隔5條存儲數(shù)據(jù)庫,實際使用中可以100條,然后清理list ,方便內(nèi)存回收
*/
private static final int BATCH_COUNT = 10000;
/**
* 緩存的數(shù)據(jù)
*/
private List<StudentInfo> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
private String fileName;
/**
* 如果使用了spring,請使用這個構(gòu)造方法。每次創(chuàng)建Listener的時候需要把spring管理的類傳進來
* 由于示例沒有進行數(shù)據(jù)庫操作,這里傳了一個文件名作為額外參數(shù)示例,傳DAO方法雷同
*
* @param fileName
*/
public StudentReaderListener(String fileName) {
this.fileName = fileName;
}
/**
* 這個每一條數(shù)據(jù)解析都會來調(diào)用
*
* @param data one row value. Is is same as {@link AnalysisContext#readRowHolder()}
* @param context
*/
@Override
public void invoke(StudentInfo data, AnalysisContext context) {
// log.info("解析到一條數(shù)據(jù):{}", JSONUtil.toJsonStr(data));
// TODO 如果需要對數(shù)據(jù)設(shè)置額外參數(shù),可以在此處處理,比如創(chuàng)建人、創(chuàng)建時間等等
data.setFileName(fileName);
cachedDataList.add(data);
// 達到BATCH_COUNT了,需要去存儲一次數(shù)據(jù)庫,防止數(shù)據(jù)幾萬條數(shù)據(jù)在內(nèi)存,容易OOM
if (cachedDataList.size() >= BATCH_COUNT) {
saveData();
// 存儲完成清理 list
cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
/**
* 所有數(shù)據(jù)解析完成了 都會來調(diào)用
*
* @param context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 這里也要保存數(shù)據(jù),確保最后遺留的數(shù)據(jù)也存儲到數(shù)據(jù)庫
saveData();
log.info("所有數(shù)據(jù)解析完成!");
}
/**
* 加上存儲數(shù)據(jù)庫
*/
private void saveData() {
log.info("{}條數(shù)據(jù),開始存儲數(shù)據(jù)庫!", cachedDataList.size());
//todo 執(zhí)行保存邏輯
log.info("存儲數(shù)據(jù)庫成功!");
}
}
main方法(可以改造到你的controller或者service)
最終讀取只需要一個文件對象即可,無論是從前端傳來的或者后臺讀取服務(wù)器上的均可。
public class EasyExcelDemo {
private static final Logger log = LoggerFactory.getLogger(EasyExcelDemo.class);
public static void main(String[] args) {
ClassLoader classLoader = EasyExcelDemo.class.getClassLoader();
URL resourceUrl = classLoader.getResource("demo/student.xlsx");
File file = new File(resourceUrl.getFile());
long start = System.currentTimeMillis();
EasyExcel.read(file.getAbsoluteFile(), StudentInfo.class,
new StudentReaderListener(file.getName()))
.sheet().doRead();
long end = System.currentTimeMillis();
log.info("文件大?。簕}Mb,讀取耗時{}ms",file.length()/1024/1024, end - start);
}
}
讀取結(jié)果
104w數(shù)據(jù),16M大小,讀取耗時大約8s。也試過47Mb大小文件,帶入庫約2min30s,根據(jù)數(shù)據(jù)量大小時間會有不同,但是由于此方法是一條一條讀取,然后一批一批處理,不會把所有數(shù)據(jù)加到內(nèi)存,因此不會OOM,除非每批數(shù)據(jù)量設(shè)置特別大或者你的內(nèi)存特別小。

image.png