java如何在項目中實現(xiàn)excel導入導出功能
一、初識EasyExcel*
1. Apache POI
先說POI
,有過報表導入導出經(jīng)驗的同學,應該聽過或者使用。
Apache POI
是Apache軟件基金會的開源函式庫,提供跨平臺的Java API
實現(xiàn)Microsoft Office
格式檔案讀寫。但是存在如下一些問題:
1.1 學習使用成本較高
對POI有過深入了解的才知道原來POI還有SAX模式(Dom解析模式)。但SAX模式相對比較復雜,excel有03和07兩種版本,兩個版本數(shù)據(jù)存儲方式截然不同,sax解析方式也各不一樣。
想要了解清楚這兩種解析方式,才去寫代碼測試,估計兩天時間是需要的。再加上即使解析完,要轉換到自己業(yè)務模型還要很多繁瑣的代碼??傮w下來感覺至少需要三天,由于代碼復雜,后續(xù)維護成本巨大。
POI的SAX模式的API可以一定程度的解決一些內存溢出的問題,但是POI還是有一些缺陷,比如07版Excel解壓縮以及解壓后存儲都是在內存中完成的,內存消耗依然很大,一個3M的Excel用POI的SAX解析,依然需要100M左右內存。
1.2 POI的內存消耗較大
大部分使用POI都是使用他的userModel模式。userModel的好處是上手容易使用簡單,隨便拷貝個代碼跑一下,剩下就是寫業(yè)務轉換了,雖然轉換也要寫上百行代碼,相對比較好理解。然而userModel模式最大的問題是在于非常大的內存消耗,一個幾兆的文件解析要用掉上百兆的內存?,F(xiàn)在很多應用采用這種模式,之所以還正常在跑一定是并發(fā)不大,并發(fā)上來后一定會OOM或者頻繁的full gc。
總體上來說,簡單寫法重度依賴內存,復雜寫法學習成本高。
特點
功能強大
代碼書寫冗余繁雜
讀寫大文件耗費內存較大,容易OOM
2. EasyExcel
2.1 重寫了POI對07版Excel的解析
EasyExcel重寫了POI對07版Excel的解析,可以把內存消耗從100M左右降低到10M以內,并且再大的Excel不會出現(xiàn)內存溢出,03版仍依賴POI的SAX模式。
下圖為64M內存1分鐘內讀取75M(46W行25列)的Excel(當然還有急速模式能更快,但是內存占用會在100M多一點)
- 在上層做了模型轉換的封裝,讓使用者更加簡單方便
特點
- 在數(shù)據(jù)模型層面進行了封裝,使用簡單
- 重寫了07版本的Excel的解析代碼,降低內存消耗,能有效避免OOM
- 只能操作Excel
- 不能讀取圖片
二、快速入門–QuickStart
0、導入依賴坐標
<!-- EasyExcel --> <dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>2.1.6</version> </dependency> <!-- lombok 優(yōu)雅編程 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.20</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency>
導入
easyexcel-2.1.6
坐標的時候,已依賴傳遞導入poi-3.17
的POI。
1、最簡單的讀
1.1、需求、準備工作
/** * 需求:單實體導入(從磁盤將文件導入到應用程序) * 導入Excel學員信息到系統(tǒng)。 * 包含如下列:姓名、性別、出生日期 * 模板詳見:學員信息.xlsx */
學員信息.xlsx
1.2、編寫導出數(shù)據(jù)的實體類
package com.taotie.test; import com.alibaba.excel.annotation.ExcelIgnore; import com.alibaba.excel.annotation.ExcelProperty; import com.alibaba.excel.annotation.write.style.ColumnWidth; import com.alibaba.excel.annotation.write.style.HeadRowHeight; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.Date; /** * --- 天道酬勤 --- * * @author QiuShiju * @desc */ // 基于lombok @Data @NoArgsConstructor @AllArgsConstructor @HeadRowHeight(20) // 指定列頭行高 @ColumnWidth(20) // 指定列寬 public class Student { /** * 學生姓名 */ @ExcelProperty(value = "學生姓名", index = 0) private String name; /** * 學生性別 */ @ExcelProperty(value = "學生性別", index = 2) private String gender; /** * 學生出生日期 */ @ExcelProperty(value = "學生出生日期", index = 1) private Date birthday; /** * id */ // @ExcelProperty(value = "編號",index = 3) @ExcelIgnore // 忽略,不讀取 private String id; }
注解: 文章后面有詳解
1.3、 讀取Excel文件(上傳)
調用EasyExcel
的API
讀取的Excel
文件的測試類StudentReadDemo
package com.taotie.test; import com.alibaba.excel.EasyExcel; import com.alibaba.excel.read.builder.ExcelReaderBuilder; import com.alibaba.excel.read.builder.ExcelReaderSheetBuilder; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; /** * --- 天道酬勤 --- * * @author QiuShiju * @desc */ @SpringBootTest public class TestEasyExcel { /** * 測試讀取數(shù)據(jù) */ @Test public void testRead() { // 讀取文件,讀取完之后會自動關閉 /** * 參數(shù)1:pathName 文件路徑;"d:\\學員信息.xls" * 參數(shù)2:head 每行數(shù)據(jù)對應的實體;Student.class * 參數(shù)3:readListener 讀監(jiān)聽器,每讀一樣就會調用一次該監(jiān)聽器的invoke方法 * 參數(shù)4:sheet方法參數(shù): 工作表的順序號(從0開始)或者工作表的名字,不傳默認為0 */ // // 封裝工作簿對象 // ExcelReaderBuilder workBook = EasyExcel.read // ("E:\\學員信息.xlsx", // Student.class, // new StudentReadListener( )); // // 封裝工作表 // ExcelReaderSheetBuilder sheet1 = workBook.sheet( ); // // 讀取 // sheet1.doRead( ); // 最簡單的寫法 EasyExcel.read("E:\\學員信息.xlsx",Student.class,new StudentReadListener()).sheet().doRead(); } }
讀取Excel的監(jiān)聽器,用于處理讀取產(chǎn)生的數(shù)據(jù)
package com.taotie.listener; import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.event.AnalysisEventListener; import com.taotie.domain.Student; /** * @Author Vsunks.v * @Description: */ public class StudentReadListener extends AnalysisEventListener<Student> { // 每讀一行,會調用該invoke方法一次 @Override public void invoke(Student data, AnalysisContext context) { System.out.println("data = " + data); } // 全部讀完之后,會調用該方法 @Override public void doAfterAllAnalysed(AnalysisContext context) { // TODO...... } }
2、最簡單的寫(導出)
2.1 需求、準備工作
/** * 需求:單實體導出(從應用程序將文件導入到磁盤) * 導出多個學生對象到Excel表格 * 包含如下列:姓名、性別、出生日期 * 模板詳見:學員信息.xlsx */
2.2、編寫導出數(shù)據(jù)的實體
// 還是上個實體類...
2.3、 準備數(shù)據(jù)并寫入到文件
/** * 測試寫出數(shù)據(jù) */ @Test public void simpleWrite() { // 創(chuàng)造數(shù)據(jù) ArrayList<Student> list = new ArrayList<>( ); list.add(new Student("張三","男",new Date(),"1001")); list.add(new Student("李四","女",new Date(),"1002")); list.add(new Student("王五","男",new Date(),"1003")); list.add(new Student("趙六","女",new Date(),"1004")); list.add(new Student("周期","男",new Date(),"1005")); list.add(new Student("茅十八","女",new Date(),"1006")); // 寫出的文件路徑,當前項目下 String fileName = "student.xlsx"; // 這里 需要指定寫用哪個class去寫,然后寫到第一個sheet,名字為模板 然后文件流會自動關閉 // 如果這里想使用03 則 傳入excelType參數(shù)即可 EasyExcel.write(fileName, Student.class).sheet("學生信息").doWrite(list); System.out.println("導出OK" ); }
三、vue文件上傳和下載[重點]
基于springboot的文件上傳和下載
0. 導入依賴
<!-- EasyExcel --> <!-- lombok --> <!-- junit -->
3.1 文件上傳
注意: 本地Excel表格,列要和數(shù)據(jù)庫一致
需求: 批量插入計量單位(導入excel數(shù)據(jù)到項目中)
思路:
- 前端設計文件上傳組件 ,點擊開始文件上傳
- 后端接收文件,使用工具解析數(shù)據(jù)
- 插入數(shù)據(jù)庫
3.1.1 前端
在頁面設計上傳的組件
1.設置上傳按鈕,顯示上傳的對話框
2.設置對話框+上傳組件
<!-- 上傳excel的對話框 --> <el-dialog title="上傳計量單位Excel" :visible.sync="dialogExcelVisible" width="40%"> <el-upload class="upload-demo" drag action="http://localhost:8888/md/unit/upload/excel" accept=".xlsx,.xls" :on-success="uploadExcelSuccess" :on-error="uploadExcelError" multiple> <i class="el-icon-upload"></i> <div class="el-upload__text">將文件拖到此處,或<em>點擊上傳</em></div> <div class="el-upload__tip" slot="tip">只能上傳.xlsx,.xls文件,且不超過500kb</div> </el-upload> </el-dialog>
uploadExcelSuccess(){ this.$message({ type:"success", message:"上傳成功" }) this.dialogExcelVisible = false; this.fetchData(); }, uploadExcelError(err){ this.$message({ type:"error", message:err }) }
3.1.2 后端-實體類注解
3.1.3 后端-監(jiān)聽器
package com.qf.config; import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.event.AnalysisEventListener; import com.qf.entity.SysUser; import com.qf.entity.UnitMeasure; import com.qf.service.SysUserService; import com.qf.service.UnitMeasureService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import java.util.ArrayList; /** * --- 天道酬勤 --- * * @author QiuShiju * @desc */ @Component @Scope("prototype") // 作者要求每次讀取都要使用新的Listener public class MdUnitMeasureReadListener extends AnalysisEventListener<UnitMeasure> { // 有個很重要的點 DemoDataListener 不能被spring管理, // 要每次讀取excel都要new,然后里面用到spring可以構造方法傳進去 private UnitMeasureService service; public MdUnitMeasureReadListener(){} // 空參構造 // 有參構造 public MdUnitMeasureReadListener(UnitMeasureService service){ this.service = service; } // 每隔5條存儲數(shù)據(jù)庫,實際項目中使用時可以100條,然后清理list ,方便內存回收 private final int BATCH_COUNT = 5; private ArrayList<UnitMeasure> list = new ArrayList<>( ); // 每讀一行,會調用該invoke方法一次 @Override public void invoke(UnitMeasure data, AnalysisContext context) { list.add(data); // 達到BATCH_COUNT了,需要去存儲一次數(shù)據(jù)庫,防止數(shù)據(jù)幾萬條數(shù)據(jù)在內存,容易OOM if (list.size( ) >= BATCH_COUNT) { // 調用方法,執(zhí)行插入 saveData( ); // 存儲完成清理 list list.clear(); } } // 全部讀完之后,會調用該方法 @Override public void doAfterAllAnalysed(AnalysisContext context) { // 這里也要保存數(shù)據(jù),確保最后遺留的數(shù)據(jù)也存儲到數(shù)據(jù)庫 if(list.size() > 0) { saveData( ); } System.out.println("所有數(shù)據(jù)解析完成!"); } // 向數(shù)據(jù)庫插入數(shù)據(jù) private void saveData() { System.out.println("開始存儲數(shù)據(jù)庫!"); // 調用業(yè)務層存數(shù)據(jù)庫 service.saveBatch(list); System.out.println("存儲數(shù)據(jù)庫成功!"); } }
3.1.4 后端-Controller接收
/** * 上傳Excel文件 */ @PostMapping("/upload/excel") public R uploadExcel(MultipartFile file) { try { // 重點,此處第三個參數(shù)需要傳入Service對象 EasyExcel.read(file.getInputStream( ), UnitMeasure.class, new MdUnitMeasureReadListener(service)) .sheet( ).doRead( ); }catch (Exception e){ e.printStackTrace(); return R.fail(e.getMessage()); } return R.ok( ); }
3.1.5 后端-Service
// 需要添加批量插入的方法
3.1.6 后端-Mapper
<!-- 批量插入 --> <insert id="saveBatch"> insert into md_unit_measure (measure_code,measure_name,primary_flag,primary_id,change_rate,enable_flag) values <foreach collection="list" item="unit" separator=","> (#{unit.measureCode}, #{unit.measureName}, #{unit.primaryFlag}, #{unit.primaryId}, #{unit.changeRate}, #{unit.enableFlag}) </foreach>
3.1.7 測試
本地創(chuàng)建一個excel表格,按照實體類中定義的列名填充數(shù)據(jù)
點擊上傳
3.2 文件導出
需求: 將項目中的數(shù)據(jù)導出到本地excel表格
思路:
- 前端設計按鈕,點擊開始導出
- 后端請求,查詢出數(shù)據(jù),
- 使用工具導出
實體類于之前一樣
// 略
Controller層
/** * 下載Excel文件 * 注意,返回值是void */ @GetMapping("/download/excel") public void downloadExcel(HttpServletResponse response) throws IOException { // 查詢數(shù)據(jù)庫全部數(shù)據(jù) List<UnitMeasure> list = service.findAll(null); // 下面這個注釋是前端使用方式1,即a標簽發(fā)請求時采用 response.setContentType("application/vnd.ms-excel"); response.setCharacterEncoding("utf-8"); // 這里URLEncoder.encode可以防止中文亂碼 String fileName = URLEncoder.encode("計量單位信息", "UTF-8"); response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx"); // easyexcel導出工具 EasyExcel.write(response.getOutputStream(), UnitMeasure.class).autoCloseStream(Boolean.FALSE).sheet("計量單位信息") .doWrite(list); }
// todo servicce+mapper實現(xiàn)查詢全部
前端
1.導出按鈕觸發(fā)函數(shù)
2.函數(shù)內確認導出,發(fā)出請求到后端
3.3、自定義單元格樣式
EasyExcel支持調整行高、列寬、背景色、字體大小等內容,但是控制方式與使用原生POI無異,比較繁瑣,不建議使用。
但是可以使用模板填充的方式,向預設樣式的表格中直接寫入數(shù)據(jù),寫入數(shù)據(jù)的時候會保持原有樣式。
總結
到此這篇關于java如何在項目中實現(xiàn)excel導入導出功能的文章就介紹到這了,更多相關java實現(xiàn)excel導入導出內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Mybatis Interceptor 攔截器的實現(xiàn)
這篇文章主要介紹了Mybatis Interceptor 攔截器的實現(xiàn),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-12-12大數(shù)據(jù) java hive udf函數(shù)的示例代碼(手機號碼脫敏)
這篇文章主要介紹了大數(shù)據(jù) java hive udf函數(shù)(手機號碼脫敏),的相關知識,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-06-06詳解SpringBoot初始教程之Tomcat、Https配置以及Jetty優(yōu)化
本篇文章主要介紹了詳解SpringBoot初始教程之Tomcat、Https配置以及Jetty優(yōu)化,具有一定的參考價值,有興趣的可以了解一下2017-09-09SpringBoot整合Redis的哨兵模式的實現(xiàn)
Redis提供了哨兵模式來處理主從切換和故障轉移,本文主要介紹了SpringBoot整合Redis的哨兵模式的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2024-08-08