注解 | 類型 | 描述 |
---|---|---|
ExcelProperty | 導(dǎo)入 | 指定當(dāng)前字段對應(yīng)excel中的那一列。可以根據(jù)名字或者Index去匹配。當(dāng)然也可以不寫,默認(rèn)第一個字段就是index=0,以此類推。千萬注意,要么全部不寫,要么全部用index,要么全部用名字去匹配。千萬別三個混著用,除非你非常了解源代碼中三個混著用怎么去排序的。 |
ExcelIgnore | 導(dǎo)入 | 默認(rèn)所有字段都會和excel去匹配,加了這個注解會忽略該字段 |
DateTimeFormat | 導(dǎo)入 | 日期轉(zhuǎn)換,用String 去接收excel日期格式的數(shù)據(jù)會調(diào)用這個注解。里面的value 參照java.text.SimpleDateFormat
|
NumberFormat | 導(dǎo)入 | 數(shù)字轉(zhuǎn)換,用String 去接收excel數(shù)字格式的數(shù)據(jù)會調(diào)用這個注解。里面的value 參照java.text.DecimalFormat
|
ExcelIgnoreUnannotated | 導(dǎo)入 | 默認(rèn)不加ExcelProperty 的注解的都會參與讀寫,加了不會參與 |
導(dǎo)入方法參數(shù):ReadWorkbook
,ReadSheet
都會有的參數(shù),如果為空,默認(rèn)使用上級。
-
converter
轉(zhuǎn)換器,默認(rèn)加載了很多轉(zhuǎn)換器。也可以自定義。 -
readListener
監(jiān)聽器,在讀取數(shù)據(jù)的過程中會不斷的調(diào)用監(jiān)聽器。 -
headRowNumber
需要讀的表格有幾行頭數(shù)據(jù)。默認(rèn)有一行頭,也就是認(rèn)為第二行開始起為數(shù)據(jù)。 -
head
與clazz
二選一。讀取文件頭對應(yīng)的列表,會根據(jù)列表匹配數(shù)據(jù),建議使用class。 -
clazz
與head
二選一。讀取文件的頭對應(yīng)的class,也可以使用注解。如果兩個都不指定,則會讀取全部數(shù)據(jù)。 -
autoTrim
字符串、表頭等數(shù)據(jù)自動trim - password 讀的時候是否需要使用密碼
ReadWorkbook(理解成excel對象)參數(shù)
-
excelType
當(dāng)前excel的類型 默認(rèn)會自動判斷 -
inputStream
與file
二選一。讀取文件的流,如果接收到的是流就只用,不用流建議使用file
參數(shù)。因?yàn)槭褂昧?code>inputStream easyexcel會幫忙創(chuàng)建臨時文件,最終還是file
-
file
與inputStream
二選一。讀取文件的文件。 -
autoCloseStream
自動關(guān)閉流。 -
readCache
默認(rèn)小于5M用 內(nèi)存,超過5M會使用EhCache
,這里不建議使用這個參數(shù)。 -
useDefaultListener@since 2.1.4默認(rèn)會加入ModelBuildEventListener來幫忙轉(zhuǎn)換成傳入class的對象,設(shè)置成false后將不會協(xié)助轉(zhuǎn)換對象,自定義的監(jiān)聽器會接收到Map<Integer,CellData>對象,如果還想繼續(xù)接聽到
class對象,請調(diào)用readListener方法,加入自定義的beforeListener、ModelBuildEventListener、 自定義afterListener即可。
ReadSheet(就是excel的一個Sheet)參數(shù)
-
sheetNo
需要讀取Sheet的編碼,建議使用這個來指定讀取哪個Sheet -
sheetName
根據(jù)名字去匹配Sheet,excel 2003不支持根據(jù)名字去匹配
添加pom依賴
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.2.6</version>
</dependency>
<!--工具類-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.23</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.21</version>
</dependency>
<!--commons依賴 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.7</version>
</dependency>
第一種:簡單導(dǎo)入
實(shí)體類
package com.example.mybatismysql8demo.excel;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
import java.math.BigDecimal;
@Data
//忽視無注解的字段
@ExcelIgnoreUnannotated
public class GoodsImportExcel {
/**
* 用名字去匹配,這里需要注意,如果名字重復(fù),會導(dǎo)致只有一個字段讀取到數(shù)據(jù)
*/
@ExcelProperty(value = {"商品信息","商品名稱"},index = 0)
public String goodsName;
@ExcelProperty(value = {"商品信息","商品價格"},index = 1)
public BigDecimal price;
@ExcelProperty(value = {"商品信息","商品數(shù)量"},index = 2)
public Integer num;
}
監(jiān)聽器
package com.example.mybatismysql8demo.handler;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.example.mybatismysql8demo.excel.GoodsImportExcel;
import com.google.common.collect.Lists;
import org.apache.commons.collections4.CollectionUtils;
import java.util.List;
import java.util.function.Consumer;
/**
* 讀取excel數(shù)據(jù)
*/
public class DemoDataListener extends AnalysisEventListener<GoodsImportExcel> {
/**臨時存儲正常數(shù)據(jù)集合,最大存儲100*/
private List<GoodsImportExcel> successDataList = Lists.newArrayListWithExpectedSize(100);
/**自定義消費(fèi)者函數(shù)接口用于自定義監(jiān)聽器中數(shù)據(jù)組裝*/
private final Consumer<List<GoodsImportExcel>> successConsumer;
public DemoDataListener(Consumer<List<GoodsImportExcel>> successConsumer) {
this.successConsumer = successConsumer;
}
@Override
public void invoke(GoodsImportExcel goodsImportExcel, AnalysisContext analysisContext) {
successDataList.add(goodsImportExcel);
System.out.println("數(shù)據(jù):"+goodsImportExcel);
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
if (CollectionUtils.isNotEmpty(successDataList)) {
successConsumer.accept(successDataList);
}
}
}
執(zhí)行方法
package com.example.mybatismysql8demo.controller;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.read.metadata.ReadSheet;
import com.alibaba.fastjson.JSONObject;
import com.example.mybatismysql8demo.excel.GoodsImportExcel;
import com.example.mybatismysql8demo.handler.DemoDataListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.util.*;
@Slf4j
@RestController
public class EasyExcelController {
@PostMapping("/easyExcelImport")
public void importExcel(MultipartFile file,Integer type) {
if (!file.isEmpty()) {
//文件名稱
int begin = Objects.requireNonNull(file.getOriginalFilename()).indexOf(".");
//文件名稱長度
int last = file.getOriginalFilename().length();
//判斷文件格式是否正確
String fileName = file.getOriginalFilename().substring(begin, last);
if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
throw new IllegalArgumentException("上傳文件格式錯誤");
}
} else {
throw new IllegalArgumentException("文件不能為空");
}
try (InputStream inputStream = file.getInputStream()) {
if (type == 1){
simpleRead(inputStream);
}else if (type == 2){
synchronousRead(inputStream);
}else {
repeatedRead(inputStream);
}
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
/**
* 最簡單的讀的監(jiān)聽器
*/
public void simpleRead(InputStream inputStream){
//獲取正確數(shù)據(jù)
ArrayList<GoodsImportExcel> successArrayList = new ArrayList<>();
EasyExcel.read(inputStream)
.head(GoodsImportExcel.class)
.registerReadListener(new DemoDataListener(
// 監(jiān)聽器中doAfterAllAnalysed執(zhí)行此方法;所有讀取完成之后處理邏輯
successArrayList::addAll))
// 設(shè)置sheet,默認(rèn)讀取第一個
.sheet()
// 設(shè)置標(biāo)題(字段列表)所在行數(shù)
.headRowNumber(2)
.doReadSync();
System.out.println(successArrayList);
}
/**
* 同步的返回,不推薦使用,如果數(shù)據(jù)量大會把數(shù)據(jù)放到內(nèi)存里面
*/
public void synchronousRead(InputStream inputStream){
// 這里 需要指定讀用哪個class去讀,然后讀取第一個sheet 同步讀取會自動finish
List<GoodsImportExcel> batchGoodsImportModels = EasyExcel.read(inputStream)
.head(GoodsImportExcel.class)
// 設(shè)置sheet,默認(rèn)讀取第一個
.sheet()
// 設(shè)置標(biāo)題(字段列表)所在行數(shù)
.headRowNumber(2)
.doReadSync();
System.out.println(JSONObject.toJSONString(batchGoodsImportModels));
}
/**
* 讀取多個sheet
*/
public void repeatedRead(InputStream inputStream){
ArrayList<GoodsImportExcel> successArrayList = new ArrayList<>();
//使用模型來讀取Excel(多個sheet)
ExcelReader reader = EasyExcel.read(inputStream).build();
//多個sheet
List<ReadSheet> sheetList = new ArrayList<>();
for (int i = 0; i < reader.getSheets().size(); i++){
// 這里為了簡單,所以注冊了同樣的head 和Listener 自己使用功能必須不同的Listener
ReadSheet readSheet = EasyExcel.readSheet(i)
.head(GoodsImportExcel.class)
.registerReadListener(new DemoDataListener(successArrayList::addAll))
// 設(shè)置標(biāo)題(字段列表)所在行數(shù)
.headRowNumber(2)
.build();
sheetList.add(readSheet);
}
// 這里注意 一定要把sheet1 sheet2 一起傳進(jìn)去,不然有個問題就是03版的excel 會讀取多次,浪費(fèi)性能
reader.read(sheetList);
// 這里千萬別忘記關(guān)閉,讀的時候會創(chuàng)建臨時文件,到時磁盤會崩的
reader.finish();
System.out.println(successArrayList);
}
}
結(jié)果打印
數(shù)據(jù):GoodsImportExcel(goodsName=蘋果, price=10, num=11)
數(shù)據(jù):GoodsImportExcel(goodsName=香蕉, price=8, num=12)
數(shù)據(jù):GoodsImportExcel(goodsName=梨子, price=11.0, num=30)
數(shù)據(jù):GoodsImportExcel(goodsName=葡萄, price=20.0, num=40)
[GoodsImportExcel(goodsName=蘋果, price=10, num=11), GoodsImportExcel(goodsName=香蕉, price=8, num=12), GoodsImportExcel(goodsName=梨子, price=11.0, num=30), GoodsImportExcel(goodsName=葡萄, price=20.0, num=40)]
導(dǎo)入模版
第二種:數(shù)據(jù)校驗(yàn)
自定義注解
package com.example.mybatismysql8demo.config;
import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface LengthValid {
int length() default 0;
String msg() default "";
int cell() default 0;
}
實(shí)體類
package com.example.mybatismysql8demo.excel;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import com.example.mybatismysql8demo.config.LengthValid;
import lombok.Data;
import java.math.BigDecimal;
@Data
//忽視無注解的字段
@ExcelIgnoreUnannotated
public class GoodsImportExcel {
/**
* 用名字去匹配,這里需要注意,如果名字重復(fù),會導(dǎo)致只有一個字段讀取到數(shù)據(jù)
*/
@LengthValid(length = 5,msg = "商品名稱長度超出5個字符串!",cell = 1)
@ExcelProperty(value = {"商品信息","商品名稱"},index = 0)
public String goodsName;
@ExcelProperty(value = {"商品信息","商品價格"},index = 1)
public BigDecimal price;
@ExcelProperty(value = {"商品信息","商品數(shù)量"},index = 2)
public Integer num;
private String errorMsg;
}
監(jiān)聽器
package com.example.mybatismysql8demo.handler;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.CellExtra;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.api.Assert;
import com.example.mybatismysql8demo.config.LengthValid;
import com.example.mybatismysql8demo.excel.GoodsImportExcel;
import com.google.common.collect.Lists;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
/**
* 讀取excel數(shù)據(jù)
*/
@Slf4j
public class DemoDataListener extends AnalysisEventListener<GoodsImportExcel> {
/**單次處理上限100條記錄*/
private static final int BATCH_COUNT = 100;
/**臨時存儲正常數(shù)據(jù)集合*/
private List<GoodsImportExcel> successDataList = Lists.newArrayListWithExpectedSize(BATCH_COUNT);
/**臨時存錯誤儲數(shù)據(jù)集合*/
private List<GoodsImportExcel> errorDataList = Lists.newArrayListWithExpectedSize(BATCH_COUNT);
/**自定義消費(fèi)者函數(shù)接口用于自定義監(jiān)聽器中數(shù)據(jù)組裝*/
private final Consumer<List<GoodsImportExcel>> successConsumer;
private final Consumer<List<GoodsImportExcel>> errorConsumer;
public DemoDataListener(Consumer<List<GoodsImportExcel>> successConsumer, Consumer<List<GoodsImportExcel>> errorConsumer) {
this.successConsumer = successConsumer;
this.errorConsumer = errorConsumer;
}
/**手機(jī)號格式異常日志處理*/
@Override
public void onException(Exception exception, AnalysisContext context) {
log.error("異常信息:{}", exception.getMessage());
// 如果是某一個單元格的轉(zhuǎn)換異常 能獲取到具體行號,如果要獲取頭的信息 配合invokeHeadMap使用
if (exception instanceof ExcelDataConvertException) {
ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException) exception;
log.error("第{}行,第{}列解析異常,數(shù)據(jù)為:{}", excelDataConvertException.getRowIndex(), excelDataConvertException.getColumnIndex(), excelDataConvertException.getCellData());
}else if (exception instanceof IllegalArgumentException){
throw new IllegalArgumentException(exception.getMessage());
}
}
/**
* 在這里進(jìn)行模板的判斷
* @param headMap 存放著導(dǎo)入表格的表頭,鍵是索引,值是名稱
* @param context
*/
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
//只校驗(yàn)第三行表頭是否正確
Integer rowNum = context.getCurrentRowNum();
if (rowNum == 2) {
// 獲取數(shù)據(jù)實(shí)體的字段列表
Field[] fields = GoodsImportExcel.class.getDeclaredFields();
// 遍歷字段進(jìn)行判斷
for (Field field : fields) {
// 獲取當(dāng)前字段上的ExcelProperty注解信息
ExcelProperty fieldAnnotation = field.getAnnotation(ExcelProperty.class);
// 判斷當(dāng)前字段上是否存在ExcelProperty注解
if (fieldAnnotation != null) {
String value = fieldAnnotation.value()[1];
// 存在ExcelProperty注解則根據(jù)注解的value值到表格中對比是否存在對應(yīng)的表頭
if(!headMap.containsValue(value)){
// 如果表格不包含模版類字段中的表頭,則拋出異常不再往下執(zhí)行
throw new RuntimeException("模板錯誤,請檢查導(dǎo)入模板");
}
}
}
}
}
/**每行讀取監(jiān)聽觸發(fā)邏輯*/
@SneakyThrows
@Override
public void invoke(GoodsImportExcel goodsImportExcel, AnalysisContext analysisContext) {
//獲取總行數(shù)
Integer rowNumber = analysisContext.readSheetHolder().getApproximateTotalRowNumber();
//行數(shù)
int row = analysisContext.readRowHolder().getRowIndex();
log.info("第" + row + "行數(shù)據(jù)進(jìn)行處理");
// 手機(jī)號格式校驗(yàn)
validParam(goodsImportExcel,row);
//正常數(shù)據(jù)
successDataList.add(goodsImportExcel);
// 按照指定條數(shù)對導(dǎo)入數(shù)據(jù)進(jìn)行分批處理
if (successDataList.size() >= BATCH_COUNT) {
successConsumer.accept(successDataList);
successDataList = Lists.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
private void validParam(GoodsImportExcel goodsImportExcel, int row) throws IllegalAccessException {
// 參數(shù)校驗(yàn)
Field[] fields = goodsImportExcel.getClass().getDeclaredFields();
for (Field field : fields) {
//設(shè)置可訪問
field.setAccessible(true);
//判斷字段是否添加校驗(yàn)
boolean valid = field.isAnnotationPresent(LengthValid.class);
if (valid) {
//獲取注解信息
LengthValid annotation = field.getAnnotation(LengthValid.class);
//行數(shù)列數(shù)
String msg = "第" + row + "行的第" + annotation.cell() + "列:";
//值
String value = (String) field.get(goodsImportExcel);
if(value.length() > annotation.length()){
//錯誤信息
goodsImportExcel.setErrorMsg(msg + annotation.msg());
//錯誤數(shù)據(jù)
errorDataList.add(goodsImportExcel);
// 按照指定條數(shù)對導(dǎo)入數(shù)據(jù)進(jìn)行分批處理
if (errorDataList.size() >= BATCH_COUNT) {
errorConsumer.accept(errorDataList);
errorDataList = Lists.newArrayListWithExpectedSize(BATCH_COUNT);
}
throw new RuntimeException(msg + annotation.msg());
}
}
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
if (CollectionUtils.isNotEmpty(successDataList)) {
successConsumer.accept(successDataList);
}
if (CollectionUtils.isNotEmpty(errorDataList)) {
errorConsumer.accept(errorDataList);
}
}
/**
* 額外信息(批注、超鏈接、合并單元格信息讀取)
*/
@Override
public void extra(CellExtra extra, AnalysisContext context) {
log.info("讀取到了一條額外信息:{}", JSONObject.toJSONString(extra));
switch (extra.getType()) {
case COMMENT:
log.info("額外信息是批注,在rowIndex:{},columnIndex;{},內(nèi)容是:{}", extra.getRowIndex(), extra.getColumnIndex(), extra.getText());
break;
case HYPERLINK:
if ("Sheet1!A1".equals(extra.getText())) {
log.info("額外信息是超鏈接,在rowIndex:{},columnIndex;{},內(nèi)容是:{}", extra.getRowIndex(), extra.getColumnIndex(), extra.getText());
} else if ("Sheet2!A1".equals(extra.getText())) {
log.info("額外信息是超鏈接,而且覆蓋了一個區(qū)間,在firstRowIndex:{},firstColumnIndex;{},lastRowIndex:{},lastColumnIndex:{}," + "內(nèi)容是:{}",
extra.getFirstRowIndex(), extra.getFirstColumnIndex(), extra.getLastRowIndex(),
extra.getLastColumnIndex(), extra.getText());
} else {
Assert.fail("Unknown hyperlink!");
}
break;
case MERGE:
log.info("額外信息是超鏈接,而且覆蓋了一個區(qū)間,在firstRowIndex:{},firstColumnIndex;{},lastRowIndex:{},lastColumnIndex:{}",
extra.getFirstRowIndex(), extra.getFirstColumnIndex(), extra.getLastRowIndex(),
extra.getLastColumnIndex());
break;
default:
}
}
/**
*監(jiān)聽器的hasNext()方法時沒有注意到默認(rèn)返回的是false,導(dǎo)致一進(jìn)監(jiān)聽器就判斷已經(jīng)沒有下一條記錄,直接跳出監(jiān)聽器,然后導(dǎo)入就完成,也不會報錯,改成返回true即可解決
*/
@Override
public boolean hasNext(AnalysisContext analysisContext) {
return true;
}
}
執(zhí)行方法
package com.example.mybatismysql8demo.controller;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.read.metadata.ReadSheet;
import com.alibaba.fastjson.JSONObject;
import com.example.mybatismysql8demo.excel.GoodsImportExcel;
import com.example.mybatismysql8demo.handler.DemoDataListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.util.*;
@Slf4j
@RestController
public class EasyExcelController {
@PostMapping("/easyExcelImport")
public void importExcel(MultipartFile file,Integer type) {
if (!file.isEmpty()) {
//文件名稱
int begin = Objects.requireNonNull(file.getOriginalFilename()).indexOf(".");
//文件名稱長度
int last = file.getOriginalFilename().length();
//判斷文件格式是否正確
String fileName = file.getOriginalFilename().substring(begin, last);
if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
throw new IllegalArgumentException("上傳文件格式錯誤");
}
} else {
throw new IllegalArgumentException("文件不能為空");
}
try (InputStream inputStream = file.getInputStream()) {
if (type == 1){
simpleRead(inputStream);
}else if (type == 2){
synchronousRead(inputStream);
}else {
repeatedRead(inputStream);
}
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
/**
* 最簡單的讀的監(jiān)聽器
*/
public void simpleRead(InputStream inputStream){
//獲取正確數(shù)據(jù)
ArrayList<GoodsImportExcel> successArrayList = new ArrayList<>();
//獲取錯誤數(shù)據(jù)
ArrayList<GoodsImportExcel> errorArrayList = new ArrayList<>();
EasyExcel.read(inputStream)
.head(GoodsImportExcel.class)
.registerReadListener(new DemoDataListener(
// 監(jiān)聽器中doAfterAllAnalysed執(zhí)行此方法;所有讀取完成之后處理邏輯
successArrayList::addAll, errorArrayList::addAll))
// 設(shè)置sheet,默認(rèn)讀取第一個
.sheet()
// 設(shè)置標(biāo)題(字段列表)所在行數(shù)
.headRowNumber(2)
.doReadSync();
System.out.println(successArrayList);
System.out.println(errorArrayList);
}
/**
* 同步的返回,不推薦使用,如果數(shù)據(jù)量大會把數(shù)據(jù)放到內(nèi)存里面
*/
public void synchronousRead(InputStream inputStream){
// 這里 需要指定讀用哪個class去讀,然后讀取第一個sheet 同步讀取會自動finish
List<GoodsImportExcel> batchGoodsImportModels = EasyExcel.read(inputStream)
.head(GoodsImportExcel.class)
// 設(shè)置sheet,默認(rèn)讀取第一個
.sheet()
// 設(shè)置標(biāo)題(字段列表)所在行數(shù)
.headRowNumber(2)
.doReadSync();
System.out.println(JSONObject.toJSONString(batchGoodsImportModels));
}
/**
* 讀取多個sheet
*/
public void repeatedRead(InputStream inputStream){
ArrayList<GoodsImportExcel> successArrayList = new ArrayList<>();
//獲取錯誤數(shù)據(jù)
ArrayList<GoodsImportExcel> errorArrayList = new ArrayList<>();
//使用模型來讀取Excel(多個sheet)
ExcelReader reader = EasyExcel.read(inputStream).build();
//多個sheet
List<ReadSheet> sheetList = new ArrayList<>();
for (int i = 0; i < reader.getSheets().size(); i++){
// 這里為了簡單,所以注冊了同樣的head 和Listener 自己使用功能必須不同的Listener
ReadSheet readSheet = EasyExcel.readSheet(i)
.head(GoodsImportExcel.class)
.registerReadListener(new DemoDataListener(successArrayList::addAll, errorArrayList::addAll))
// 設(shè)置標(biāo)題(字段列表)所在行數(shù)
.headRowNumber(2)
.build();
sheetList.add(readSheet);
}
// 這里注意 一定要把sheet1 sheet2 一起傳進(jìn)去,不然有個問題就是03版的excel 會讀取多次,浪費(fèi)性能
reader.read(sheetList);
// 這里千萬別忘記關(guān)閉,讀的時候會創(chuàng)建臨時文件,到時磁盤會崩的
reader.finish();
System.out.println(successArrayList);
System.out.println(errorArrayList);
}
}
結(jié)果打印
[GoodsImportExcel(goodsName=蘋果, price=10, num=11, errorMsg=null), GoodsImportExcel(goodsName=香蕉, price=8, num=12, errorMsg=null), GoodsImportExcel(goodsName=葡萄, price=20.0, num=40, errorMsg=null)]
[GoodsImportExcel(goodsName=梨子1111, price=11.0, num=30, errorMsg=第2行的第1列:商品名稱長度超出5個字符串!)]
導(dǎo)入模版
第三種:讀取存在合并
監(jiān)聽器
package com.example.mybatismysql8demo.handler;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.metadata.CellExtra;
import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* Excel模板的讀取監(jiān)聽類
* @author gd
*/
public class ImportExcelListener<T> extends AnalysisEventListener<T> {
private static final Logger LOGGER = LoggerFactory.getLogger(ImportExcelListener.class);
/**
* 解析的數(shù)據(jù)
*/
private final List<T> list = new ArrayList<>();
/**
* 正文起始行
*/
private final Integer headRowNumber;
/**
* 合并單元格
*/
private final List<CellExtra> extraMergeInfoList = new ArrayList<>();
public ImportExcelListener(Integer headRowNumber) {
this.headRowNumber = headRowNumber;
}
/**
* 這個每一條數(shù)據(jù)解析都會來調(diào)用
*/
@Override
public void invoke(T data, AnalysisContext context) {
LOGGER.info("數(shù)據(jù)處理: " + JSON.toJSONString(data));
list.add(data);
}
/**
* 所有數(shù)據(jù)解析完成了 都會來調(diào)用
* @param context context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
LOGGER.info("所有數(shù)據(jù)解析完成!");
}
/**
* 返回解析出來的List
*/
public List<T> getData() {
return list;
}
/**
* 讀取額外信息:合并單元格
*/
@Override
public void extra(CellExtra extra, AnalysisContext context) {
LOGGER.info("讀取到了一條額外信息:{}", JSON.toJSONString(extra));
switch (extra.getType()) {
case COMMENT:
LOGGER.info("額外信息是批注,在rowIndex:{},columnIndex;{},內(nèi)容是:{}", extra.getRowIndex(), extra.getColumnIndex(), extra.getText());
break;
case MERGE: {
LOGGER.info(
"額外信息是合并單元格,而且覆蓋了一個區(qū)間,在firstRowIndex:{},firstColumnIndex;{},lastRowIndex:{},lastColumnIndex:{}",
extra.getFirstRowIndex(), extra.getFirstColumnIndex(), extra.getLastRowIndex(), extra.getLastColumnIndex());
if (extra.getRowIndex() >= headRowNumber) {
extraMergeInfoList.add(extra);
}
break;
}
default:
}
}
/**
* 返回解析出來的合并單元格List
*/
public List<CellExtra> getExtraMergeInfoList() {
return extraMergeInfoList;
}
}
合并數(shù)據(jù)處理工具類
package com.example.mybatismysql8demo.utils;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.enums.CellExtraTypeEnum;
import com.alibaba.excel.metadata.CellExtra;
import com.alibaba.excel.util.CollectionUtils;
import com.example.mybatismysql8demo.config.LengthValid;
import com.example.mybatismysql8demo.excel.GoodsImportExcel;
import com.example.mybatismysql8demo.handler.ImportExcelListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.List;
public class ImportExcelMergeUtil<T> {
private static final Logger LOGGER = LoggerFactory.getLogger(ImportExcelMergeUtil.class);
/**
* 返回解析后的List
*
* @param: fileName 文件名
* @param: clazz Excel對應(yīng)屬性名
* @param: sheetNo 要解析的sheet
* @param: headRowNumber 正文起始行
* @return java.util.List<T> 解析后的List
*/
public void getList(InputStream inputStream, Class<GoodsImportExcel> clazz, Integer sheetNo, Integer headRowNumber,List<T> successList,List<T> errorList) {
ImportExcelListener<T> listener = new ImportExcelListener<>(headRowNumber);
try {
EasyExcel.read(inputStream, clazz, listener).extraRead(CellExtraTypeEnum.MERGE).sheet(sheetNo).headRowNumber(headRowNumber).doRead();
} catch (Exception e) {
LOGGER.error(e.getMessage());
}
List<CellExtra> extraMergeInfoList = listener.getExtraMergeInfoList();
//解析數(shù)據(jù)
List<T> list;
if (CollectionUtils.isEmpty(extraMergeInfoList)) {
list = (listener.getData());
}else {
list = explainMergeData(listener.getData(), extraMergeInfoList, headRowNumber);
}
//數(shù)據(jù)處理
for (T v : list) {
if(validParam(v)){
errorList.add(v);
}else {
successList.add(v);
}
}
}
private Boolean validParam(T object){
// 參數(shù)校驗(yàn)
Field[] fields = object.getClass().getDeclaredFields();
for (Field field : fields) {
//設(shè)置可訪問
field.setAccessible(true);
//判斷字段是否添加校驗(yàn)
boolean valid = field.isAnnotationPresent(LengthValid.class);
if (valid) {
try {
//獲取注解信息
LengthValid annotation = field.getAnnotation(LengthValid.class);
//值
String value = (String) field.get(object);
if(value.length() > annotation.length()){
//錯誤信息(需要設(shè)置字段為public)
Field errorMsg = object.getClass().getField("errorMsg");
if (errorMsg.get(object) == null){
errorMsg.set(object, annotation.msg());
}else {
errorMsg.set(object,errorMsg.get(object) + "," + annotation.msg());
}
return true;
}
} catch (IllegalAccessException | NoSuchFieldException e) {
e.printStackTrace();
}
}
}
return false;
}
/**
* 處理合并單元格
* @param data 解析數(shù)據(jù)
* @param extraMergeInfoList 合并單元格信息
* @param headRowNumber 起始行
* @return 填充好的解析數(shù)據(jù)
*/
private List<T> explainMergeData(List<T> data, List<CellExtra> extraMergeInfoList, Integer headRowNumber) {
//循環(huán)所有合并單元格信息
extraMergeInfoList.forEach(cellExtra -> {
int firstRowIndex = cellExtra.getFirstRowIndex() - headRowNumber;
int lastRowIndex = cellExtra.getLastRowIndex() - headRowNumber;
int firstColumnIndex = cellExtra.getFirstColumnIndex();
int lastColumnIndex = cellExtra.getLastColumnIndex();
//獲取初始值
Object initValue = getInitValueFromList(firstRowIndex, firstColumnIndex, data);
//設(shè)置值
for (int i = firstRowIndex; i <= lastRowIndex; i++) {
for (int j = firstColumnIndex; j <= lastColumnIndex; j++) {
setInitValueToList(initValue, i, j, data);
}
}
});
return data;
}
/**
* 設(shè)置合并單元格的值
*
* @param filedValue 值
* @param rowIndex 行
* @param columnIndex 列
* @param data 解析數(shù)據(jù)
*/
private void setInitValueToList(Object filedValue, Integer rowIndex, Integer columnIndex, List<T> data) {
T object = data.get(rowIndex);
for (Field field : object.getClass().getDeclaredFields()) {
//提升反射性能,關(guān)閉安全檢查
field.setAccessible(true);
ExcelProperty annotation = field.getAnnotation(ExcelProperty.class);
if (annotation != null) {
if (annotation.index() == columnIndex) {
try {
field.set(object, filedValue);
break;
} catch (IllegalAccessException e) {
LOGGER.error("設(shè)置合并單元格的值異常:"+e.getMessage());
}
}
}
}
}
/**
* 獲取合并單元格的初始值
* rowIndex對應(yīng)list的索引
* columnIndex對應(yīng)實(shí)體內(nèi)的字段
* @param firstRowIndex 起始行
* @param firstColumnIndex 起始列
* @param data 列數(shù)據(jù)
* @return 初始值
*/
private Object getInitValueFromList(Integer firstRowIndex, Integer firstColumnIndex, List<T> data) {
Object filedValue = null;
T object = data.get(firstRowIndex);
for (Field field : object.getClass().getDeclaredFields()) {
//提升反射性能,關(guān)閉安全檢查
field.setAccessible(true);
ExcelProperty annotation = field.getAnnotation(ExcelProperty.class);
if (annotation != null) {
if (annotation.index() == firstColumnIndex) {
try {
filedValue = field.get(object);
break;
} catch (IllegalAccessException e) {
LOGGER.error("獲取合并單元格的初始值異常:"+e.getMessage());
}
}
}
}
return filedValue;
}
}
執(zhí)行方法文章來源:http://www.zghlxwxcb.cn/news/detail-859882.html
package com.example.mybatismysql8demo.controller;
import com.alibaba.fastjson.JSONObject;
import com.example.mybatismysql8demo.excel.GoodsImportExcel;
import com.example.mybatismysql8demo.utils.ImportExcelMergeUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.util.*;
@Slf4j
@RestController
public class EasyExcelController {
@PostMapping("/easyExcelImport")
public void importExcel(MultipartFile file) {
if (!file.isEmpty()) {
//文件名稱
int begin = Objects.requireNonNull(file.getOriginalFilename()).indexOf(".");
//文件名稱長度
int last = file.getOriginalFilename().length();
//判斷文件格式是否正確
String fileName = file.getOriginalFilename().substring(begin, last);
if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
throw new IllegalArgumentException("上傳文件格式錯誤");
}
} else {
throw new IllegalArgumentException("文件不能為空");
}
ImportExcelMergeUtil<GoodsImportExcel> helper = new ImportExcelMergeUtil<>();
List<GoodsImportExcel> successList = new ArrayList<>();
List<GoodsImportExcel> errorList = new ArrayList<>();
try {
helper.getList(file.getInputStream(), GoodsImportExcel.class,0,2,successList,errorList);
System.out.println("正確數(shù)據(jù):"+successList);
System.out.println("錯誤數(shù)據(jù):"+errorList);
} catch (IOException e) {
e.printStackTrace();
}
}
}
結(jié)果打印
正確數(shù)據(jù):[GoodsImportExcel(goodsName=香蕉, price=8, num=12, errorMsg=null)]
錯誤數(shù)據(jù):[GoodsImportExcel(goodsName=蘋果11111, price=10, num=11, errorMsg=商品名稱長度超出5個字符串!), GoodsImportExcel(goodsName=蘋果11111, price=9.0, num=20, errorMsg=商品名稱長度超出5個字符串!)]
導(dǎo)入模版文章來源地址http://www.zghlxwxcb.cn/news/detail-859882.html
到了這里,關(guān)于JAVA實(shí)現(xiàn)easyExcel批量導(dǎo)入的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!