詳解POI的使用方法(DOM和SAX的方式)及存在的不足
阿新 • • 發佈:2020-03-20
# 簡介
Apache POI是一套基於 **OOXML 標準**(Office Open XML)和 **OLE2 標準**來讀寫各種格式檔案的 Java API,也就是說只要是遵循以上標準的檔案,POI 都能夠進行讀寫,而不僅僅只能操作我們熟知的辦公程式檔案。本文只會涉及到 excel 相關內容,其他檔案的操作可以參考[poi官方網站]( https://poi.apache.org/components )。
這裡先總結下 POI 的使用體驗。POI 面向介面的設計非常巧妙,使用 `ss.usermodel` 包讀寫 xls 和 xlsx 時,可以使用同一套程式碼,即使這兩種檔案格式採用的是完全不同的標準。POI 提供了`SXSSFWorkbook`用於解決 xlsx 寫大檔案時容易出現的 OOM 問題。但是,還是存在以下不足(都只針對讀場景):
1. **使用 ss.usermodel 包解析 excel 效率較低、記憶體佔用較大,且容易出現 OOM**。類似於 xml 中的 DOM,這種方式會在記憶體中構建整個文件的結構,在處理大檔案時容易出現 OOM。然而,**大部分場景我們並不需要隨機地去訪問 excel 中的節點**。
2. **POI 提供的 SAX 解析可以解決第一個問題,但是 API 太過複雜**。為了解決第一個問題,POI 提供了基於事件驅動的 SAX 方式,這種方式記憶體佔用小、效率高, 但是 API 太過繁瑣,**開發者必須在熟知文件規範的前提下才能使用**,而且 xls 和 xlsx 使用的是完全不同的兩套 API,實際專案中必須針對不同檔案型別分別實現。這一點可以從本文的例子看出來。
針對以上問題,阿里的 easyexcel 對 POI 進行高階封裝,提供了一套非常簡便的 API,其中,讀部分只封裝了 SAX 部分 API,事實上,使用 easyexcel 讀 excel 只會採用 SAX 方式,另外,easyexcel 重寫了 POI 對 xlsx 的解析,能夠原本一個3M的 excel 用 POI SAX 依然需要100M左右記憶體降低到幾M,easyexcel 的內容本文也會涉及到。
## 什麼是 OLE2 和 OOXML
OLE2 和 OOXML 本質上都是一種檔案格式規範或標準,平時看到的 excel 中,有字型、公式、顏色、圖片等等,看起來非常複雜,但是在檔案結構上都遵循著固定的格式。
OLE2 檔案一般包括 xls、doc、ppt 等,是二進位制格式的檔案。 相關內容可以參考:[ 複合文件Ole物件二進位制儲存格式 ]( 複合文件Ole物件二進位制儲存格式 )。
OOXML檔案一般包括 xlsx、docx、pptx 等。該類檔案以指定格式的 xml 為基礎並以 zip 格式壓縮,這裡我利用解壓工具解壓本地的一個 xml 檔案,可以看到以下檔案結構,在本文例子中,我們會重點關注 sharedStrings.xml 和 sheet1.xml 的內容,因為使用 SAX API 時必須用到:
## POI的元件
針對不同應用的檔案,使用時需要引入對應的 maven 依賴,這裡給出官方給出的指引。如果我們不使用 SAX API 方式讀寫 excel,一般只會用到這個 org.apache.poi.ss 中的 API,具體的實現類放在 org.apache.poi.hssf 或 org.apache.poi.xssf 。
| 元件 | 作用 | Maven依賴 |
| :---------------------------------------: | :-----------------------------: | :----------------------------------------------------------: |
| POIFS | OLE2 Filesystem | *poi* |
| HPSF | OLE2 Property Sets | *poi* |
| HSSF | Excel XLS | *poi* |
| HSLF | PowerPoint PPT | *poi-scratchpad* |
| HWPF | Word DOC | *poi-scratchpad* |
| HDGF | Visio VSD | *poi-scratchpad* |
| HPBF | Publisher PUB | *poi-scratchpad* |
| HSMF | Outlook MSG | *poi-scratchpad* |
| DDF | Escher common drawings | *poi* |
| HWMF | WMF drawings | *poi-scratchpad* |
| OpenXML4J | OOXML | *poi-ooxml* plus either *poi-ooxml-schemas* or *ooxml-schemas* and *ooxml-security* |
| XSSF | Excel XLSX | *poi-ooxml* |
| XSLF | PowerPoint PPTX | *poi-ooxml* |
| XWPF | Word DOCX | *poi-ooxml* |
| XDGF | Visio VSDX | *poi-ooxml* |
| Common SL | PowerPoint PPT 和 PPTX 共用元件 | *poi-scratchpad* and *poi-ooxml* |
| Common SS | Excel XLS 和 XLSX 共用元件 | *poi-ooxml* |
# 怎麼使用POI
## 工程環境
JDK:1.8.0_201
maven:3.6.1
IDE:Spring Tool Suite 4.3.2.RELEASE
POI:4.1.2
easyexcel:2.1.6
mysql:5.7.28
## 建立專案
專案型別Maven Project,打包方式 jar。
## 引入依賴
```xml
junit
junit
4.12
test
org.apache.poi
poi-ooxml
4.1.2
com.alibaba
easyexcel
2.1.6
com.zaxxer
HikariCP
2.6.1
mysql
mysql-connector-java
8.0.15
```
## 讀excel--入門案例
### 需求
讀取指定 excel 第一個單元格的內容。該指定檔案第一個單元格內容為“測試”。
### 編寫測試方法
建議採用`WorkbookFactory`來獲取`Workbook`例項,而不是根據檔案型別寫死具體的實現類。另外,獲取單元格物件時建議採用 `SheetUtil`獲取,裡面會對行物件進行判空操作。
```java
@Test
public void test01() throws IOException {
// 處理XSSF
String path = "extend\\file\\poi_test_01.xlsx";
// 處理HSSF
//String path = "extend\\file\\poi_test_01.xls";
// 建立工作簿,會根據excel命名選擇不同的Workbook實現類
Workbook wb = WorkbookFactory.create(new File(path));
// 獲取工作表
Sheet sheet = wb.getSheetAt(0);
// 獲取行
Row row = sheet.getRow(0);
// 獲取單元格
Cell cell = row.getCell(0);
// 也可以採用以下方式獲取單元格
// Cell cell = SheetUtil.getCell(sheet, 0, 0);
// 獲取單元格內容
String value = cell.getStringCellValue();
System.err.println("第一個單元格字元:" + value);
// 釋放資源
wb.close();
}
```
### 測試
執行以上方法,控制檯打印出第一個單元格的內容:
## 寫excel--入門案例
### 需求
生成一個 excel 檔案,並給第一個單元格賦值為"測試",並設定列寬 26,行高 20.25,內容居中,下框線,單元格橙色填充。
### 編寫測試方法
`CellUtil`是 POI 自帶的工具類,這裡簡化了三句程式碼(建立單元格,設定樣式,賦值)。注意,當寫入 xlsx 的大檔案時,可以考慮使用`SXSSFWorkbook`來避免 OOM。
```java
@Test
public void test01() throws FileNotFoundException, IOException {
// 處理XSSF
String path = "extend\\file\\poi_test_01.xlsx";
// 處理HSSF
// String path = "extend\\file\\poi_test_01.xls";
// 建立工作簿
boolean flag = path.endsWith(".xlsx");
Workbook wb = WorkbookFactory.create(flag ? true : false);
// Workbook wb = new SXSSFWorkbook(100);//記憶體僅保留100行資料,可避免OOM
// 建立工作表
Sheet sheet = wb.createSheet(WorkbookUtil.createSafeSheetName("MySheet001"));
// 設定列寬
sheet.setColumnWidth(0, 26 * 256);
// 建立行(索引從0開始)
Row row = sheet.createRow(0);
// 設定行高
row.setHeightInPoints(20.25f);
// 建立單元格樣式物件
CellStyle style = wb.createCellStyle();
// 設定樣式
style.setAlignment(HorizontalAlignment.CENTER); // 橫向居中
style.setVerticalAlignment(VerticalAlignment.CENTER);// 縱向居中
style.setBorderBottom(BorderStyle.THIN);
style.setFillForegroundColor(IndexedColors.ORANGE.getIndex());
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
// 建立單元格、設定樣式和內容
CellUtil.createCell(row, 0, "測試", style);
// 儲存到本地目錄
OutputStream out = new FileOutputStream(new File(path));
wb.write(out);
// 釋放資源
out.close();
wb.close();
}
```
### 測試
執行以上方法,指定路徑下生成了 excel 檔案,並填充了第一個單元格:
## 讀excel--批量匯入excel資料到資料庫
### 需求
將 excel 中的使用者資料匯入到資料庫(sql 已提供,在當前專案的 extend/sql 下),資料格式如下:
該檔案總計1000條資料,xls 大小 128 KB,xlsx 大小 40 KB,兩種型別檔案內容一致。
### 編寫測試方法
一般 excel 的內容格式是提前約定好的,我們知道使用者資料哪一列是使用者名稱,哪一列是電話號碼,所以,在獲取單元格資料後可以準確地轉換,但這種方式需要針對不同的物件分別定義一個轉換方法。
```java
@Test
public void test02() throws SQLException, IOException {
// 處理XSSF
String path = "extend\\file\\user_data.xlsx";
// 處理HSSF
//String path = "extend\\file\\user_data.xls";
// 定義集合,用於存放excel中的使用者資料
List list = new ArrayList<>();
InputStream in = new FileInputStream(path);
// 建立工作簿
Workbook wb = WorkbookFactory.create(in);
// 獲取工作表
Sheet sheet = wb.getSheetAt(0);
// 獲取所有行
Iterator iterator = sheet.iterator();
int rowNum = 0;
// 遍歷行
while(iterator.hasNext()) {
Row row = iterator.next();
// 跳過標題行
if(rowNum == 0 || rowNum == 1) {
rowNum++;
continue;
}
// 將使用者物件儲存到集合中
list.add(constructUserByRow(row));
}
// 批量儲存
new UserService().save(list);
// 釋放資源
in.close();
wb.close();
}
/**
* userList = new UserService().findAll().stream().map((x) -> new UserDTO(x)).collect(Collectors.toList());
// 遍歷資料
for(UserDTO user : userList) {
// 建立資料行
nRow = sheet.createRow(rowIndex++);
// 設定資料行高
nRow.setHeightInPoints(lineHeight);
// 重置cellIndex,從第一列開始寫資料
cellIndex = 1;
// 建立資料單元格,設定單元格內容和樣式
// 使用者名稱
nCell = nRow.createCell(cellIndex++);
nCell.setCellStyle(cs1);
nCell.setCellValue(user.getName());
// 性別
nCell = nRow.createCell(cellIndex++);
nCell.setCellStyle(cs2);
nCell.setCellValue(user.getGenderStr());
// 年齡
nCell = nRow.createCell(cellIndex++);
nCell.setCellStyle(cs3);
nCell.setCellValue(user.getAge());
// 手機號
nCell = nRow.createCell(cellIndex++);
nCell.setCellStyle(cs4);
nCell.setCellValue(user.getPhone());
}
// 儲存到本地目錄
OutputStream out = new FileOutputStream(new File(outpath));
wb.write(out);
// 釋放資源
out.close();
wb.close();
}
```
### 測試
執行以上方法,在指定資料夾可以看到生成的檔案:
## 讀xls--使用SAX方式
### 需求
使用 SAX 的方式將 xls 中的使用者資料匯入到資料庫,資料與以上例子一樣。
### 編寫測試方法
相比前面的例子,使用 SAX 方式記憶體佔用小,效率高,但是 POI 提供的這套 API 用起來非常繁瑣,使用時不得不必須去了解 xls 檔案的結構。我這裡只是簡單展示,監聽器部分的程式碼不太嚴謹,實際專案還是用 easyexcel 來操作吧。
```java
@Test
public void test02() throws Exception {
// 建立POIFSFileSystem
String filename = "extend\\file\\user_data.xls";
POIFSFileSystem poifs = new POIFSFileSystem(new File(filename));
// 建立HSSFRequest,並新增自定義監聽器
HSSFRequest req = new HSSFRequest();
EventExample listener = new EventExample();
req.addListenerForAllRecords(listener);
// 解析和觸發事件
HSSFEventFactory factory = new HSSFEventFactory();
factory.processWorkbookEvents(req, poifs);
// 儲存使用者到資料庫
new UserService().save(listener.getList());
poifs.close();
}
private static class EventExample implements HSSFListener {
private SSTRecord sstrec;
private int lastCellRow = -1;
private int lastCellColumn = -1;
private List list = new ArrayList();
private UserDTO user;
@Override
public void processRecord(Record record) {
switch(record.getSid()) {
// 進入新的sheet
case BoundSheetRecord.sid:
lastCellRow = -1;
lastCellColumn = -1;
break;
// excel中的數值型別和字元存放在不同的位置
case NumberRecord.sid:
NumberRecord numrec = (NumberRecord)record;
// 使用者年齡
user.setAge(Double.valueOf(numrec.getValue()).intValue());
lastCellRow = numrec.getRow();
lastCellColumn = numrec.getColumn();
break;
// SSTRecords中儲存著excel中使用的字元,重複的會合併為一個
case SSTRecord.sid:
sstrec = (SSTRecord)record;
break;
// 讀取到單元格的字元
case LabelSSTRecord.sid:
LabelSSTRecord lrec = (LabelSSTRecord)record;
int thisRow = lrec.getRow();
// 使用者資料從第三行開始
if(thisRow >= 2) {
// 進入新行時,原物件放入集合,並建立新物件
if(thisRow != lastCellRow) {
if(user != null) {
list.add(user);
}
user = new UserDTO();
}
// 根據列數為使用者物件設定屬性
switch(lrec.getColumn()) {
case 1:
// 使用者名稱
user.setName(sstrec.getString(lrec.getSSTIndex()).getString());
break;
case 2:
// 使用者性別
user.setGenderStr(sstrec.getString(lrec.getSSTIndex()).getString());
break;
case 4:
// 使用者電話
user.setPhone(sstrec.getString(lrec.getSSTIndex()).getString());
break;
default:
break;
}
lastCellRow = thisRow;
lastCellColumn = lrec.getColumn();
}
break;
case EOFRecord.sid:
// 最後一行讀取完後直接放入集合
if(lastCellRow != -1 && user != null && lastCellColumn == 4) {
list.add(user);
}
break;
default:
break;
}
}
public List getList() {
return list;
}
}
```
### 測試
執行以上方法,可以在資料庫看到匯入的資料:
## 讀xlsx--使用SAX方式
### 需求
使用 SAX 的方式將 xlsx 中的使用者資料匯入到資料庫,資料與以上例子一樣。
### 編寫測試方法
POI 針對 xlsx 的 SAX API 也是非常繁瑣,屬於非常低階的封裝,這裡竟然需要使用 JDK 原生的 SAX 解析來處理事件,定義事件處理器時,我必須去了解 xml 的節點結構。和上面例子一樣,這裡也只是簡單地演示這套 API 的使用,具體程式碼不太嚴謹,當然,實際開發我們不會採用這種方式,建議還是使用 easyexcel 吧。
```java
@Test
public void test01() throws Exception {
String filename = "extend\\file\\user_data.xlsx";
OPCPackage pkg = OPCPackage.open(filename);
XSSFReader r = new XSSFReader(pkg);
// 獲取sharedStrings.xml的內容,這裡存放著excel中的字元
SharedStringsTable sst = r.getSharedStringsTable();
// 接下來就是採用SAX方式解析xml的過程
// 構造解析器,這裡會設定自定義的處理器
XMLReader parser = XMLHelper.newXMLReader();
SheetHandler handler = new SheetHandler(sst);
parser.setContentHandler(handler);
// 解析指定的sheet
InputStream sheet2 = r.getSheet("rId1");
parser.parse(new InputSource(sheet2));
// 儲存使用者到資料庫
new UserService().save(handler.getList());
// handler.getList().forEach(System.err::println);
sheet2.close();
}
private static class SheetHandler extends DefaultHandler {
private SharedStringsTable sst;
private String cellContents;
private boolean cellContentsIsString;
private int cellColumn = -1;
private int cellRow = -1;
List list = new ArrayList<>();
UserDTO user;
private SheetHandler(SharedStringsTable sst) {
this.sst = sst;
}
@Override
public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException {
// 讀取到行
if("row".equals(name)) {
cellRow++;
if(cellRow >= 2) {
// 換行時重新建立使用者例項
user = new UserDTO();
}
}
// 讀取到列 c => cell
if("c".equals(name) && cellRow >= 2) {
// 設定當前讀取到哪一列
char columnChar = attributes.getValue("r").charAt(0);
switch(columnChar) {
case 'B':
cellColumn = 1;
break;
case 'C':
cellColumn = 2;
break;
case 'D':
cellColumn = 3;
break;
case 'E':
cellColumn = 4;
break;
default:
cellColumn = -1;
break;
}
// 當前單元格中的值是否為字元,是的話對應的值被放在SharedStringsTable中
if("s".equals(attributes.getValue("t"))) {
cellContentsIsString = true;
}
}
// Clear contents cache
cellContents = "";
}
@Override
public void endElement(String uri, String localName, String name) throws SAXException {
// 跳過標題
if(cellRow < 2) {
return;
}
// v節點是c的子節點,表示單元格的值
if(name.equals("v")) {
int idx;
if(cellContentsIsString) {
idx = Integer.parseInt(cellContents);
} else {
idx = Double.valueOf(cellContents).intValue();
}
switch(cellColumn) {
case 1:
user.setName(sst.getItemAt(idx).getString());
break;
case 2:
user.setGenderStr(sst.getItemAt(idx).getString());
break;
case 3:
// 年齡的值是數值型別,不在SharedStringsTable中
user.setAge(idx);
break;
case 4:
user.setPhone(sst.getItemAt(idx).getString());
break;
default:
break;
}
}
// 讀取完一行,將使用者物件放入集合中
if("row".equals(name) && user != null) {
list.add(user);
}
// 重置引數
if("c".equals(name)) {
cellColumn = -1;
cellContentsIsString = false;
}
}
@Override
public void characters(char[] ch, int start, int length) {
cellContents += new String(ch, start, length);
}
public List getList() {
return list;
}
}
```
### 測試
執行以上方法,可以在資料庫看到匯入的資料:
# 使用easyexcel讀寫excel
通過以上例子,我們會發現,POI SAX 方式的 API 確實非常繁瑣,使用時我必須熟悉地掌握 OLE2 或 OOXML 的規範,才能夠使用。這是比較低層級的封裝。相比之下,ss.usermodel 的 API 要好用很多,但是這套 API 底層解析 方式有點類似 DOM,效率較低,且記憶體佔用較大。
前面已經講過,easyexcel 對 POI 進行了高階封裝,極大地方便了我們讀寫 excel,而且只會採用 SAX 這種更快的方式來讀取,下面補充下如何使用 easyexcel 讀寫 excel。
## 建立實體
使用 easyexcel 讀寫 excel 時,我們不需要自己寫 row => entity 或者 entity => row 的方法,只要按照以下註解好就行。被`@ExcelProperty`註解的屬性對應 row 中的具體內容,而被`@ExcelIgnore`註解表示不需要與 row 進行轉換。
```java
@ContentRowHeight(16)
public class UserDTO implements Serializable {
private static final long serialVersionUID = 1L;
@ExcelIgnore
private String id;
/**
* list = EasyExcel.read(path).head(UserDTO.class).sheet(0).headRowNumber(2).doReadSync();
// 儲存
new UserService().save(list);
}
```
當然,我們也可以採用自定義的監聽器,如下:
```java
@Test
public void test01() throws SQLException, IOException {
// XSSF
String path = "D:\\growUp\\git_repository\\09-poi-demo\\extend\\file\\user_data.xlsx";
// HSSF
// String path = "D:\\growUp\\git_repository\\09-poi-demo\\extend\\file\\user_data.xls";
List list = new ArrayList();
// 定義回撥監聽器
ReadListener syncReadListener = new AnalysisEventListener() {
@Override
public void invoke(UserDTO data, AnalysisContext context) {
list.add(data);
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// TODO Auto-generated method stub
}
};
// 讀取excel
EasyExcel.read(path, UserDTO.class, syncReadListener).sheet(0).headRowNumber(2).doRead();
// 儲存
new UserService().save(list);
}
```
## 批量匯出資料庫資料到excel
和讀一樣,這裡也只用了一行程式碼就完成了對 excel 的操作。
```java
@Test
public void test01() throws SQLException, IOException {
// XSSF
String path = "D:\\growUp\\git_repository\\09-poi-demo\\extend\\file\\user_data.xlsx";
// HSSF
// String path = "D:\\growUp\\git_repository\\09-poi-demo\\extend\\file\\user_data.xls";
// 獲取使用者資料
List list = new UserService().findAll().stream().map((x) -> new UserDTO(x)).collect(Collectors.toList());
// 寫入excel
EasyExcel.write(path, UserDTO.class).sheet(0).relativeHeadRowIndex(1).doWrite(list);
}
```
# 參考資料
[Apache POI - the Java API for Microsoft Documents]( https://poi.apache.org)
> 相關原始碼請移步:[https://github.com/ZhangZiSheng001/poi-demo](https://github.com/ZhangZiSheng001/poi-demo)
>本文為原創文章,轉載請附上原文出處連結: [https://www.cnblogs.com/ZhangZiSheng001/p/12329937.html](https://www.cnblogs.com/ZhangZiSheng001/p/1232993
通過行資料構造使用者物件
*/ private UserDTO constructUserByRow(Row row) { UserDTO userDTO = new UserDTO(); Cell cell = null; // 使用者名稱 cell = row.getCell(1); userDTO.setName(cell.getStringCellValue()); // 性別 cell = row.getCell(2); userDTO.setGenderStr(cell.getStringCellValue()); // 年齡 cell = row.getCell(3); userDTO.setAge(((Double)cell.getNumericCellValue()).intValue()); // 電話 cell = row.getCell(4); userDTO.setPhone(cell.getStringCellValue()); return userDTO; } ``` ### 測試 執行以上方法,可以在資料庫看到匯入的資料: ## 寫excel--批量匯出資料庫資料到excel ### 需求 將資料庫的使用者資料匯出到excel中。這個例子使用模板進行匯出,模板如下(如果是 xlsx 的大檔案,為了能夠使用`SXSSFWorkbook`最好不要用模板)。 ### 編寫測試方法 寫入的時候使用樣式還是比較繁瑣,實際開發能不使用盡量不要用,或者也可以單獨封裝成一個方法。注意,構造`Workbook`時不要使用`WorkbookFactory.create(file)`方式,否則,模板也會被修改。 ```java @Test public void test02() throws SQLException, IOException { // 處理XSSF String templatePath = "extend\\file\\user_data_template.xlsx"; String outpath = "extend\\file\\user_data.xlsx"; // 處理HSSF // String templatePath = "extend\\file\\user_data_template.xls"; // String path = "extend\\file\\user_data.xls"; InputStream in = new FileInputStream(templatePath); // 建立工作簿,注意,這裡如果傳入File物件,模板也會被改寫 Workbook wb = WorkbookFactory.create(in); // 讀取工作表 Sheet sheet = wb.getSheetAt(0); // 定義複用變數 int rowIndex = 0; // 行的索引 int cellIndex = 1; // 單元格的索引 Row nRow = null; Cell nCell = null; // 讀取大標題行 nRow = sheet.getRow(rowIndex++); // 使用後 +1 // 讀取大標題的單元格 nCell = nRow.getCell(cellIndex); // 設定大標題的內容 nCell.setCellValue("2020年2月使用者表"); // 跳過第二行(模板的小標題) rowIndex++; // 讀取第三行,獲取它的樣式 nRow = sheet.getRow(rowIndex); // 讀取行高 float lineHeight = nRow.getHeightInPoints(); // 獲取第三行的4個單元格中的樣式 CellStyle cs1 = nRow.getCell(cellIndex++).getCellStyle(); CellStyle cs2 = nRow.getCell(cellIndex++).getCellStyle(); CellStyle cs3 = nRow.getCell(cellIndex++).getCellStyle(); CellStyle cs4 = nRow.getCell(cellIndex++).getCellStyle(); // 查詢使用者列表 List使用者名稱
*/ @ExcelProperty(value = { "使用者名稱" }, index = 1) private String name; /** *性別
*/ @ExcelProperty(value = { "性別" }, index = 2) private String genderStr; /** *年齡
*/ @ExcelProperty(value = { "年齡" }, index = 3) private Integer age; /** *電話號碼
*/ @ExcelProperty(value = { "手機號" }, index = 4) @ColumnWidth(14) private String phone; @ExcelIgnore private Integer gender = 0; // 以下省略setter/getter方法 } ``` ## 批量匯入excel 資料到資料庫 easyexcel 封裝或重寫了 POI SAX 部分的 API,所以也是需要設定回撥的監聽器,以下方式會採用預設的監聽器,並返回封裝好的物件。 ```java @Test public void test02() throws SQLException, IOException { // XSSF String path = "D:\\growUp\\git_repository\\09-poi-demo\\extend\\file\\user_data.xlsx"; // HSSF // String path = "D:\\growUp\\git_repository\\09-poi-demo\\extend\\file\\user_data.xls"; // 讀取excel List