本文包含以下内容,由于篇幅较长,可以根据需要选择阅读:
1. POI的介绍
2. 如何使用POI及POI的不足
3. 如何使用easyexcel

Apache POI是一套基于 OOXML 标准(Office Open XML)和 OLE2 标准来读写各种格式文件的 Java API,也就是说只要是遵循以上标准的文件,POI 都能够进行读写,而不仅仅只能操作我们熟知的办公程序文件。本文只会涉及到 excel 相关内容,其他文件的操作可以参考poi官方网站

这里先总结下 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 本质上都是一种文件格式规范或标准,平时看到的 excel 中,有字体、公式、颜色、图片等等,看起来非常复杂,但是在文件结构上都遵循着固定的格式。

OLE2 文件一般包括 xls、doc、ppt 等,是二进制格式的文件。 相关内容可以参考:复合文档Ole对象二进制储存格式

OOXML文件一般包括 xlsx、docx、pptx 等。该类文件以指定格式的 xml 为基础并以 zip 格式压缩,这里我利用解压工具解压本地的一个 xml 文件,可以看到以下文件结构,在本文例子中,我们会重点关注 sharedStrings.xml 和 sheet1.xml 的内容,因为使用 SAX API 时必须用到:

xlsx文件结构

针对不同应用的文件,使用时需要引入对应的 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

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。

  1. <!-- junit -->
  2. <dependency>
  3. <groupId>junit</groupId>
  4. <artifactId>junit</artifactId>
  5. <version>4.12</version>
  6. <scope>test</scope>
  7. </dependency>
  8. <!-- poi-ooxml -->
  9. <dependency>
  10. <groupId>org.apache.poi</groupId>
  11. <artifactId>poi-ooxml</artifactId>
  12. <version>4.1.2</version>
  13. </dependency>
  14. <!-- easyexcel -->
  15. <dependency>
  16. <groupId>com.alibaba</groupId>
  17. <artifactId>easyexcel</artifactId>
  18. <version>2.1.6</version>
  19. </dependency>
  20. <!-- hikari -->
  21. <dependency>
  22. <groupId>com.zaxxer</groupId>
  23. <artifactId>HikariCP</artifactId>
  24. <version>2.6.1</version>
  25. </dependency>
  26. <!-- mysql驱动 -->
  27. <dependency>
  28. <groupId>mysql</groupId>
  29. <artifactId>mysql-connector-java</artifactId>
  30. <version>8.0.15</version>
  31. </dependency>

读取指定 excel 第一个单元格的内容。该指定文件第一个单元格内容为“测试”。

建议采用WorkbookFactory来获取Workbook实例,而不是根据文件类型写死具体的实现类。另外,获取单元格对象时建议采用 SheetUtil获取,里面会对行对象进行判空操作。

  1. @Test
  2. public void test01() throws IOException {
  3. // 处理XSSF
  4. String path = "extend\\file\\poi_test_01.xlsx";
  5. // 处理HSSF
  6. //String path = "extend\\file\\poi_test_01.xls";
  7. // 创建工作簿,会根据excel命名选择不同的Workbook实现类
  8. Workbook wb = WorkbookFactory.create(new File(path));
  9. // 获取工作表
  10. Sheet sheet = wb.getSheetAt(0);
  11. // 获取行
  12. Row row = sheet.getRow(0);
  13. // 获取单元格
  14. Cell cell = row.getCell(0);
  15. // 也可以采用以下方式获取单元格
  16. // Cell cell = SheetUtil.getCell(sheet, 0, 0);
  17. // 获取单元格内容
  18. String value = cell.getStringCellValue();
  19. System.err.println("第一个单元格字符:" + value);
  20. // 释放资源
  21. wb.close();
  22. }

运行以上方法,控制台打印出第一个单元格的内容:

ReadTest01

生成一个 excel 文件,并给第一个单元格赋值为”测试”,并设置列宽 26,行高 20.25,内容居中,下框线,单元格橙色填充。

CellUtil是 POI 自带的工具类,这里简化了三句代码(创建单元格,设置样式,赋值)。注意,当写入 xlsx 的大文件时,可以考虑使用SXSSFWorkbook来避免 OOM。

  1. @Test
  2. public void test01() throws FileNotFoundException, IOException {
  3. // 处理XSSF
  4. String path = "extend\\file\\poi_test_01.xlsx";
  5. // 处理HSSF
  6. // String path = "extend\\file\\poi_test_01.xls";
  7. // 创建工作簿
  8. boolean flag = path.endsWith(".xlsx");
  9. Workbook wb = WorkbookFactory.create(flag ? true : false);
  10. // Workbook wb = new SXSSFWorkbook(100);//内存仅保留100行数据,可避免OOM
  11. // 创建工作表
  12. Sheet sheet = wb.createSheet(WorkbookUtil.createSafeSheetName("MySheet001"));
  13. // 设置列宽
  14. sheet.setColumnWidth(0, 26 * 256);
  15. // 创建行(索引从0开始)
  16. Row row = sheet.createRow(0);
  17. // 设置行高
  18. row.setHeightInPoints(20.25f);
  19. // 创建单元格样式对象
  20. CellStyle style = wb.createCellStyle();
  21. // 设置样式
  22. style.setAlignment(HorizontalAlignment.CENTER); // 横向居中
  23. style.setVerticalAlignment(VerticalAlignment.CENTER);// 纵向居中
  24. style.setBorderBottom(BorderStyle.THIN);
  25. style.setFillForegroundColor(IndexedColors.ORANGE.getIndex());
  26. style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
  27. // 创建单元格、设置样式和内容
  28. CellUtil.createCell(row, 0, "测试", style);
  29. // 保存到本地目录
  30. OutputStream out = new FileOutputStream(new File(path));
  31. wb.write(out);
  32. // 释放资源
  33. out.close();
  34. wb.close();
  35. }

运行以上方法,指定路径下生成了 excel 文件,并填充了第一个单元格:

WriteTest01

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

ReadTest02

该文件总计1000条数据,xls 大小 128 KB,xlsx 大小 40 KB,两种类型文件内容一致。

一般 excel 的内容格式是提前约定好的,我们知道用户数据哪一列是用户名,哪一列是电话号码,所以,在获取单元格数据后可以准确地转换,但这种方式需要针对不同的对象分别定义一个转换方法。

  1. @Test
  2. public void test02() throws SQLException, IOException {
  3. // 处理XSSF
  4. String path = "extend\\file\\user_data.xlsx";
  5. // 处理HSSF
  6. //String path = "extend\\file\\user_data.xls";
  7. // 定义集合,用于存放excel中的用户数据
  8. List<UserDTO> list = new ArrayList<>();
  9. InputStream in = new FileInputStream(path);
  10. // 创建工作簿
  11. Workbook wb = WorkbookFactory.create(in);
  12. // 获取工作表
  13. Sheet sheet = wb.getSheetAt(0);
  14. // 获取所有行
  15. Iterator<Row> iterator = sheet.iterator();
  16. int rowNum = 0;
  17. // 遍历行
  18. while(iterator.hasNext()) {
  19. Row row = iterator.next();
  20. // 跳过标题行
  21. if(rowNum == 0 || rowNum == 1) {
  22. rowNum++;
  23. continue;
  24. }
  25. // 将用户对象保存到集合中
  26. list.add(constructUserByRow(row));
  27. }
  28. // 批量保存
  29. new UserService().save(list);
  30. // 释放资源
  31. in.close();
  32. wb.close();
  33. }
  34. /**
  35. * <p>通过行数据构造用户对象</p>
  36. */
  37. private UserDTO constructUserByRow(Row row) {
  38. UserDTO userDTO = new UserDTO();
  39. Cell cell = null;
  40. // 用户名
  41. cell = row.getCell(1);
  42. userDTO.setName(cell.getStringCellValue());
  43. // 性别
  44. cell = row.getCell(2);
  45. userDTO.setGenderStr(cell.getStringCellValue());
  46. // 年龄
  47. cell = row.getCell(3);
  48. userDTO.setAge(((Double)cell.getNumericCellValue()).intValue());
  49. // 电话
  50. cell = row.getCell(4);
  51. userDTO.setPhone(cell.getStringCellValue());
  52. return userDTO;
  53. }

运行以上方法,可以在数据库看到导入的数据:

ReadTest03

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

WriteTest02

写入的时候使用样式还是比较繁琐,实际开发能不使用尽量不要用,或者也可以单独封装成一个方法。注意,构造Workbook时不要使用WorkbookFactory.create(file)方式,否则,模板也会被修改。

  1. @Test
  2. public void test02() throws SQLException, IOException {
  3. // 处理XSSF
  4. String templatePath = "extend\\file\\user_data_template.xlsx";
  5. String outpath = "extend\\file\\user_data.xlsx";
  6. // 处理HSSF
  7. // String templatePath = "extend\\file\\user_data_template.xls";
  8. // String path = "extend\\file\\user_data.xls";
  9. InputStream in = new FileInputStream(templatePath);
  10. // 创建工作簿,注意,这里如果传入File对象,模板也会被改写
  11. Workbook wb = WorkbookFactory.create(in);
  12. // 读取工作表
  13. Sheet sheet = wb.getSheetAt(0);
  14. // 定义复用变量
  15. int rowIndex = 0; // 行的索引
  16. int cellIndex = 1; // 单元格的索引
  17. Row nRow = null;
  18. Cell nCell = null;
  19. // 读取大标题行
  20. nRow = sheet.getRow(rowIndex++); // 使用后 +1
  21. // 读取大标题的单元格
  22. nCell = nRow.getCell(cellIndex);
  23. // 设置大标题的内容
  24. nCell.setCellValue("2020年2月用户表");
  25. // 跳过第二行(模板的小标题)
  26. rowIndex++;
  27. // 读取第三行,获取它的样式
  28. nRow = sheet.getRow(rowIndex);
  29. // 读取行高
  30. float lineHeight = nRow.getHeightInPoints();
  31. // 获取第三行的4个单元格中的样式
  32. CellStyle cs1 = nRow.getCell(cellIndex++).getCellStyle();
  33. CellStyle cs2 = nRow.getCell(cellIndex++).getCellStyle();
  34. CellStyle cs3 = nRow.getCell(cellIndex++).getCellStyle();
  35. CellStyle cs4 = nRow.getCell(cellIndex++).getCellStyle();
  36. // 查询用户列表
  37. List<UserDTO> userList = new UserService().findAll().stream().map((x) -> new UserDTO(x)).collect(Collectors.toList());
  38. // 遍历数据
  39. for(UserDTO user : userList) {
  40. // 创建数据行
  41. nRow = sheet.createRow(rowIndex++);
  42. // 设置数据行高
  43. nRow.setHeightInPoints(lineHeight);
  44. // 重置cellIndex,从第一列开始写数据
  45. cellIndex = 1;
  46. // 创建数据单元格,设置单元格内容和样式
  47. // 用户名
  48. nCell = nRow.createCell(cellIndex++);
  49. nCell.setCellStyle(cs1);
  50. nCell.setCellValue(user.getName());
  51. // 性别
  52. nCell = nRow.createCell(cellIndex++);
  53. nCell.setCellStyle(cs2);
  54. nCell.setCellValue(user.getGenderStr());
  55. // 年龄
  56. nCell = nRow.createCell(cellIndex++);
  57. nCell.setCellStyle(cs3);
  58. nCell.setCellValue(user.getAge());
  59. // 手机号
  60. nCell = nRow.createCell(cellIndex++);
  61. nCell.setCellStyle(cs4);
  62. nCell.setCellValue(user.getPhone());
  63. }
  64. // 保存到本地目录
  65. OutputStream out = new FileOutputStream(new File(outpath));
  66. wb.write(out);
  67. // 释放资源
  68. out.close();
  69. wb.close();
  70. }

运行以上方法,在指定文件夹可以看到生成的文件:

WriteTest03

使用 SAX 的方式将 xls 中的用户数据导入到数据库,数据与以上例子一样。

相比前面的例子,使用 SAX 方式内存占用小,效率高,但是 POI 提供的这套 API 用起来非常繁琐,使用时不得不必须去了解 xls 文件的结构。我这里只是简单展示,监听器部分的代码不太严谨,实际项目还是用 easyexcel 来操作吧。

  1. @Test
  2. public void test02() throws Exception {
  3. // 创建POIFSFileSystem
  4. String filename = "extend\\file\\user_data.xls";
  5. POIFSFileSystem poifs = new POIFSFileSystem(new File(filename));
  6. // 创建HSSFRequest,并添加自定义监听器
  7. HSSFRequest req = new HSSFRequest();
  8. EventExample listener = new EventExample();
  9. req.addListenerForAllRecords(listener);
  10. // 解析和触发事件
  11. HSSFEventFactory factory = new HSSFEventFactory();
  12. factory.processWorkbookEvents(req, poifs);
  13. // 保存用户到数据库
  14. new UserService().save(listener.getList());
  15. poifs.close();
  16. }
  17. private static class EventExample implements HSSFListener {
  18. private SSTRecord sstrec;
  19. private int lastCellRow = -1;
  20. private int lastCellColumn = -1;
  21. private List<UserDTO> list = new ArrayList<UserDTO>();
  22. private UserDTO user;
  23. @Override
  24. public void processRecord(Record record) {
  25. switch(record.getSid()) {
  26. // 进入新的sheet
  27. case BoundSheetRecord.sid:
  28. lastCellRow = -1;
  29. lastCellColumn = -1;
  30. break;
  31. // excel中的数值类型和字符存放在不同的位置
  32. case NumberRecord.sid:
  33. NumberRecord numrec = (NumberRecord)record;
  34. // 用户年龄
  35. user.setAge(Double.valueOf(numrec.getValue()).intValue());
  36. lastCellRow = numrec.getRow();
  37. lastCellColumn = numrec.getColumn();
  38. break;
  39. // SSTRecords中存储着excel中使用的字符,重复的会合并为一个
  40. case SSTRecord.sid:
  41. sstrec = (SSTRecord)record;
  42. break;
  43. // 读取到单元格的字符
  44. case LabelSSTRecord.sid:
  45. LabelSSTRecord lrec = (LabelSSTRecord)record;
  46. int thisRow = lrec.getRow();
  47. // 用户数据从第三行开始
  48. if(thisRow >= 2) {
  49. // 进入新行时,原对象放入集合,并创建新对象
  50. if(thisRow != lastCellRow) {
  51. if(user != null) {
  52. list.add(user);
  53. }
  54. user = new UserDTO();
  55. }
  56. // 根据列数为用户对象设置属性
  57. switch(lrec.getColumn()) {
  58. case 1:
  59. // 用户名
  60. user.setName(sstrec.getString(lrec.getSSTIndex()).getString());
  61. break;
  62. case 2:
  63. // 用户性别
  64. user.setGenderStr(sstrec.getString(lrec.getSSTIndex()).getString());
  65. break;
  66. case 4:
  67. // 用户电话
  68. user.setPhone(sstrec.getString(lrec.getSSTIndex()).getString());
  69. break;
  70. default:
  71. break;
  72. }
  73. lastCellRow = thisRow;
  74. lastCellColumn = lrec.getColumn();
  75. }
  76. break;
  77. case EOFRecord.sid:
  78. // 最后一行读取完后直接放入集合
  79. if(lastCellRow != -1 && user != null && lastCellColumn == 4) {
  80. list.add(user);
  81. }
  82. break;
  83. default:
  84. break;
  85. }
  86. }
  87. public List<UserDTO> getList() {
  88. return list;
  89. }
  90. }

运行以上方法,可以在数据库看到导入的数据:

ReadTest03

使用 SAX 的方式将 xlsx 中的用户数据导入到数据库,数据与以上例子一样。

POI 针对 xlsx 的 SAX API 也是非常繁琐,属于非常低级的封装,这里竟然需要使用 JDK 原生的 SAX 解析来处理事件,定义事件处理器时,我必须去了解 xml 的节点结构。和上面例子一样,这里也只是简单地演示这套 API 的使用,具体代码不太严谨,当然,实际开发我们不会采用这种方式,建议还是使用 easyexcel 吧。

  1. @Test
  2. public void test01() throws Exception {
  3. String filename = "extend\\file\\user_data.xlsx";
  4. OPCPackage pkg = OPCPackage.open(filename);
  5. XSSFReader r = new XSSFReader(pkg);
  6. // 获取sharedStrings.xml的内容,这里存放着excel中的字符
  7. SharedStringsTable sst = r.getSharedStringsTable();
  8. // 接下来就是采用SAX方式解析xml的过程
  9. // 构造解析器,这里会设置自定义的处理器
  10. XMLReader parser = XMLHelper.newXMLReader();
  11. SheetHandler handler = new SheetHandler(sst);
  12. parser.setContentHandler(handler);
  13. // 解析指定的sheet
  14. InputStream sheet2 = r.getSheet("rId1");
  15. parser.parse(new InputSource(sheet2));
  16. // 保存用户到数据库
  17. new UserService().save(handler.getList());
  18. // handler.getList().forEach(System.err::println);
  19. sheet2.close();
  20. }
  21. private static class SheetHandler extends DefaultHandler {
  22. private SharedStringsTable sst;
  23. private String cellContents;
  24. private boolean cellContentsIsString;
  25. private int cellColumn = -1;
  26. private int cellRow = -1;
  27. List<UserDTO> list = new ArrayList<>();
  28. UserDTO user;
  29. private SheetHandler(SharedStringsTable sst) {
  30. this.sst = sst;
  31. }
  32. @Override
  33. public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException {
  34. // 读取到行
  35. if("row".equals(name)) {
  36. cellRow++;
  37. if(cellRow >= 2) {
  38. // 换行时重新创建用户实例
  39. user = new UserDTO();
  40. }
  41. }
  42. // 读取到列 c => cell
  43. if("c".equals(name) && cellRow >= 2) {
  44. // 设置当前读取到哪一列
  45. char columnChar = attributes.getValue("r").charAt(0);
  46. switch(columnChar) {
  47. case 'B':
  48. cellColumn = 1;
  49. break;
  50. case 'C':
  51. cellColumn = 2;
  52. break;
  53. case 'D':
  54. cellColumn = 3;
  55. break;
  56. case 'E':
  57. cellColumn = 4;
  58. break;
  59. default:
  60. cellColumn = -1;
  61. break;
  62. }
  63. // 当前单元格中的值是否为字符,是的话对应的值被放在SharedStringsTable中
  64. if("s".equals(attributes.getValue("t"))) {
  65. cellContentsIsString = true;
  66. }
  67. }
  68. // Clear contents cache
  69. cellContents = "";
  70. }
  71. @Override
  72. public void endElement(String uri, String localName, String name) throws SAXException {
  73. // 跳过标题
  74. if(cellRow < 2) {
  75. return;
  76. }
  77. // v节点是c的子节点,表示单元格的值
  78. if(name.equals("v")) {
  79. int idx;
  80. if(cellContentsIsString) {
  81. idx = Integer.parseInt(cellContents);
  82. } else {
  83. idx = Double.valueOf(cellContents).intValue();
  84. }
  85. switch(cellColumn) {
  86. case 1:
  87. user.setName(sst.getItemAt(idx).getString());
  88. break;
  89. case 2:
  90. user.setGenderStr(sst.getItemAt(idx).getString());
  91. break;
  92. case 3:
  93. // 年龄的值是数值类型,不在SharedStringsTable中
  94. user.setAge(idx);
  95. break;
  96. case 4:
  97. user.setPhone(sst.getItemAt(idx).getString());
  98. break;
  99. default:
  100. break;
  101. }
  102. }
  103. // 读取完一行,将用户对象放入集合中
  104. if("row".equals(name) && user != null) {
  105. list.add(user);
  106. }
  107. // 重置参数
  108. if("c".equals(name)) {
  109. cellColumn = -1;
  110. cellContentsIsString = false;
  111. }
  112. }
  113. @Override
  114. public void characters(char[] ch, int start, int length) {
  115. cellContents += new String(ch, start, length);
  116. }
  117. public List<UserDTO> getList() {
  118. return list;
  119. }
  120. }

运行以上方法,可以在数据库看到导入的数据:

ReadTest03

通过以上例子,我们会发现,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 进行转换。

  1. @ContentRowHeight(16)
  2. public class UserDTO implements Serializable {
  3. private static final long serialVersionUID = 1L;
  4. @ExcelIgnore
  5. private String id;
  6. /**
  7. * <p>用户名</p>
  8. */
  9. @ExcelProperty(value = { "用户名" }, index = 1)
  10. private String name;
  11. /**
  12. * <p>性别</p>
  13. */
  14. @ExcelProperty(value = { "性别" }, index = 2)
  15. private String genderStr;
  16. /**
  17. * <p>年龄</p>
  18. */
  19. @ExcelProperty(value = { "年龄" }, index = 3)
  20. private Integer age;
  21. /**
  22. * <p>电话号码</p>
  23. */
  24. @ExcelProperty(value = { "手机号" }, index = 4)
  25. @ColumnWidth(14)
  26. private String phone;
  27. @ExcelIgnore
  28. private Integer gender = 0;
  29. // 以下省略setter/getter方法
  30. }

easyexcel 封装或重写了 POI SAX 部分的 API,所以也是需要设置回调的监听器,以下方式会采用默认的监听器,并返回封装好的对象。

  1. @Test
  2. public void test02() throws SQLException, IOException {
  3. // XSSF
  4. String path = "D:\\growUp\\git_repository\\09-poi-demo\\extend\\file\\user_data.xlsx";
  5. // HSSF
  6. // String path = "D:\\growUp\\git_repository\\09-poi-demo\\extend\\file\\user_data.xls";
  7. // 读取excel
  8. List<UserDTO> list = EasyExcel.read(path).head(UserDTO.class).sheet(0).headRowNumber(2).doReadSync();
  9. // 保存
  10. new UserService().save(list);
  11. }

当然,我们也可以采用自定义的监听器,如下:

  1. @Test
  2. public void test01() throws SQLException, IOException {
  3. // XSSF
  4. String path = "D:\\growUp\\git_repository\\09-poi-demo\\extend\\file\\user_data.xlsx";
  5. // HSSF
  6. // String path = "D:\\growUp\\git_repository\\09-poi-demo\\extend\\file\\user_data.xls";
  7. List<UserDTO> list = new ArrayList<UserDTO>();
  8. // 定义回调监听器
  9. ReadListener<UserDTO> syncReadListener = new AnalysisEventListener<UserDTO>() {
  10. @Override
  11. public void invoke(UserDTO data, AnalysisContext context) {
  12. list.add(data);
  13. }
  14. @Override
  15. public void doAfterAllAnalysed(AnalysisContext context) {
  16. // TODO Auto-generated method stub
  17. }
  18. };
  19. // 读取excel
  20. EasyExcel.read(path, UserDTO.class, syncReadListener).sheet(0).headRowNumber(2).doRead();
  21. // 保存
  22. new UserService().save(list);
  23. }

和读一样,这里也只用了一行代码就完成了对 excel 的操作。

  1. @Test
  2. public void test01() throws SQLException, IOException {
  3. // XSSF
  4. String path = "D:\\growUp\\git_repository\\09-poi-demo\\extend\\file\\user_data.xlsx";
  5. // HSSF
  6. // String path = "D:\\growUp\\git_repository\\09-poi-demo\\extend\\file\\user_data.xls";
  7. // 获取用户数据
  8. List<UserDTO> list = new UserService().findAll().stream().map((x) -> new UserDTO(x)).collect(Collectors.toList());
  9. // 写入excel
  10. EasyExcel.write(path, UserDTO.class).sheet(0).relativeHeadRowIndex(1).doWrite(list);
  11. }

Apache POI – the Java API for Microsoft Documents

本文为原创文章,转载请附上原文出处链接:https://github.com/ZhangZiSheng001/01-spi-demo

版权声明:本文为ZhangZiSheng001原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/ZhangZiSheng001/p/12532074.html