详解POI的使用方法(DOM和SAX的方式)及存在的不足
简介
Apache POI是一套基于 OOXML 标准(Office Open XML)和 OLE2 标准来读写各种格式文件的 Java API,也就是说只要是遵循以上标准的文件,POI 都能够进行读写,而不仅仅只能操作我们熟知的办公程序文件。本文只会涉及到 excel 相关内容,其他文件的操作可以参考poi官方网站。
这里先总结下 POI 的使用体验。POI 面向接口的设计非常巧妙,使用 ss.usermodel 包读写 xls 和 xlsx 时,可以使用同一套代码,即使这两种文件格式采用的是完全不同的标准。POI 提供了SXSSFWorkbook用于解决 xlsx 写大文件时容易出现的 OOM 问题。但是,还是存在以下不足(都只针对读场景):
- 使用 ss.usermodel 包解析 excel 效率较低、内存占用较大,且容易出现 OOM。类似于 xml 中的 DOM,这种方式会在内存中构建整个文档的结构,在处理大文件时容易出现 OOM。然而,大部分场景我们并不需要随机地去访问 excel 中的节点。
- 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对象二进制储存格式。
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。
引入依赖
        <!-- 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();
    }
测试
运行以上方法,控制台打印出第一个单元格的内容:

写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 文件,并填充了第一个单元格:

读excel--批量导入excel数据到数据库
需求
将 excel 中的用户数据导入到数据库(sql 已提供,在当前项目的 extend/sql 下),数据格式如下:

该文件总计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;
    }
测试
运行以上方法,可以在数据库看到导入的数据:

写excel--批量导出数据库数据到excel
需求
将数据库的用户数据导出到excel中。这个例子使用模板进行导出,模板如下(如果是 xlsx 的大文件,为了能够使用SXSSFWorkbook最好不要用模板)。

编写测试方法
写入的时候使用样式还是比较繁琐,实际开发能不使用尽量不要用,或者也可以单独封装成一个方法。注意,构造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();
    }
测试
运行以上方法,在指定文件夹可以看到生成的文件:

读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;
        }
    }
测试
运行以上方法,可以在数据库看到导入的数据:

读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;
        }
    }
测试
运行以上方法,可以在数据库看到导入的数据:

使用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://www.cnblogs.com/ZhangZiSheng001/p/12329937.html
详解POI的使用方法(DOM和SAX的方式)及存在的不足的更多相关文章
- 详解Python模块导入方法
		python常被昵称为胶水语言,它能很轻松的把用其他语言制作的各种模块(尤其是C/C++)轻松联结在一起.python包含子目录中的模块方法比较简单,关键是能够在sys.path里面找到通向模块文件的 ... 
- HTTPS(SSL)详解以及PHP调用方法
		HTTPS 详解 1. 两个加密秘钥的概念 (1) 对称加密 即加密的秘钥和解密的秘钥一样 (2) 非对称加密 即加密的秘钥和解密的秘钥不一样, 分别称为公钥 和 私钥, 公钥完全公开 私钥解密者 ... 
- View绘制详解(五),draw方法细节详解之View的滚动/滑动问题
		关于View绘制系列的文章已经完成了四篇了,前面四篇文章主要带小伙伴们熟悉一下View的体系的整体框架.View的测量以及布局等过程,从本篇博客开始,我们就来看看View的绘制过程.View的绘制涉及 ... 
- Spring框架详解介绍-基本使用方法
		1.Spring框架-控制反转(IOC) 2.Spring框架-面向切面编程(AOP) 3.Spring 内置的JdbcTemplate(Spring-JDBC) Spring框架-控制反转(IOC) ... 
- XML - 十分钟了解XML结构以及DOM和SAX解析方式
		引言 NOKIA 有句著名的广告语:"科技以人为本".不论什么技术都是为了满足人的生产生活须要而产生的.详细到小小的一个手机.里面蕴含的技术也是浩如烟海.是几千年来人类科技的结晶, ... 
- 详解学习C#的方法和步骤
		如果你已经有较多的面向对象开发经验,跳过以下这两步: 第一步 掌握一门.NET面向对象语言,C#或VB.NET. 强烈反对在没系统学过一门面向对象语言的前提下去学ASP.NET. ASP.NET是一个 ... 
- 详解Linux安装GCC方法
		转载自:http://blog.csdn.net/bulljordan23/article/details/7723495/ 下载: http://ftp.gnu.org/gnu/gcc/gcc-4. ... 
- js学习--DOM操作详解大全 前奏(认识DOM)
		一 . 节点属性 DOM 是树型结构,相应的,可以通过一些节点属性来遍历节点树: 方法 说明 nodeName 节点名称,相当于tagName.属性节点返回属性名,文本节点返回#text.nodeNa ... 
- 为你详解Linux安装GCC方法
		下载: http://ftp.gnu.org/gnu/gcc/gcc-4.5.1/gcc-4.5.1.tar.bz2浏览: http://ftp.gnu.org/gnu/gcc/gcc-4.5.1/查 ... 
随机推荐
- MIAME|Highwire press
			生物信息学 GEO可存储基因芯片数据,支持MIAME.MIAME是minimum information about a microarry experiment.这之中存储研究原始数据+标准化之后的 ... 
- tomcat启动不了的问题
			tomcat启动的几个问题 1.端口冲突 2.非端口冲突,需要加入配置host文件 日志文件: 解决办法:https://blog.csdn.net/u012949658/article/detail ... 
- apache和tomcat的关系
			apache和tomcat的关系: 举个例子:apache是一辆卡车,上面可以装一些东西如html等.但是不能装水,要装水必须要有容器(桶),tomcat就是一个桶(装像JAVA这样的水),而这个桶也 ... 
- HTMLTestRunner测试报告
			把测试报告写入文件中,设置报告生成的路径 测试报告名称上添加时间 HTMLTestRunner文件如下,复制即可用,把该文件放在Lib下即可 """ A TestRunn ... 
- Json格式化的实现(Jackson、Gson)
			一.第一种(Jackson) 需要用到的jar包: https://pan.baidu.com/s/1wrkUwEoKpmqgmYPQSN-iZg package util; import com.f ... 
- 常见40个常用的js页面效果图
			1. oncontextmenu="window.event.returnValue=false" 将彻底屏蔽鼠标右键<table border oncontextmenu= ... 
- rpm报错warning: /var/tmp/rpm-tmp.1OZa8q: Header V3 DSA/SHA1 Signature, key ID 5072e1f5: NOKEY的解决
			参考链接:http://blog.51cto.com/zymin0823/1546537 报错: 解决:使用如下两个选项 
- 学习HEXO的历程
			前言: 简介 开始搭建 命令 API测试 逛github相关的帖子时,发现了hexo.正好想要做一个个人的博客,用来记录自己的各类感悟,所以花一些时间学习学习,以后博客可以放github,省得去注册c ... 
- C++走向远洋——28(项目三,时间类,2)
			*/ * Copyright (c) 2016,烟台大学计算机与控制工程学院 * All rights reserved. * 文件名:time.cpp * 作者:常轩 * 微信公众号:Worldhe ... 
- IT从业者疫情之下出路何在
			作为一个IT行业十年经历的从业人员,在北京大公司工作过,但因衡量着北京大都市的繁华下高消费和高房价,选择到二线城市发展和组建家庭,由此逃离北上广,结束了数年的北漂生涯.很荣幸到了二线城市顺利遇见属于自 ... 
