詳解POI的使用方法(DOM和SAX的方式)及存在的不足

簡介

Apache POI是一套基於 OOXML 標準(Office Open XML)和 OLE2 標準來讀寫各類格式文件的 Java API,也就是說只要是遵循以上標準的文件,POI 都可以進行讀寫,而不只僅只能操做咱們熟知的辦公程序文件。本文只會涉及到 excel 相關內容,其餘文件的操做能夠參考poi官方網站html

這裏先總結下 POI 的使用體驗。POI 面向接口的設計很是巧妙,使用 ss.usermodel 包讀寫 xls 和 xlsx 時,可使用同一套代碼,即便這兩種文件格式採用的是徹底不一樣的標準。POI 提供了SXSSFWorkbook用於解決 xlsx 寫大文件時容易出現的 OOM 問題。可是,仍是存在如下不足(都只針對讀場景):java

  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 的內容本文也會涉及到。mysql

什麼是 OLE2 和 OOXML

OLE2 和 OOXML 本質上都是一種文件格式規範或標準,平時看到的 excel 中,有字體、公式、顏色、圖片等等,看起來很是複雜,可是在文件結構上都遵循着固定的格式。git

OLE2 文件通常包括 xls、doc、ppt 等,是二進制格式的文件。 相關內容能夠參考:複合文檔Ole對象二進制儲存格式github

OOXML文件通常包括 xlsx、docx、pptx 等。該類文件以指定格式的 xml 爲基礎並以 zip 格式壓縮,這裏我利用解壓工具解壓本地的一個 xml 文件,能夠看到如下文件結構,在本文例子中,咱們會重點關注 sharedStrings.xml 和 sheet1.xml 的內容,由於使用 SAX API 時必須用到:sql

xlsx文件結構

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_201apache

maven:3.6.1xss

IDE:Spring Tool Suite 4.3.2.RELEASEmaven

POI:4.1.2

easyexcel:2.1.6

mysql:5.7.28

建立項目

項目類型Maven Project,打包方式 jar。

引入依賴

<!-- junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <!-- poi-ooxml -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>4.1.2</version>
        </dependency>
        <!-- easyexcel -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>2.1.6</version>
        </dependency>
        <!-- hikari -->
        <dependency>
            <groupId>com.zaxxer</groupId>
            <artifactId>HikariCP</artifactId>
            <version>2.6.1</version>
        </dependency>
        <!-- mysql驅動 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.15</version>
        </dependency>

讀excel--入門案例

需求

讀取指定 excel 第一個單元格的內容。該指定文件第一個單元格內容爲「測試」。

編寫測試方法

建議採用WorkbookFactory來獲取Workbook實例,而不是根據文件類型寫死具體的實現類。另外,獲取單元格對象時建議採用 SheetUtil獲取,裏面會對行對象進行判空操做。

@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();
    }

測試

運行以上方法,控制檯打印出第一個單元格的內容:

ReadTest01

寫excel--入門案例

需求

生成一個 excel 文件,並給第一個單元格賦值爲"測試",並設置列寬 26,行高 20.25,內容居中,下框線,單元格橙色填充。

編寫測試方法

CellUtil是 POI 自帶的工具類,這裏簡化了三句代碼(建立單元格,設置樣式,賦值)。注意,當寫入 xlsx 的大文件時,能夠考慮使用SXSSFWorkbook來避免 OOM。

@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 文件,並填充了第一個單元格:

WriteTest01

讀excel--批量導入excel數據到數據庫

需求

將 excel 中的用戶數據導入到數據庫(sql 已提供,在當前項目的 extend/sql 下),數據格式以下:

ReadTest02

該文件總計1000條數據,xls 大小 128 KB,xlsx 大小 40 KB,兩種類型文件內容一致。

編寫測試方法

通常 excel 的內容格式是提早約定好的,咱們知道用戶數據哪一列是用戶名,哪一列是電話號碼,因此,在獲取單元格數據後能夠準確地轉換,但這種方式須要針對不一樣的對象分別定義一個轉換方法。

@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<UserDTO> list = new ArrayList<>();

        InputStream in = new FileInputStream(path);
        // 建立工做簿
        Workbook wb = WorkbookFactory.create(in);

        // 獲取工做表
        Sheet sheet = wb.getSheetAt(0);

        // 獲取全部行
        Iterator<Row> 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();
    }
    /**
     * <p>經過行數據構造用戶對象</p>
     */
    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;
    }

測試

運行以上方法,能夠在數據庫看到導入的數據:

ReadTest03

寫excel--批量導出數據庫數據到excel

需求

將數據庫的用戶數據導出到excel中。這個例子使用模板進行導出,模板以下(若是是 xlsx 的大文件,爲了可以使用SXSSFWorkbook最好不要用模板)。

WriteTest02

編寫測試方法

寫入的時候使用樣式仍是比較繁瑣,實際開發能不使用盡可能不要用,或者也能夠單獨封裝成一個方法。注意,構造Workbook時不要使用WorkbookFactory.create(file)方式,不然,模板也會被修改。

@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<UserDTO> 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();
    }

測試

運行以上方法,在指定文件夾能夠看到生成的文件:

WriteTest03

讀xls--使用SAX方式

需求

使用 SAX 的方式將 xls 中的用戶數據導入到數據庫,數據與以上例子同樣。

編寫測試方法

相比前面的例子,使用 SAX 方式內存佔用小,效率高,可是 POI 提供的這套 API 用起來很是繁瑣,使用時不得沒必要須去了解 xls 文件的結構。我這裏只是簡單展現,監聽器部分的代碼不太嚴謹,實際項目仍是用 easyexcel 來操做吧。

@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<UserDTO> list = new ArrayList<UserDTO>();

        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<UserDTO> getList() {
            return list;
        }
    }

測試

運行以上方法,能夠在數據庫看到導入的數據:

ReadTest03

讀xlsx--使用SAX方式

需求

使用 SAX 的方式將 xlsx 中的用戶數據導入到數據庫,數據與以上例子同樣。

編寫測試方法

POI 針對 xlsx 的 SAX API 也是很是繁瑣,屬於很是低級的封裝,這裏居然須要使用 JDK 原生的 SAX 解析來處理事件,定義事件處理器時,我必須去了解 xml 的節點結構。和上面例子同樣,這裏也只是簡單地演示這套 API 的使用,具體代碼不太嚴謹,固然,實際開發咱們不會採用這種方式,建議仍是使用 easyexcel 吧。

@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<UserDTO> 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<UserDTO> getList() {
            return list;
        }
    }

測試

運行以上方法,能夠在數據庫看到導入的數據:

ReadTest03

使用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 進行轉換。

@ContentRowHeight(16)
public class UserDTO implements Serializable {

    private static final long serialVersionUID = 1L;
    
    @ExcelIgnore
    private String id;

    /**
     * <p>用戶名</p>
     */
    @ExcelProperty(value = { "用戶名" }, index = 1)
    private String name;

    /**
     * <p>性別</p>
     */
    @ExcelProperty(value = { "性別" }, index = 2)
    private String genderStr;

    /**
     * <p>年齡</p>
     */
    @ExcelProperty(value = { "年齡" }, index = 3)
    private Integer age;

    /**
     * <p>電話號碼</p>
     */
    @ExcelProperty(value = { "手機號" }, index = 4)
    @ColumnWidth(14)
    private String phone;
    
    @ExcelIgnore
    private Integer gender = 0;
    
    // 如下省略setter/getter方法
}

批量導入excel 數據到數據庫

easyexcel 封裝或重寫了 POI SAX 部分的 API,因此也是須要設置回調的監聽器,如下方式會採用默認的監聽器,並返回封裝好的對象。

@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<UserDTO> list = EasyExcel.read(path).head(UserDTO.class).sheet(0).headRowNumber(2).doReadSync();
        // 保存
        new UserService().save(list);
    }

固然,咱們也能夠採用自定義的監聽器,以下:

@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<UserDTO> list = new ArrayList<UserDTO>();
        
        // 定義回調監聽器
        ReadListener<UserDTO> syncReadListener = new AnalysisEventListener<UserDTO>() {
            @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 的操做。

@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<UserDTO> 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://github.com/ZhangZiSheng001/poi-demo

本文爲原創文章,轉載請附上原文出處連接: http://www.javashuo.com/article/p-flxvizqo-v.html

相關文章
相關標籤/搜索