導(dǎo)入excel作為日常開發(fā)中最最最常見的需求箩祥,可以簡(jiǎn)單做、也可以復(fù)雜做肆氓,也有很多很多的成形框架可以用袍祖,比如easyexcel、easypoi谢揪、jxls等等蕉陋,各有優(yōu)劣,大家可以根據(jù)業(yè)務(wù)要求進(jìn)行選擇拨扶。本文給出一個(gè)大數(shù)據(jù)量下讀取excel的示例凳鬓,若有需要,可以取源碼參考患民。
造一個(gè)100w+的示例文件
引入pom
另外使用了hutool缩举,主打一個(gè)懶人,通通加上匹颤。
<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>
定義實(shí)體類
實(shí)體類很簡(jiǎn)單仅孩,主要@ExcelProperty和excel文件中屬性對(duì)應(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("班級(jí)")
private String grade;
private String fileName;
}
編寫讀取監(jiān)聽
注意:
- 注釋里的TODO,需要根據(jù)自己的業(yè)務(wù)進(jìn)行補(bǔ)充印蓖,本文只寫了讀取excel辽慕,沒有做數(shù)據(jù)存儲(chǔ)
- 全部讀取完的回調(diào)方法里需要再保存一次,否則最后一次讀取的會(huì)漏存另伍;
- invoke方法里可以執(zhí)行自己對(duì)讀取數(shù)據(jù)的其他操作鼻百,比如補(bǔ)充其他屬性、對(duì)象轉(zhuǎn)換等等
@Slf4j
public class StudentReaderListener implements ReadListener<StudentInfo> {
/**
* 每隔5條存儲(chǔ)數(shù)據(jù)庫摆尝,實(shí)際使用中可以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,請(qǐng)使用這個(gè)構(gòu)造方法堕汞。每次創(chuàng)建Listener的時(shí)候需要把spring管理的類傳進(jìn)來
* 由于示例沒有進(jìn)行數(shù)據(jù)庫操作勺爱,這里傳了一個(gè)文件名作為額外參數(shù)示例,傳DAO方法雷同
*
* @param fileName
*/
public StudentReaderListener(String fileName) {
this.fileName = fileName;
}
/**
* 這個(gè)每一條數(shù)據(jù)解析都會(huì)來調(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 如果需要對(duì)數(shù)據(jù)設(shè)置額外參數(shù)讯检,可以在此處處理,比如創(chuàng)建人琐鲁、創(chuàng)建時(shí)間等等
data.setFileName(fileName);
cachedDataList.add(data);
// 達(dá)到BATCH_COUNT了,需要去存儲(chǔ)一次數(shù)據(jù)庫人灼,防止數(shù)據(jù)幾萬條數(shù)據(jù)在內(nèi)存围段,容易OOM
if (cachedDataList.size() >= BATCH_COUNT) {
saveData();
// 存儲(chǔ)完成清理 list
cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
/**
* 所有數(shù)據(jù)解析完成了 都會(huì)來調(diào)用
*
* @param context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 這里也要保存數(shù)據(jù),確保最后遺留的數(shù)據(jù)也存儲(chǔ)到數(shù)據(jù)庫
saveData();
log.info("所有數(shù)據(jù)解析完成投放!");
}
/**
* 加上存儲(chǔ)數(shù)據(jù)庫
*/
private void saveData() {
log.info("{}條數(shù)據(jù)奈泪,開始存儲(chǔ)數(shù)據(jù)庫!", cachedDataList.size());
//todo 執(zhí)行保存邏輯
log.info("存儲(chǔ)數(shù)據(jù)庫成功!");
}
}
main方法(可以改造到你的controller或者service)
最終讀取只需要一個(gè)文件對(duì)象即可涝桅,無論是從前端傳來的或者后臺(tái)讀取服務(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冯遂,讀取耗時(shí){}ms",file.length()/1024/1024, end - start);
}
}
讀取結(jié)果
104w數(shù)據(jù)蕊肥,16M大小,讀取耗時(shí)大約8s蛤肌。也試過47Mb大小文件壁却,帶入庫約2min30s,根據(jù)數(shù)據(jù)量大小時(shí)間會(huì)有不同寻定,但是由于此方法是一條一條讀取儒洛,然后一批一批處理,不會(huì)把所有數(shù)據(jù)加到內(nèi)存狼速,因此不會(huì)OOM,除非每批數(shù)據(jù)量設(shè)置特別大或者你的內(nèi)存特別小卦停。