介紹
快速開(kāi)始
引入依賴(lài)
簡(jiǎn)單導(dǎo)出
定義實(shí)體類(lèi)
復(fù)雜導(dǎo)出
簡(jiǎn)單導(dǎo)入
參考資料
介紹
EasyExcel 是一個(gè)基于 Java 的、快速、簡(jiǎn)潔、解決大文件內(nèi)存溢出的 Excel 處理工具。它能讓你在不用考慮性能、內(nèi)存的等因素的情況下,快速完成 Excel 的讀、寫(xiě)等功能。
EasyExcel文檔地址:
https://easyexcel.opensource.alibaba.com/
基于 Spring Boot + MyBatis Plus + Vue & Element 實(shí)現(xiàn)的后臺(tái)管理系統(tǒng) + 用戶(hù)小程序,支持 RBAC 動(dòng)態(tài)權(quán)限、多租戶(hù)、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能
項(xiàng)目地址:https://github.com/YunaiV/ruoyi-vue-pro
視頻教程:https://doc.iocoder.cn/video/
快速開(kāi)始
引入依賴(lài)
com.alibaba easyexcel 3.1.3
簡(jiǎn)單導(dǎo)出
以導(dǎo)出用戶(hù)信息為例,接下來(lái)手把手教大家如何使用EasyExcel實(shí)現(xiàn)導(dǎo)出功能!
定義實(shí)體類(lèi)
在EasyExcel中,以面向?qū)ο笏枷雭?lái)實(shí)現(xiàn)導(dǎo)入導(dǎo)出,無(wú)論是導(dǎo)入數(shù)據(jù)還是導(dǎo)出數(shù)據(jù)都可以想象成具體某個(gè)對(duì)象的集合,所以為了實(shí)現(xiàn)導(dǎo)出用戶(hù)信息功能,首先創(chuàng)建一個(gè)用戶(hù)對(duì)象UserDO實(shí)體類(lèi),用于封裝用戶(hù)信息:
/** *用戶(hù)信息 * *@authorwilliam@StarImmortal */ @Data publicclassUserDO{ @ExcelProperty("用戶(hù)編號(hào)") @ColumnWidth(20) privateLongid; @ExcelProperty("用戶(hù)名") @ColumnWidth(20) privateStringusername; @ExcelIgnore privateStringpassword; @ExcelProperty("昵稱(chēng)") @ColumnWidth(20) privateStringnickname; @ExcelProperty("生日") @ColumnWidth(20) @DateTimeFormat("yyyy-MM-dd") privateDatebirthday; @ExcelProperty("手機(jī)號(hào)") @ColumnWidth(20) privateStringphone; @ExcelProperty("身高(米)") @NumberFormat("#.##") @ColumnWidth(20) privateDoubleheight; @ExcelProperty(value="性別",converter=GenderConverter.class) @ColumnWidth(10) privateIntegergender; }
上面代碼中類(lèi)屬性上使用了EasyExcel核心注解:
@ExcelProperty: 核心注解,value屬性可用來(lái)設(shè)置表頭名稱(chēng),converter屬性可以用來(lái)設(shè)置類(lèi)型轉(zhuǎn)換器;
@ColumnWidth: 用于設(shè)置表格列的寬度;
@DateTimeFormat: 用于設(shè)置日期轉(zhuǎn)換格式;
@NumberFormat: 用于設(shè)置數(shù)字轉(zhuǎn)換格式。
自定義轉(zhuǎn)換器
在EasyExcel中,如果想實(shí)現(xiàn)枚舉類(lèi)型到字符串類(lèi)型轉(zhuǎn)換(例如gender屬性:1 -> 男,2 -> 女),需實(shí)現(xiàn)Converter接口來(lái)自定義轉(zhuǎn)換器,下面為自定義GenderConverter性別轉(zhuǎn)換器代碼實(shí)現(xiàn):
/** *Excel性別轉(zhuǎn)換器 * *@authorwilliam@StarImmortal */ publicclassGenderConverterimplementsConverter{ @Override publicClass>supportJavaTypeKey(){ returnInteger.class; } @Override publicCellDataTypeEnumsupportExcelTypeKey(){ returnCellDataTypeEnum.STRING; } @Override publicIntegerconvertToJavaData(ReadConverterContext>context){ returnGenderEnum.convert(context.getReadCellData().getStringValue()).getValue(); } @Override publicWriteCellData>convertToExcelData(WriteConverterContext context){ returnnewWriteCellData<>(GenderEnum.convert(context.getValue()).getDescription()); } } /** *性別枚舉 * *@authorwilliam@StarImmortal */ @Getter @AllArgsConstructor publicenumGenderEnum{ /** *未知 */ UNKNOWN(0,"未知"), /** *男性 */ MALE(1,"男性"), /** *女性 */ FEMALE(2,"女性"); privatefinalIntegervalue; @JsonFormat privatefinalStringdescription; publicstaticGenderEnumconvert(Integervalue){ returnStream.of(values()) .filter(bean->bean.value.equals(value)) .findAny() .orElse(UNKNOWN); } publicstaticGenderEnumconvert(Stringdescription){ returnStream.of(values()) .filter(bean->bean.description.equals(description)) .findAny() .orElse(UNKNOWN); } }
定義接口
/** *EasyExcel導(dǎo)入導(dǎo)出 * *@authorwilliam@StarImmortal */ @RestController @RequestMapping("/excel") publicclassExcelController{ @GetMapping("/export/user") publicvoidexportUserExcel(HttpServletResponseresponse){ try{ this.setExcelResponseProp(response,"用戶(hù)列表"); ListuserList=this.getUserList(); EasyExcel.write(response.getOutputStream()) .head(UserDO.class) .excelType(ExcelTypeEnum.XLSX) .sheet("用戶(hù)列表") .doWrite(userList); }catch(IOExceptione){ thrownewRuntimeException(e); } } /** *設(shè)置響應(yīng)結(jié)果 * *@paramresponse響應(yīng)結(jié)果對(duì)象 *@paramrawFileName文件名 *@throwsUnsupportedEncodingException不支持編碼異常 */ privatevoidsetExcelResponseProp(HttpServletResponseresponse,StringrawFileName)throwsUnsupportedEncodingException{ response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setCharacterEncoding("utf-8"); StringfileName=URLEncoder.encode(rawFileName,"UTF-8").replaceAll("+","%20"); response.setHeader("Content-disposition","attachment;filename*=utf-8''"+fileName+".xlsx"); } /** *讀取用戶(hù)列表數(shù)據(jù) * *@return用戶(hù)列表數(shù)據(jù) *@throwsIOExceptionIO異常 */ privateList getUserList()throwsIOException{ ObjectMapperobjectMapper=newObjectMapper(); ClassPathResourceclassPathResource=newClassPathResource("mock/users.json"); InputStreaminputStream=classPathResource.getInputStream(); returnobjectMapper.readValue(inputStream,newTypeReference >(){ }); } }
測(cè)試接口
運(yùn)行項(xiàng)目,通過(guò) Postman 或者 Apifox 工具來(lái)進(jìn)行接口測(cè)試
注意:在 Apifox 中訪問(wèn)接口后無(wú)法直接下載,需要點(diǎn)擊返回結(jié)果中的下載圖標(biāo)才行,點(diǎn)擊之后方可對(duì)Excel文件進(jìn)行保存。
接口地址:http://localhost:8080/excel/export/user
復(fù)雜導(dǎo)出
由于 EasyPoi 支持嵌套對(duì)象導(dǎo)出,直接使用內(nèi)置 @ExcelCollection 注解即可實(shí)現(xiàn),遺憾的是 EasyExcel 不支持一對(duì)多導(dǎo)出,只能自行實(shí)現(xiàn),通過(guò)此issues了解到,項(xiàng)目維護(hù)者建議通過(guò)自定義合并策略方式來(lái)實(shí)現(xiàn)一對(duì)多導(dǎo)出。
解決思路:只需把訂單主鍵相同的列中需要合并的列給合并了,就可以實(shí)現(xiàn)這種一對(duì)多嵌套信息的導(dǎo)出
自定義注解
創(chuàng)建一個(gè)自定義注解,用于標(biāo)記哪些屬性需要合并單元格,哪個(gè)屬性是主鍵:
/** *用于判斷是否需要合并以及合并的主鍵 * *@authorwilliam@StarImmortal */ @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented public@interfaceExcelMerge{ /** *是否合并單元格 * *@returntrue||false */ booleanmerge()defaulttrue; /** *是否為主鍵(即該字段相同的行合并) * *@returntrue||false */ booleanisPrimaryKey()defaultfalse; }
定義實(shí)體類(lèi)
在需要合并單元格的屬性上設(shè)置 @ExcelMerge 注解,二級(jí)表頭通過(guò)設(shè)置 @ExcelProperty 注解中 value 值為數(shù)組形式來(lái)實(shí)現(xiàn)該效果:
/** *@authorwilliam@StarImmortal */ @Data publicclassOrderBO{ @ExcelProperty(value="訂單主鍵") @ColumnWidth(16) @ExcelMerge(merge=true,isPrimaryKey=true) privateStringid; @ExcelProperty(value="訂單編號(hào)") @ColumnWidth(20) @ExcelMerge(merge=true) privateStringorderId; @ExcelProperty(value="收貨地址") @ExcelMerge(merge=true) @ColumnWidth(20) privateStringaddress; @ExcelProperty(value="創(chuàng)建時(shí)間") @ColumnWidth(20) @DateTimeFormat("yyyy-MM-ddHHss") @ExcelMerge(merge=true) privateDatecreateTime; @ExcelProperty(value={"商品信息","商品編號(hào)"}) @ColumnWidth(20) privateStringproductId; @ExcelProperty(value={"商品信息","商品名稱(chēng)"}) @ColumnWidth(20) privateStringname; @ExcelProperty(value={"商品信息","商品標(biāo)題"}) @ColumnWidth(30) privateStringsubtitle; @ExcelProperty(value={"商品信息","品牌名稱(chēng)"}) @ColumnWidth(20) privateStringbrandName; @ExcelProperty(value={"商品信息","商品價(jià)格"}) @ColumnWidth(20) privateBigDecimalprice; @ExcelProperty(value={"商品信息","商品數(shù)量"}) @ColumnWidth(20) privateIntegercount; }
數(shù)據(jù)映射與平鋪
導(dǎo)出之前,需要對(duì)數(shù)據(jù)進(jìn)行處理,將訂單數(shù)據(jù)進(jìn)行平鋪,orderList為平鋪前格式,exportData為平鋪后格式:
自定義單元格合并策略
當(dāng) Excel 中兩列主鍵相同時(shí),合并被標(biāo)記需要合并的列:
/** *自定義單元格合并策略 * *@authorwilliam@StarImmortal */ publicclassExcelMergeStrategyimplementsRowWriteHandler{ /** *主鍵下標(biāo) */ privateIntegerprimaryKeyIndex; /** *需要合并的列的下標(biāo)集合 */ privatefinalListmergeColumnIndexList=newArrayList<>(); /** *數(shù)據(jù)類(lèi)型 */ privatefinalClass>elementType; publicExcelMergeStrategy(Class>elementType){ this.elementType=elementType; } @Override publicvoidafterRowDispose(WriteSheetHolderwriteSheetHolder,WriteTableHolderwriteTableHolder,Rowrow,IntegerrelativeRowIndex,BooleanisHead){ //判斷是否為標(biāo)題 if(isHead){ return; } //獲取當(dāng)前工作表 Sheetsheet=writeSheetHolder.getSheet(); //初始化主鍵下標(biāo)和需要合并字段的下標(biāo) if(primaryKeyIndex==null){ this.initPrimaryIndexAndMergeIndex(writeSheetHolder); } //判斷是否需要和上一行進(jìn)行合并 //不能和標(biāo)題合并,只能數(shù)據(jù)行之間合并 if(row.getRowNum()<=?1)?{ ????????????return; ????????} ????????//?獲取上一行數(shù)據(jù) ????????Row?lastRow?=?sheet.getRow(row.getRowNum()?-?1); ????????//?將本行和上一行是同一類(lèi)型的數(shù)據(jù)(通過(guò)主鍵字段進(jìn)行判斷),則需要合并 ????????if?(lastRow.getCell(primaryKeyIndex).getStringCellValue().equalsIgnoreCase(row.getCell(primaryKeyIndex).getStringCellValue()))?{ ????????????for?(Integer?mergeIndex?:?mergeColumnIndexList)?{ ????????????????CellRangeAddress?cellRangeAddress?=?new?CellRangeAddress(row.getRowNum()?-?1,?row.getRowNum(),?mergeIndex,?mergeIndex); ????????????????sheet.addMergedRegionUnsafe(cellRangeAddress); ????????????} ????????} ????} ????/** ?????*?初始化主鍵下標(biāo)和需要合并字段的下標(biāo) ?????* ?????*?@param?writeSheetHolder?WriteSheetHolder ?????*/ ????private?void?initPrimaryIndexAndMergeIndex(WriteSheetHolder?writeSheetHolder)?{ ????????//?獲取當(dāng)前工作表 ????????Sheet?sheet?=?writeSheetHolder.getSheet(); ????????//?獲取標(biāo)題行 ????????Row?titleRow?=?sheet.getRow(0); ????????//?獲取所有屬性字段 ????????Field[]?fields?=?this.elementType.getDeclaredFields(); ????????//?遍歷所有字段 ????????for?(Field?field?:?fields)?{ ????????????//?獲取@ExcelProperty注解,用于獲取該字段對(duì)應(yīng)列的下標(biāo) ????????????ExcelProperty?excelProperty?=?field.getAnnotation(ExcelProperty.class); ????????????//?判斷是否為空 ????????????if?(null?==?excelProperty)?{ ????????????????continue; ????????????} ????????????//?獲取自定義注解,用于合并單元格 ????????????ExcelMerge?excelMerge?=?field.getAnnotation(ExcelMerge.class); ????????????//?判斷是否需要合并 ????????????if?(null?==?excelMerge)?{ ????????????????continue; ????????????} ????????????for?(int?i?=?0;?i?
定義接口
將自定義合并策略 ExcelMergeStrategy 通過(guò) registerWriteHandler 注冊(cè)上去:
/** *EasyExcel導(dǎo)入導(dǎo)出 * *@authorwilliam@StarImmortal */ @RestController @RequestMapping("/excel") publicclassExcelController{ @GetMapping("/export/order") publicvoidexportOrderExcel(HttpServletResponseresponse){ try{ this.setExcelResponseProp(response,"訂單列表"); ListorderList=this.getOrderList(); List exportData=this.convert(orderList); EasyExcel.write(response.getOutputStream()) .head(OrderBO.class) .registerWriteHandler(newExcelMergeStrategy(OrderBO.class)) .excelType(ExcelTypeEnum.XLSX) .sheet("訂單列表") .doWrite(exportData); }catch(IOExceptione){ thrownewRuntimeException(e); } } /** *設(shè)置響應(yīng)結(jié)果 * *@paramresponse響應(yīng)結(jié)果對(duì)象 *@paramrawFileName文件名 *@throwsUnsupportedEncodingException不支持編碼異常 */ privatevoidsetExcelResponseProp(HttpServletResponseresponse,StringrawFileName)throwsUnsupportedEncodingException{ response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setCharacterEncoding("utf-8"); StringfileName=URLEncoder.encode(rawFileName,"UTF-8").replaceAll("+","%20"); response.setHeader("Content-disposition","attachment;filename*=utf-8''"+fileName+".xlsx"); } }
測(cè)試接口
運(yùn)行項(xiàng)目,通過(guò) Postman 或者 Apifox 工具來(lái)進(jìn)行接口測(cè)試
注意:在 Apifox 中訪問(wèn)接口后無(wú)法直接下載,需要點(diǎn)擊返回結(jié)果中的下載圖標(biāo)才行,點(diǎn)擊之后方可對(duì)Excel文件進(jìn)行保存。
接口地址:http://localhost:8080/excel/export/order
簡(jiǎn)單導(dǎo)入
以導(dǎo)入用戶(hù)信息為例,接下來(lái)手把手教大家如何使用EasyExcel實(shí)現(xiàn)導(dǎo)入功能!
/** *EasyExcel導(dǎo)入導(dǎo)出 * *@authorwilliam@StarImmortal */ @RestController @RequestMapping("/excel") @Api(tags="EasyExcel") publicclassExcelController{ @PostMapping("/import/user") publicResponseVOimportUserExcel(@RequestPart(value="file")MultipartFilefile){ try{ ListuserList=EasyExcel.read(file.getInputStream()) .head(UserDO.class) .sheet() .doReadSync(); returnResponseVO.success(userList); }catch(IOExceptione){ returnResponseVO.error(); } } } 責(zé)任編輯:彭菁
-
內(nèi)存
+關(guān)注
關(guān)注
8文章
3108瀏覽量
74978 -
文件
+關(guān)注
關(guān)注
1文章
578瀏覽量
25195 -
Excel
+關(guān)注
關(guān)注
4文章
225瀏覽量
56358
原文標(biāo)題:SpringBoot 集成 EasyExcel 3.x 優(yōu)雅實(shí)現(xiàn) Excel 導(dǎo)入導(dǎo)出
文章出處:【微信號(hào):芋道源碼,微信公眾號(hào):芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
Linux平臺(tái)大文件生成和處理方法
Python利用pandas讀寫(xiě)Excel文件

ReqMan需求提取和協(xié)同處理工具怎么樣看了就知道
介紹一款蘋(píng)果操作系統(tǒng)的電源管理工具
TXT大文件切割軟體應(yīng)用程序免費(fèi)下載

內(nèi)存溢出和內(nèi)存泄露的區(qū)別_內(nèi)存溢出的原因以及解決方法
EXCEL“大文件Vlookup工具”使用步驟資料下載

如何通過(guò)python輕松處理大文件
如何解決內(nèi)存溢出

java內(nèi)存溢出排查方法
jvm內(nèi)存溢出故障排查
jvm內(nèi)存溢出該如何定位解決
內(nèi)存溢出與內(nèi)存泄漏:定義、區(qū)別與解決方案
Spire.Cloud.Excel云端Excel文檔處理SDK

評(píng)論