java如何在項(xiàng)目中實(shí)現(xiàn)excel導(dǎo)入導(dǎo)出功能
一、初識EasyExcel*
1. Apache POI
先說POI,有過報(bào)表導(dǎo)入導(dǎo)出經(jīng)驗(yàn)的同學(xué),應(yīng)該聽過或者使用。
Apache POI是Apache軟件基金會(huì)的開源函式庫,提供跨平臺的Java API實(shí)現(xiàn)Microsoft Office格式檔案讀寫。但是存在如下一些問題:
1.1 學(xué)習(xí)使用成本較高
對POI有過深入了解的才知道原來POI還有SAX模式(Dom解析模式)。但SAX模式相對比較復(fù)雜,excel有03和07兩種版本,兩個(gè)版本數(shù)據(jù)存儲(chǔ)方式截然不同,sax解析方式也各不一樣。
想要了解清楚這兩種解析方式,才去寫代碼測試,估計(jì)兩天時(shí)間是需要的。再加上即使解析完,要轉(zhuǎn)換到自己業(yè)務(wù)模型還要很多繁瑣的代碼??傮w下來感覺至少需要三天,由于代碼復(fù)雜,后續(xù)維護(hù)成本巨大。
POI的SAX模式的API可以一定程度的解決一些內(nèi)存溢出的問題,但是POI還是有一些缺陷,比如07版Excel解壓縮以及解壓后存儲(chǔ)都是在內(nèi)存中完成的,內(nèi)存消耗依然很大,一個(gè)3M的Excel用POI的SAX解析,依然需要100M左右內(nèi)存。
1.2 POI的內(nèi)存消耗較大
大部分使用POI都是使用他的userModel模式。userModel的好處是上手容易使用簡單,隨便拷貝個(gè)代碼跑一下,剩下就是寫業(yè)務(wù)轉(zhuǎn)換了,雖然轉(zhuǎn)換也要寫上百行代碼,相對比較好理解。然而userModel模式最大的問題是在于非常大的內(nèi)存消耗,一個(gè)幾兆的文件解析要用掉上百兆的內(nèi)存。現(xiàn)在很多應(yīng)用采用這種模式,之所以還正常在跑一定是并發(fā)不大,并發(fā)上來后一定會(huì)OOM或者頻繁的full gc。
總體上來說,簡單寫法重度依賴內(nèi)存,復(fù)雜寫法學(xué)習(xí)成本高。
特點(diǎn)
功能強(qiáng)大
代碼書寫冗余繁雜
讀寫大文件耗費(fèi)內(nèi)存較大,容易OOM
2. EasyExcel
2.1 重寫了POI對07版Excel的解析
EasyExcel重寫了POI對07版Excel的解析,可以把內(nèi)存消耗從100M左右降低到10M以內(nèi),并且再大的Excel不會(huì)出現(xiàn)內(nèi)存溢出,03版仍依賴POI的SAX模式。
下圖為64M內(nèi)存1分鐘內(nèi)讀取75M(46W行25列)的Excel(當(dāng)然還有急速模式能更快,但是內(nèi)存占用會(huì)在100M多一點(diǎn))

- 在上層做了模型轉(zhuǎn)換的封裝,讓使用者更加簡單方便
特點(diǎn)
- 在數(shù)據(jù)模型層面進(jìn)行了封裝,使用簡單
- 重寫了07版本的Excel的解析代碼,降低內(nèi)存消耗,能有效避免OOM
- 只能操作Excel
- 不能讀取圖片
二、快速入門–QuickStart
0、導(dǎo)入依賴坐標(biāo)
<!-- 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>
導(dǎo)入
easyexcel-2.1.6坐標(biāo)的時(shí)候,已依賴傳遞導(dǎo)入poi-3.17的POI。
1、最簡單的讀
1.1、需求、準(zhǔn)備工作
/** * 需求:單實(shí)體導(dǎo)入(從磁盤將文件導(dǎo)入到應(yīng)用程序) * 導(dǎo)入Excel學(xué)員信息到系統(tǒng)。 * 包含如下列:姓名、性別、出生日期 * 模板詳見:學(xué)員信息.xlsx */
學(xué)員信息.xlsx

1.2、編寫導(dǎo)出數(shù)據(jù)的實(shí)體類
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 {
/**
* 學(xué)生姓名
*/
@ExcelProperty(value = "學(xué)生姓名", index = 0)
private String name;
/**
* 學(xué)生性別
*/
@ExcelProperty(value = "學(xué)生性別", index = 2)
private String gender;
/**
* 學(xué)生出生日期
*/
@ExcelProperty(value = "學(xué)生出生日期", index = 1)
private Date birthday;
/**
* id
*/
// @ExcelProperty(value = "編號",index = 3)
@ExcelIgnore // 忽略,不讀取
private String id;
}
注解: 文章后面有詳解
1.3、 讀取Excel文件(上傳)
調(diào)用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() {
// 讀取文件,讀取完之后會(huì)自動(dòng)關(guān)閉
/**
* 參數(shù)1:pathName 文件路徑;"d:\\學(xué)員信息.xls"
* 參數(shù)2:head 每行數(shù)據(jù)對應(yīng)的實(shí)體;Student.class
* 參數(shù)3:readListener 讀監(jiān)聽器,每讀一樣就會(huì)調(diào)用一次該監(jiān)聽器的invoke方法
* 參數(shù)4:sheet方法參數(shù): 工作表的順序號(從0開始)或者工作表的名字,不傳默認(rèn)為0
*/
// // 封裝工作簿對象
// ExcelReaderBuilder workBook = EasyExcel.read
// ("E:\\學(xué)員信息.xlsx",
// Student.class,
// new StudentReadListener( ));
// // 封裝工作表
// ExcelReaderSheetBuilder sheet1 = workBook.sheet( );
// // 讀取
// sheet1.doRead( );
// 最簡單的寫法
EasyExcel.read("E:\\學(xué)員信息.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> {
// 每讀一行,會(huì)調(diào)用該invoke方法一次
@Override
public void invoke(Student data, AnalysisContext context) {
System.out.println("data = " + data);
}
// 全部讀完之后,會(huì)調(diào)用該方法
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// TODO......
}
}
2、最簡單的寫(導(dǎo)出)
2.1 需求、準(zhǔn)備工作
/** * 需求:單實(shí)體導(dǎo)出(從應(yīng)用程序?qū)⑽募?dǎo)入到磁盤) * 導(dǎo)出多個(gè)學(xué)生對象到Excel表格 * 包含如下列:姓名、性別、出生日期 * 模板詳見:學(xué)員信息.xlsx */
2.2、編寫導(dǎo)出數(shù)據(jù)的實(shí)體
// 還是上個(gè)實(shí)體類...
2.3、 準(zhǔn)備數(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"));
// 寫出的文件路徑,當(dāng)前項(xiàng)目下
String fileName = "student.xlsx";
// 這里 需要指定寫用哪個(gè)class去寫,然后寫到第一個(gè)sheet,名字為模板 然后文件流會(huì)自動(dòng)關(guān)閉
// 如果這里想使用03 則 傳入excelType參數(shù)即可
EasyExcel.write(fileName, Student.class).sheet("學(xué)生信息").doWrite(list);
System.out.println("導(dǎo)出OK" );
}


三、vue文件上傳和下載[重點(diǎn)]
基于springboot的文件上傳和下載
0. 導(dǎo)入依賴
<!-- EasyExcel --> <!-- lombok --> <!-- junit -->
3.1 文件上傳
注意: 本地Excel表格,列要和數(shù)據(jù)庫一致
需求: 批量插入計(jì)量單位(導(dǎo)入excel數(shù)據(jù)到項(xiàng)目中)
思路:
- 前端設(shè)計(jì)文件上傳組件 ,點(diǎn)擊開始文件上傳
- 后端接收文件,使用工具解析數(shù)據(jù)
- 插入數(shù)據(jù)庫
3.1.1 前端
在頁面設(shè)計(jì)上傳的組件
1.設(shè)置上傳按鈕,顯示上傳的對話框
2.設(shè)置對話框+上傳組件

<!-- 上傳excel的對話框 -->
<el-dialog title="上傳計(jì)量單位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>點(diǎn)擊上傳</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 后端-實(shí)體類注解

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> {
// 有個(gè)很重要的點(diǎn) DemoDataListener 不能被spring管理,
// 要每次讀取excel都要new,然后里面用到spring可以構(gòu)造方法傳進(jìn)去
private UnitMeasureService service;
public MdUnitMeasureReadListener(){} // 空參構(gòu)造
// 有參構(gòu)造
public MdUnitMeasureReadListener(UnitMeasureService service){
this.service = service;
}
// 每隔5條存儲(chǔ)數(shù)據(jù)庫,實(shí)際項(xiàng)目中使用時(shí)可以100條,然后清理list ,方便內(nèi)存回收
private final int BATCH_COUNT = 5;
private ArrayList<UnitMeasure> list = new ArrayList<>( );
// 每讀一行,會(huì)調(diào)用該invoke方法一次
@Override
public void invoke(UnitMeasure data, AnalysisContext context) {
list.add(data);
// 達(dá)到BATCH_COUNT了,需要去存儲(chǔ)一次數(shù)據(jù)庫,防止數(shù)據(jù)幾萬條數(shù)據(jù)在內(nèi)存,容易OOM
if (list.size( ) >= BATCH_COUNT) {
// 調(diào)用方法,執(zhí)行插入
saveData( );
// 存儲(chǔ)完成清理 list
list.clear();
}
}
// 全部讀完之后,會(huì)調(diào)用該方法
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 這里也要保存數(shù)據(jù),確保最后遺留的數(shù)據(jù)也存儲(chǔ)到數(shù)據(jù)庫
if(list.size() > 0) {
saveData( );
}
System.out.println("所有數(shù)據(jù)解析完成!");
}
// 向數(shù)據(jù)庫插入數(shù)據(jù)
private void saveData() {
System.out.println("開始存儲(chǔ)數(shù)據(jù)庫!");
// 調(diào)用業(yè)務(wù)層存數(shù)據(jù)庫
service.saveBatch(list);
System.out.println("存儲(chǔ)數(shù)據(jù)庫成功!");
}
}
3.1.4 后端-Controller接收
/**
* 上傳Excel文件
*/
@PostMapping("/upload/excel")
public R uploadExcel(MultipartFile file) {
try {
// 重點(diǎn),此處第三個(gè)參數(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)建一個(gè)excel表格,按照實(shí)體類中定義的列名填充數(shù)據(jù)
點(diǎn)擊上傳

3.2 文件導(dǎo)出
需求: 將項(xiàng)目中的數(shù)據(jù)導(dǎo)出到本地excel表格
思路:
- 前端設(shè)計(jì)按鈕,點(diǎn)擊開始導(dǎo)出
- 后端請求,查詢出數(shù)據(jù),
- 使用工具導(dǎo)出
實(shí)體類于之前一樣
// 略
Controller層
/**
* 下載Excel文件
* 注意,返回值是void
*/
@GetMapping("/download/excel")
public void downloadExcel(HttpServletResponse response) throws IOException {
// 查詢數(shù)據(jù)庫全部數(shù)據(jù)
List<UnitMeasure> list = service.findAll(null);
// 下面這個(gè)注釋是前端使用方式1,即a標(biāo)簽發(fā)請求時(shí)采用
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
// 這里URLEncoder.encode可以防止中文亂碼
String fileName = URLEncoder.encode("計(jì)量單位信息", "UTF-8");
response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
// easyexcel導(dǎo)出工具
EasyExcel.write(response.getOutputStream(), UnitMeasure.class).autoCloseStream(Boolean.FALSE).sheet("計(jì)量單位信息")
.doWrite(list);
}// todo servicce+mapper實(shí)現(xiàn)查詢?nèi)?/p>
前端
1.導(dǎo)出按鈕觸發(fā)函數(shù)

2.函數(shù)內(nèi)確認(rèn)導(dǎo)出,發(fā)出請求到后端

3.3、自定義單元格樣式
EasyExcel支持調(diào)整行高、列寬、背景色、字體大小等內(nèi)容,但是控制方式與使用原生POI無異,比較繁瑣,不建議使用。
但是可以使用模板填充的方式,向預(yù)設(shè)樣式的表格中直接寫入數(shù)據(jù),寫入數(shù)據(jù)的時(shí)候會(huì)保持原有樣式。
總結(jié)
到此這篇關(guān)于java如何在項(xiàng)目中實(shí)現(xiàn)excel導(dǎo)入導(dǎo)出功能的文章就介紹到這了,更多相關(guān)java實(shí)現(xiàn)excel導(dǎo)入導(dǎo)出內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- java實(shí)現(xiàn)Excel的導(dǎo)入、導(dǎo)出
- Java實(shí)現(xiàn)Excel導(dǎo)入導(dǎo)出數(shù)據(jù)庫的方法示例
- java實(shí)現(xiàn)Excel的導(dǎo)入導(dǎo)出
- Java中Easypoi實(shí)現(xiàn)excel多sheet表導(dǎo)入導(dǎo)出功能
- java使用EasyExcel導(dǎo)入導(dǎo)出excel
- Java實(shí)現(xiàn)Excel導(dǎo)入導(dǎo)出操作詳解
- java利用easyexcel實(shí)現(xiàn)導(dǎo)入與導(dǎo)出功能
- java操作excel導(dǎo)入導(dǎo)出的3種方式
- Java使用EasyExcel實(shí)現(xiàn)Excel的導(dǎo)入導(dǎo)出
- Java如何使用poi導(dǎo)入導(dǎo)出excel工具類
相關(guān)文章
java中用數(shù)組實(shí)現(xiàn)環(huán)形隊(duì)列的示例代碼
這篇文章主要介紹了java中用數(shù)組實(shí)現(xiàn)環(huán)形隊(duì)列的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04
手動(dòng)模擬JDK動(dòng)態(tài)代理的方法
這篇文章主要介紹了手動(dòng)模擬JDK動(dòng)態(tài)代理的方法,幫助大家更好的了解和學(xué)習(xí)Java 代理的相關(guān)知識,感興趣的朋友可以了解下2020-11-11
如何讓W(xué)in10實(shí)現(xiàn)Java文件的開機(jī)自啟動(dòng)
這篇文章主要介紹了如何讓W(xué)in10實(shí)現(xiàn)Java文件的開機(jī)自啟動(dòng),對于一些想要一直運(yùn)行的Java文件,就會(huì)造成每次系統(tǒng)更新之后的重啟導(dǎo)致Java文件無法繼續(xù)運(yùn)行。,需要的朋友可以參考下2019-06-06
Idea導(dǎo)入eureka源碼實(shí)現(xiàn)過程解析
這篇文章主要介紹了Idea導(dǎo)入eureka源碼實(shí)現(xiàn)過程解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08
mybatis-plus查詢字段中含有關(guān)鍵詞時(shí)異常的問題及解決方案
本文主要介紹了在使用MyBatis-Plus進(jìn)行數(shù)據(jù)庫操作時(shí),遇到字段名與SQL關(guān)鍵字沖突的問題,通過分析和解決過程,作者發(fā)現(xiàn)是由于實(shí)體類中字段名與SQL關(guān)鍵字一致導(dǎo)致的錯(cuò)誤,解決方法是在實(shí)體類中使用@TableField注解,并在字段名前后加上反引號(``),以避免SQL解析錯(cuò)誤2026-01-01

