diff --git a/README.md b/README.md index 0cf42e0..17ce619 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,8 @@ # JAVA解析Excel工具easyexcel Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。easyexcel重写了poi对07版Excel的解析,能够原本一个3M的excel用POI sax依然需要100M左右内存降低到几M,并且再大的excel不会出现内存溢出,03版依赖POI的sax模式。在上层做了模型转换的封装,让使用者更加简单方便 ## 相关文档 -* [关于软件](/abouteasyexcel.md) * [快速使用](/quickstart.md) +* [关于软件](/abouteasyexcel.md) * [常见问题](/problem.md) * [更新记事](/update.md) * [English-README](/easyexcel_en.md) @@ -74,7 +74,7 @@ DEMO代码地址:[https://github.com/alibaba/easyexcel/blob/master/src/test/ja * 文件下载 *
  • 1. 创建excel对应的实体对象 参照{@link DownloadData} *
  • 2. 设置返回的 参数 - *
  • 3. 直接写,这里注意,finish的时候会自动关闭OutputStream,当然你外面再关闭异常问题不大 + *
  • 3. 直接写,这里注意,finish的时候会自动关闭OutputStream,当然你外面再关闭流问题不大 */ @GetMapping("download") public void download(HttpServletResponse response) throws IOException { diff --git a/img/readme/quickstart/read/demo.png b/img/readme/quickstart/read/demo.png new file mode 100644 index 0000000..6ac47ce Binary files /dev/null and b/img/readme/quickstart/read/demo.png differ diff --git a/quickstart.md b/quickstart.md index 296e276..b597669 100644 --- a/quickstart.md +++ b/quickstart.md @@ -1,4 +1,10 @@ # easyexcel核心功能 +## 目录 +### 读 +DEMO代码地址:[https://github.com/alibaba/easyexcel/blob/master/src/test/java/com/alibaba/easyexcel/demo/read/ReadTest.java](/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java) +* [最简单的读](#simpleRead) + +### 写 ## *读任意大小的03、07版Excel不会OO]
    ## *读Excel自动通过注解,把结果映射为java模型
    @@ -12,343 +18,304 @@ ## *写Excel时一个sheet可以写多个Table
    ## *写Excel时候自定义是否需要写表头
    -## 读Excel - -使用easyexcel解析03、07版本的Excel只是ExcelTypeEnum不同,其他使用完全相同,使用者无需知道底层解析的差异。 - -### 无java模型直接把excel解析的每行结果以List<String>返回 在ExcelListener获取解析结果 -读excel代码示例如下: +## 读excel样例 +### 最简单的读 +#### excel示例 +![img](img/readme/quickstart/read/demo.png) +#### 对象 +```java +@Data +public class DemoData { + private String string; + private Date date; + private Double doubleData; +} ``` - @Test - public void testExcel2003NoModel() { - InputStream inputStream = getInputStream("loan1.xls"); - try { - // 解析每行结果在listener中处理 - ExcelListener listener = new ExcelListener(); - - ExcelReader excelReader = new ExcelReader(inputStream, ExcelTypeEnum.XLS, null, listener); - excelReader.read(); - } catch (Exception e) { - - } finally { - try { - inputStream.close(); - } catch (IOException e) { - e.printStackTrace(); - } +#### 监听器 +```java +public class DemoDataListener extends AnalysisEventListener { + private static final Logger LOGGER = LoggerFactory.getLogger(DemoDataListener.class); + /** + * 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收 + */ + private static final int BATCH_COUNT = 5; + List list = new ArrayList(); + + @Override + public void invoke(DemoData data, AnalysisContext context) { + LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data)); + list.add(data); + if (list.size() >= BATCH_COUNT) { + saveData(); + list.clear(); } } -``` -ExcelListener示例代码如下: -``` - /* 解析监听器, - * 每解析一行会回调invoke()方法。 - * 整个excel解析结束会执行doAfterAllAnalysed()方法 - * - * 下面只是我写的一个样例而已,可以根据自己的逻辑修改该类。 - * @author jipengfei - * @date 2017/03/14 - */ -public class ExcelListener extends AnalysisEventListener { - //自定义用于暂时存储data。 - //可以通过实例获取该值 - private List datas = new ArrayList(); - public void invoke(Object object, AnalysisContext context) { - System.out.println("当前行:"+context.getCurrentRowNum()); - System.out.println(object); - datas.add(object);//数据存储到list,供批量处理,或后续自己业务逻辑处理。 - doSomething(object);//根据自己业务做处理 - } - private void doSomething(Object object) { - //1、入库调用接口 - } + @Override public void doAfterAllAnalysed(AnalysisContext context) { - // datas.clear();//解析结束销毁不用的资源 - } - public List getDatas() { - return datas; + saveData(); + LOGGER.info("所有数据解析完成!"); } - public void setDatas(List datas) { - this.datas = datas; + + /** + * 加上存储数据库 + */ + private void saveData() { + LOGGER.info("{}条数据,开始存储数据库!", list.size()); + LOGGER.info("存储数据库成功!"); } } ``` -### 有java模型映射 -java模型写法如下: +#### 代码 +```java + /** + * 最简单的读 + *
  • 1. 创建excel对应的实体对象 参照{@link DemoData} + *
  • 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener} + *
  • 3. 直接读即可 + */ + @Test + public void simpleRead() { + // 写法1: + String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; + // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭 + EasyExcelFactory.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead(); + + // 写法2: + fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; + ExcelReader excelReader = EasyExcelFactory.read(fileName, DemoData.class, new DemoDataListener()).build(); + ReadSheet readSheet = EasyExcelFactory.readSheet(0).build(); + excelReader.read(readSheet); + // 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的 + excelReader.finish(); + } ``` -public class LoanInfo extends BaseRowModel { - @ExcelProperty(index = 0) - private String bankLoanId; - - @ExcelProperty(index = 1) - private Long customerId; - - @ExcelProperty(index = 2,format = "yyyy/MM/dd") - private Date loanDate; - - @ExcelProperty(index = 3) - private BigDecimal quota; - - @ExcelProperty(index = 4) - private String bankInterestRate; - - @ExcelProperty(index = 5) - private Integer loanTerm; - - @ExcelProperty(index = 6,format = "yyyy/MM/dd") - private Date loanEndDate; - - @ExcelProperty(index = 7) - private BigDecimal interestPerMonth; - @ExcelProperty(value = {"一级表头","二级表头"}) - private BigDecimal sax; +### 指定列的下标或者列名 +#### excel示例 +参照:[excel示例](#simpleReadExcel) +#### 对象 +```java +@Data +public class IndexOrNameData { + /** + * 强制读取第三个 这里不建议 index 和 name 同时用,要么一个对象只用index,要么一个对象只用name去匹配 + */ + @ExcelProperty(index = 2) + private Double doubleData; + /** + * 用名字去匹配,这里需要注意,如果名字重复,会导致只有一个字段读取到数据 + */ + @ExcelProperty("字符串标题") + private String string; + @ExcelProperty("日期标题") + private Date date; } ``` -@ExcelProperty(index = 3)数字代表该字段与excel对应列号做映射,也可以采用 @ExcelProperty(value = {"一级表头","二级表头"})用于解决不确切知道excel第几列和该字段映射,位置不固定,但表头的内容知道的情况。 -``` +#### 监听器 +参照:[监听器](#simpleReadListener) 只是泛型变了而已 +#### 代码 +```java + /** + * 指定列的下标或者列名 + * + *
  • 1. 创建excel对应的实体对象,并使用{@link ExcelProperty}注解. 参照{@link IndexOrNameData} + *
  • 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link IndexOrNameDataListener} + *
  • 3. 直接读即可 + */ @Test - public void testExcel2003WithReflectModel() { - InputStream inputStream = getInputStream("loan1.xls"); - try { - // 解析每行结果在listener中处理 - AnalysisEventListener listener = new ExcelListener(); - - ExcelReader excelReader = new ExcelReader(inputStream, ExcelTypeEnum.XLS, null, listener); - - excelReader.read(new Sheet(1, 2, LoanInfo.class)); - } catch (Exception e) { - - } finally { - try { - inputStream.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - + public void indexOrNameRead() { + String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; + // 这里默认读取第一个sheet + EasyExcelFactory.read(fileName, IndexOrNameData.class, new IndexOrNameDataListener()).sheet().doRead(); } ``` -带模型解析与不带模型解析主要在构造new Sheet(1, 2, LoanInfo.class)时候包含class。Class需要继承BaseRowModel暂时BaseRowModel没有任何内容,后面升级可能会增加一些默认的数据。 - -## 写Excel - -### 每行数据是List<String>无表头 - -``` - OutputStream out = new FileOutputStream("/Users/jipengfei/77.xlsx"); - try { - ExcelWriter writer = new ExcelWriter(out, ExcelTypeEnum.XLSX,false); - //写第一个sheet, sheet1 数据全是List 无模型映射关系 - Sheet sheet1 = new Sheet(1, 0); - sheet1.setSheetName("第一个sheet"); - writer.write(getListString(), sheet1); - writer.finish(); - } catch (Exception e) { - e.printStackTrace(); - } finally { - try { - out.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } -``` -### 每行数据是一个java模型有表头----表头层级为一 - -生成Excel格式如下图 -![屏幕快照 2017-06-02 上午9.49.39.png](http://ata2-img.cn-hangzhou.img-pub.aliyun-inc.com/dfcb44d05380e2e26bce93f850d9fc99.png) - -模型写法如下: +### 读多个sheet +#### excel示例 +参照:[excel示例](#simpleReadExcel) +#### 对象 +参照:[对象](#simpleReadObject) +#### 监听器 +参照:[监听器](#simpleReadListener) +#### 代码 +```java + /** + * 读多个sheet,这里注意一个sheet不能读取多次,一定要多次需要重新读取文件 + *
  • 1. 创建excel对应的实体对象 参照{@link DemoData} + *
  • 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener} + *
  • 3. 直接读即可 + */ + @Test + public void repeatedRead() { + String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; + ExcelReader excelReader = EasyExcelFactory.read(fileName, DemoData.class, new DemoDataListener()).build(); + ReadSheet readSheet1 = EasyExcelFactory.readSheet(0).build(); + ReadSheet readSheet2 = EasyExcelFactory.readSheet(1).build(); + excelReader.read(readSheet1); + excelReader.read(readSheet2); + // 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的 + excelReader.finish(); + } ``` -public class ExcelPropertyIndexModel extends BaseRowModel { - - @ExcelProperty(value = "姓名" ,index = 0) - private String name; - - @ExcelProperty(value = "年龄",index = 1) - private String age; - - @ExcelProperty(value = "邮箱",index = 2) - private String email; - - @ExcelProperty(value = "地址",index = 3) - private String address; - @ExcelProperty(value = "性别",index = 4) - private String sax; - - @ExcelProperty(value = "高度",index = 5) - private String heigh; - - @ExcelProperty(value = "备注",index = 6) - private String last; +### 日期、数字或者自定义格式转换 +#### excel示例 +参照:[excel示例](#simpleReadExcel) +#### 对象 +```java +@Data +public class ConverterData { + /** + * 我自定义 转换器,不管数据库传过来什么 。我给他加上“自定义:” + */ + @ExcelProperty(converter = CustomStringStringConverter.class) + private String string; + /** + * 这里用string 去接日期才能格式化。我想接收年月日格式 + */ + @DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒") + private String date; + /** + * 我想接收百分比的数字 + */ + @NumberFormat("#.##%") + private String doubleData; } ``` - @ExcelProperty(value = "姓名",index = 0) value是表头数据,默认会写在excel的表头位置,index代表第几列。 -``` - @Test - public void test1() throws FileNotFoundException { - OutputStream out = new FileOutputStream("/Users/jipengfei/78.xlsx"); - try { - ExcelWriter writer = new ExcelWriter(out, ExcelTypeEnum.XLSX); - //写第一个sheet, sheet1 数据全是List 无模型映射关系 - Sheet sheet1 = new Sheet(1, 0,ExcelPropertyIndexModel.class); - writer.write(getData(), sheet1); - writer.finish(); - } catch (Exception e) { - e.printStackTrace(); - } finally { - try { - out.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } +#### 监听器 +参照:[监听器](#simpleReadListener) 只是泛型变了 +#### 自定义转换器 +````java +public class CustomStringStringConverter implements Converter { + @Override + public Class supportJavaTypeKey() { + return String.class; } -``` - -### 每行数据是一个java模型有表头----表头层级为多层级 - -生成Excel格式如下图: -![屏幕快照 2017-06-02 上午9.53.07.png](http://ata2-img.cn-hangzhou.img-pub.aliyun-inc.com/0cdb1673665e7940cd670871afb4b3d7.png) -java模型写法如下: -``` -public class MultiLineHeadExcelModel extends BaseRowModel { - - @ExcelProperty(value = {"表头1","表头1","表头31"},index = 0) - private String p1; - @ExcelProperty(value = {"表头1","表头1","表头32"},index = 1) - private String p2; - - @ExcelProperty(value = {"表头3","表头3","表头3"},index = 2) - private int p3; - - @ExcelProperty(value = {"表头4","表头4","表头4"},index = 3) - private long p4; - - @ExcelProperty(value = {"表头5","表头51","表头52"},index = 4) - private String p5; - - @ExcelProperty(value = {"表头6","表头61","表头611"},index = 5) - private String p6; + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.STRING; + } - @ExcelProperty(value = {"表头6","表头61","表头612"},index = 6) - private String p7; + /** + * 这里读的时候会调用 + * + * @param cellData + * NotNull + * @param contentProperty + * Nullable + * @param globalConfiguration + * NotNull + * @return + */ + @Override + public String convertToJavaData(CellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return "自定义:" + cellData.getStringValue(); + } - @ExcelProperty(value = {"表头6","表头62","表头621"},index = 7) - private String p8; + /** + * 这里是写的时候会调用 不用管 + * + * @param value + * NotNull + * @param contentProperty + * Nullable + * @param globalConfiguration + * NotNull + * @return + */ + @Override + public CellData convertToExcelData(String value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return new CellData(value); + } - @ExcelProperty(value = {"表头6","表头62","表头622"},index = 8) - private String p9; } -``` -写Excel写法同上,只需将ExcelPropertyIndexModel.class改为MultiLineHeadExcelModel.class - - -### 一个Excel多个sheet写法 - -``` - @Test - public void test1() throws FileNotFoundException { - - OutputStream out = new FileOutputStream("/Users/jipengfei/77.xlsx"); - try { - ExcelWriter writer = new ExcelWriter(out, ExcelTypeEnum.XLSX,false); - //写第一个sheet, sheet1 数据全是List 无模型映射关系 - Sheet sheet1 = new Sheet(1, 0); - sheet1.setSheetName("第一个sheet"); - writer.write(getListString(), sheet1); - - //写第二个sheet sheet2 模型上打有表头的注解,合并单元格 - Sheet sheet2 = new Sheet(2, 3, MultiLineHeadExcelModel.class, "第二个sheet", null); - sheet2.setTableStyle(getTableStyle1()); - writer.write(getModeldatas(), sheet2); - - //写sheet3 模型上没有注解,表头数据动态传入 - List> head = new ArrayList>(); - List headCoulumn1 = new ArrayList(); - List headCoulumn2 = new ArrayList(); - List headCoulumn3 = new ArrayList(); - headCoulumn1.add("第一列"); - headCoulumn2.add("第二列"); - headCoulumn3.add("第三列"); - head.add(headCoulumn1); - head.add(headCoulumn2); - head.add(headCoulumn3); - Sheet sheet3 = new Sheet(3, 1, NoAnnModel.class, "第三个sheet", head); - writer.write(getNoAnnModels(), sheet3); - writer.finish(); - } catch (Exception e) { - e.printStackTrace(); - } finally { - try { - out.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } +```` +#### 代码 +```java + /** + * 日期、数字或者自定义格式转换 + *

    + * 默认读的转换器{@link DefaultConverterLoader#loadDefaultReadConverter()} + *

  • 1. 创建excel对应的实体对象 参照{@link ConverterData}.里面可以使用注解{@link DateTimeFormat}、{@link NumberFormat}或者自定义注解 + *
  • 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link ConverterDataListener} + *
  • 3. 直接读即可 + */ + @Test + public void converterRead() { + String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; + // 这里 需要指定读用哪个class去读,然后读取第一个sheet 然后千万别忘记 finish + EasyExcelFactory.read(fileName, ConverterData.class, new ConverterDataListener()) + // 这里注意 我们也可以registerConverter来指定自定义转换器, 但是这个转换变成全局了, 所有java为string,excel为string的都会用这个转换器。 + // 如果就想单个字段使用请使用@ExcelProperty 指定converter + // .registerConverter(new CustomStringStringConverter()) + // 读取sheet + .sheet().doRead(); } ``` -### 一个sheet中有多个表格 - +### 多行头 +#### excel示例 +参照:[excel示例](#simpleReadExcel) +#### 对象 +参照:[对象](#simpleReadObject) +#### 监听器 +参照:[监听器](#simpleReadListener) +#### 代码 +```java + /** + * 多行头 + * + *
  • 1. 创建excel对应的实体对象 参照{@link DemoData} + *
  • 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener} + *
  • 3. 设置headRowNumber参数,然后读。 这里要注意headRowNumber如果不指定, 会根据你传入的class的{@link ExcelProperty#value()}里面的表头的数量来决定行数, + * 如果不传入class则默认为1.当然你指定了headRowNumber不管是否传入class都是以你传入的为准。 + */ + @Test + public void complexHeaderRead() { + String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; + // 这里 需要指定读用哪个class去读,然后读取第一个sheet 然后千万别忘记 finish + EasyExcelFactory.read(fileName, DemoData.class, new DemoDataListener()).sheet() + // 这里可以设置1,因为头就是一行。如果多行头,可以设置其他值。不传入也可以,因为默认会根据DemoData 来解析,他没有指定头,也就是默认1行 + .headRowNumber(1).doRead(); + } ``` -@Test - public void test2() throws FileNotFoundException { - OutputStream out = new FileOutputStream("/Users/jipengfei/77.xlsx"); - try { - ExcelWriter writer = new ExcelWriter(out, ExcelTypeEnum.XLSX,false); - - //写sheet1 数据全是List 无模型映射关系 - Sheet sheet1 = new Sheet(1, 0); - sheet1.setSheetName("第一个sheet"); - Table table1 = new Table(1); - writer.write(getListString(), sheet1, table1); - writer.write(getListString(), sheet1, table1); - - //写sheet2 模型上打有表头的注解 - Table table2 = new Table(2); - table2.setTableStyle(getTableStyle1()); - table2.setClazz(MultiLineHeadExcelModel.class); - writer.write(getModeldatas(), sheet1, table2); - //写sheet3 模型上没有注解,表头数据动态传入,此情况下模型field顺序与excel现实顺序一致 - List> head = new ArrayList>(); - List headCoulumn1 = new ArrayList(); - List headCoulumn2 = new ArrayList(); - List headCoulumn3 = new ArrayList(); - headCoulumn1.add("第一列"); - headCoulumn2.add("第二列"); - headCoulumn3.add("第三列"); - head.add(headCoulumn1); - head.add(headCoulumn2); - head.add(headCoulumn3); - Table table3 = new Table(3); - table3.setHead(head); - table3.setClazz(NoAnnModel.class); - table3.setTableStyle(getTableStyle2()); - writer.write(getNoAnnModels(), sheet1, table3); - writer.write(getNoAnnModels(), sheet1, table3); +### 同步的返回 +#### excel示例 +参照:[excel示例](#simpleReadExcel) +#### 对象 +参照:[对象](#simpleReadObject) +#### 代码 +```java + /** + * 同步的返回,不推荐使用,如果数据量大会把数据放到内存里面 + */ + @Test + public void synchronousRead() { + String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; + // 这里 需要指定读用哪个class去读,然后读取第一个sheet 同步读取会自动finish + List list = EasyExcelFactory.read(fileName).head(DemoData.class).sheet().doReadSync(); + for (Object obj : list) { + DemoData data = (DemoData)obj; + LOGGER.info("读取到数据:{}", JSON.toJSONString(data)); + } - writer.finish(); - } catch (Exception e) { - e.printStackTrace(); - } finally { - try { - out.close(); - } catch (IOException e) { - e.printStackTrace(); - } + // 这里 也可以不指定class,返回一个list,然后读取第一个sheet 同步读取会自动finish + list = EasyExcelFactory.read(fileName).sheet().doReadSync(); + for (Object obj : list) { + // 返回每条数据的键值对 表示所在的列 和所在列的值 + Map data = (Map)obj; + LOGGER.info("读取到数据:{}", JSON.toJSONString(data)); } } ``` -## 测试数据分析 +## 测试数据分析 ![POI usermodel PK easyexcel(Excel 2003).png](http://ata2-img.cn-hangzhou.img-pub.aliyun-inc.com/02c4bfbbab99a649788523d04f84a42f.png) ![POI usermodel PK easyexcel(Excel 2007).png](http://ata2-img.cn-hangzhou.img-pub.aliyun-inc.com/f6a8a19ec959f0eb564e652de523fc9e.png) ![POI usermodel PK easyexcel(Excel 2003) (1).png](http://ata2-img.cn-hangzhou.img-pub.aliyun-inc.com/26888f7ea1cb8dc56db494926544edf7.png) diff --git a/src/main/java/com/alibaba/excel/context/WriteContextImpl.java b/src/main/java/com/alibaba/excel/context/WriteContextImpl.java index 3564cda..e4dbec8 100644 --- a/src/main/java/com/alibaba/excel/context/WriteContextImpl.java +++ b/src/main/java/com/alibaba/excel/context/WriteContextImpl.java @@ -13,7 +13,6 @@ import org.apache.poi.ss.util.CellRangeAddress; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.alibaba.excel.enums.WriteLastRowType; import com.alibaba.excel.exception.ExcelGenerateException; import com.alibaba.excel.metadata.Head; import com.alibaba.excel.util.WorkBookUtil; @@ -198,18 +197,13 @@ public class WriteContextImpl implements WriteContext { if (!currentWriteHolder.needHead() || !currentWriteHolder.excelWriteHeadProperty().hasHead()) { return; } - int lastRowNum = writeSheetHolder.getSheet().getLastRowNum(); - // 'lastRowNum' doesn't matter if it has one or zero,is's zero - if (WriteLastRowType.HAVE_DATA == writeSheetHolder.getWriteLastRowType()) { - lastRowNum++; - } - writeSheetHolder.setWriteLastRowType(WriteLastRowType.HAVE_DATA); - int rowIndex = lastRowNum + currentWriteHolder.relativeHeadRowIndex(); + int newRowIndex = writeSheetHolder.getNewRowIndexAndStartDoWrite(); + newRowIndex += currentWriteHolder.relativeHeadRowIndex(); // Combined head - addMergedRegionToCurrentSheet(excelWriteHeadProperty, rowIndex); - for (int relativeRowIndex = 0, i = rowIndex; i < excelWriteHeadProperty.getHeadRowNumber() + rowIndex; + addMergedRegionToCurrentSheet(excelWriteHeadProperty, newRowIndex); + for (int relativeRowIndex = 0, i = newRowIndex; i < excelWriteHeadProperty.getHeadRowNumber() + newRowIndex; i++, relativeRowIndex++) { - beforeRowCreate(rowIndex, relativeRowIndex); + beforeRowCreate(newRowIndex, relativeRowIndex); Row row = WorkBookUtil.createRow(writeSheetHolder.getSheet(), i); afterRowCreate(row, relativeRowIndex); addOneRowOfHeadDataToExcel(row, excelWriteHeadProperty.getHeadMap(), relativeRowIndex); diff --git a/src/main/java/com/alibaba/excel/converters/DefaultConverterLoader.java b/src/main/java/com/alibaba/excel/converters/DefaultConverterLoader.java index 8bb4fdb..0bbf8f0 100644 --- a/src/main/java/com/alibaba/excel/converters/DefaultConverterLoader.java +++ b/src/main/java/com/alibaba/excel/converters/DefaultConverterLoader.java @@ -40,28 +40,34 @@ import com.alibaba.excel.converters.string.StringStringConverter; * @author Jiaju Zhuang */ public class DefaultConverterLoader { + private static Map defaultWriteConverter; + private static Map allConverter; + /** * Load default write converter * * @return */ public static Map loadDefaultWriteConverter() { - Map converterMap = new HashMap(16); - putWriteConverter(converterMap, new BigDecimalNumberConverter()); - putWriteConverter(converterMap, new BooleanBooleanConverter()); - putWriteConverter(converterMap, new ByteNumberConverter()); - putWriteConverter(converterMap, new DateStringConverter()); - putWriteConverter(converterMap, new DoubleNumberConverter()); - putWriteConverter(converterMap, new FloatNumberConverter()); - putWriteConverter(converterMap, new IntegerNumberConverter()); - putWriteConverter(converterMap, new LongNumberConverter()); - putWriteConverter(converterMap, new ShortNumberConverter()); - putWriteConverter(converterMap, new StringStringConverter()); - return converterMap; + if (defaultWriteConverter != null) { + return defaultWriteConverter; + } + defaultWriteConverter = new HashMap(16); + putWriteConverter(new BigDecimalNumberConverter()); + putWriteConverter(new BooleanBooleanConverter()); + putWriteConverter(new ByteNumberConverter()); + putWriteConverter(new DateStringConverter()); + putWriteConverter(new DoubleNumberConverter()); + putWriteConverter(new FloatNumberConverter()); + putWriteConverter(new IntegerNumberConverter()); + putWriteConverter(new LongNumberConverter()); + putWriteConverter(new ShortNumberConverter()); + putWriteConverter(new StringStringConverter()); + return defaultWriteConverter; } - private static void putWriteConverter(Map converterMap, Converter converter) { - converterMap.put(ConverterKeyBuild.buildKey(converter.supportJavaTypeKey()), converter); + private static void putWriteConverter(Converter converter) { + defaultWriteConverter.put(ConverterKeyBuild.buildKey(converter.supportJavaTypeKey()), converter); } /** @@ -70,51 +76,63 @@ public class DefaultConverterLoader { * @return */ public static Map loadDefaultReadConverter() { - Map converterMap = new HashMap(64); - putReadConverter(converterMap, new BigDecimalBooleanConverter()); - putReadConverter(converterMap, new BigDecimalNumberConverter()); - putReadConverter(converterMap, new BigDecimalStringConverter()); - - putReadConverter(converterMap, new BooleanBooleanConverter()); - putReadConverter(converterMap, new BooleanNumberConverter()); - putReadConverter(converterMap, new BooleanStringConverter()); - - putReadConverter(converterMap, new ByteBooleanConverter()); - putReadConverter(converterMap, new ByteNumberConverter()); - putReadConverter(converterMap, new ByteStringConverter()); - - putReadConverter(converterMap, new DateNumberConverter()); - putReadConverter(converterMap, new DateStringConverter()); - - putReadConverter(converterMap, new DoubleBooleanConverter()); - putReadConverter(converterMap, new DoubleNumberConverter()); - putReadConverter(converterMap, new DoubleStringConverter()); - - putReadConverter(converterMap, new FloatBooleanConverter()); - putReadConverter(converterMap, new FloatNumberConverter()); - putReadConverter(converterMap, new FloatStringConverter()); - - putReadConverter(converterMap, new IntegerBooleanConverter()); - putReadConverter(converterMap, new IntegerNumberConverter()); - putReadConverter(converterMap, new IntegerStringConverter()); - - putReadConverter(converterMap, new LongBooleanConverter()); - putReadConverter(converterMap, new LongNumberConverter()); - putReadConverter(converterMap, new LongStringConverter()); - - putReadConverter(converterMap, new ShortBooleanConverter()); - putReadConverter(converterMap, new ShortNumberConverter()); - putReadConverter(converterMap, new ShortStringConverter()); - - putReadConverter(converterMap, new StringBooleanConverter()); - putReadConverter(converterMap, new StringNumberConverter()); - putReadConverter(converterMap, new StringStringConverter()); - putReadConverter(converterMap, new StringErrorConverter()); - return converterMap; + return loadAllConverter(); + } + + /** + * Load all converter + * + * @return + */ + public static Map loadAllConverter() { + if (allConverter != null) { + return allConverter; + } + allConverter = new HashMap(64); + putAllConverter(new BigDecimalBooleanConverter()); + putAllConverter(new BigDecimalNumberConverter()); + putAllConverter(new BigDecimalStringConverter()); + + putAllConverter(new BooleanBooleanConverter()); + putAllConverter(new BooleanNumberConverter()); + putAllConverter(new BooleanStringConverter()); + + putAllConverter(new ByteBooleanConverter()); + putAllConverter(new ByteNumberConverter()); + putAllConverter(new ByteStringConverter()); + + putAllConverter(new DateNumberConverter()); + putAllConverter(new DateStringConverter()); + + putAllConverter(new DoubleBooleanConverter()); + putAllConverter(new DoubleNumberConverter()); + putAllConverter(new DoubleStringConverter()); + + putAllConverter(new FloatBooleanConverter()); + putAllConverter(new FloatNumberConverter()); + putAllConverter(new FloatStringConverter()); + + putAllConverter(new IntegerBooleanConverter()); + putAllConverter(new IntegerNumberConverter()); + putAllConverter(new IntegerStringConverter()); + + putAllConverter(new LongBooleanConverter()); + putAllConverter(new LongNumberConverter()); + putAllConverter(new LongStringConverter()); + + putAllConverter(new ShortBooleanConverter()); + putAllConverter(new ShortNumberConverter()); + putAllConverter(new ShortStringConverter()); + + putAllConverter(new StringBooleanConverter()); + putAllConverter(new StringNumberConverter()); + putAllConverter(new StringStringConverter()); + putAllConverter(new StringErrorConverter()); + return allConverter; } - private static void putReadConverter(Map converterMap, Converter converter) { - converterMap.put(ConverterKeyBuild.buildKey(converter.supportJavaTypeKey(), converter.supportExcelTypeKey()), + private static void putAllConverter(Converter converter) { + allConverter.put(ConverterKeyBuild.buildKey(converter.supportJavaTypeKey(), converter.supportExcelTypeKey()), converter); } } diff --git a/src/main/java/com/alibaba/excel/enums/WriteLastRowType.java b/src/main/java/com/alibaba/excel/enums/WriteLastRowType.java index d68bb7b..bd477bd 100644 --- a/src/main/java/com/alibaba/excel/enums/WriteLastRowType.java +++ b/src/main/java/com/alibaba/excel/enums/WriteLastRowType.java @@ -7,11 +7,15 @@ package com.alibaba.excel.enums; **/ public enum WriteLastRowType { /** - * Tables are created without templates ,And any data has been written; + * Excel are created without templates ,And any data has been written; */ - EMPTY, + COMMON_EMPTY, /** - * It's supposed to have data in it.Tables are created with templates ,or any data has been written; + * Excel are created with templates ,And any data has been written; */ - HAVE_DATA,; + TEMPLATE_EMPTY, + /** + * Any data has been written; + */ + HAS_DATA,; } diff --git a/src/main/java/com/alibaba/excel/util/WorkBookUtil.java b/src/main/java/com/alibaba/excel/util/WorkBookUtil.java index 71bbffd..1fa7834 100644 --- a/src/main/java/com/alibaba/excel/util/WorkBookUtil.java +++ b/src/main/java/com/alibaba/excel/util/WorkBookUtil.java @@ -25,11 +25,20 @@ public class WorkBookUtil { public static Workbook createWorkBook(WriteWorkbookHolder writeWorkbookHolder) throws IOException, InvalidFormatException { if (ExcelTypeEnum.XLSX.equals(writeWorkbookHolder.getExcelType())) { + XSSFWorkbook xssfWorkbook = null; if (writeWorkbookHolder.getTemplateFile() != null) { - return new SXSSFWorkbook(new XSSFWorkbook(writeWorkbookHolder.getTemplateFile())); + xssfWorkbook = new XSSFWorkbook(writeWorkbookHolder.getTemplateFile()); } if (writeWorkbookHolder.getTemplateInputStream() != null) { - return new SXSSFWorkbook(new XSSFWorkbook(writeWorkbookHolder.getTemplateInputStream())); + xssfWorkbook = new XSSFWorkbook(writeWorkbookHolder.getTemplateInputStream()); + } + // When using SXSSFWorkbook, you can't get the actual last line.But we need to read the last line when we + // are using the template, so we cache it + if (xssfWorkbook != null) { + for (int i = 0; i < xssfWorkbook.getNumberOfSheets(); i++) { + writeWorkbookHolder.getTemplateLastRowMap().put(i, xssfWorkbook.getSheetAt(i).getLastRowNum()); + } + return new SXSSFWorkbook(xssfWorkbook); } return new SXSSFWorkbook(500); } diff --git a/src/main/java/com/alibaba/excel/write/ExcelBuilderImpl.java b/src/main/java/com/alibaba/excel/write/ExcelBuilderImpl.java index 6f06e11..a15ed79 100644 --- a/src/main/java/com/alibaba/excel/write/ExcelBuilderImpl.java +++ b/src/main/java/com/alibaba/excel/write/ExcelBuilderImpl.java @@ -10,14 +10,12 @@ import java.util.Set; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Row; -import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.util.CellRangeAddress; import com.alibaba.excel.context.WriteContext; import com.alibaba.excel.context.WriteContextImpl; import com.alibaba.excel.converters.Converter; import com.alibaba.excel.converters.ConverterKeyBuild; -import com.alibaba.excel.enums.WriteLastRowType; import com.alibaba.excel.exception.ExcelDataConvertException; import com.alibaba.excel.exception.ExcelGenerateException; import com.alibaba.excel.metadata.BaseRowModel; @@ -64,22 +62,14 @@ public class ExcelBuilderImpl implements ExcelBuilder { return; } WriteSheetHolder writeSheetHolder = context.writeSheetHolder(); - Sheet currentSheet = writeSheetHolder.getSheet(); - int lastRowNum = currentSheet.getLastRowNum(); - // 'lastRowNum' doesn't matter if it has one or zero,is's zero - if (lastRowNum == 0 && WriteLastRowType.EMPTY == writeSheetHolder.getWriteLastRowType()) { - lastRowNum--; - } - if (!data.isEmpty()) { - writeSheetHolder.setWriteLastRowType(WriteLastRowType.HAVE_DATA); - } + int newRowIndex = writeSheetHolder.getNewRowIndexAndStartDoWrite(); if (writeSheetHolder.isNew() && !writeSheetHolder.getExcelWriteHeadProperty().hasHead()) { - lastRowNum += context.currentWriteHolder().relativeHeadRowIndex(); + newRowIndex += context.currentWriteHolder().relativeHeadRowIndex(); } - // Beanmap is out of order,so use fieldList + // BeanMap is out of order,so use fieldList List fieldList = new ArrayList(); for (int relativeRowIndex = 0; relativeRowIndex < data.size(); relativeRowIndex++) { - int n = relativeRowIndex + lastRowNum + 1; + int n = relativeRowIndex + newRowIndex; addOneRowOfDataToExcel(data.get(relativeRowIndex), n, relativeRowIndex, fieldList); } } diff --git a/src/main/java/com/alibaba/excel/write/merge/LoopMergeStrategy.java b/src/main/java/com/alibaba/excel/write/merge/LoopMergeStrategy.java index 086891f..85694a8 100644 --- a/src/main/java/com/alibaba/excel/write/merge/LoopMergeStrategy.java +++ b/src/main/java/com/alibaba/excel/write/merge/LoopMergeStrategy.java @@ -13,21 +13,24 @@ import com.alibaba.excel.metadata.Head; */ public class LoopMergeStrategy extends AbstractMergeStrategy { private int eachRow; - private int eachColumn; + private int columnIndex; - public LoopMergeStrategy(int eachRow, int eachColumn) { - if (eachRow < 1 || eachColumn < 1) { - throw new IllegalArgumentException("All parameters must be greater than 1"); + public LoopMergeStrategy(int eachRow, int columnIndex) { + if (eachRow < 1) { + throw new IllegalArgumentException("EachRows must be greater than 1"); + } + if (columnIndex < 0) { + throw new IllegalArgumentException("ColumnIndex must be greater than 0"); } this.eachRow = eachRow; - this.eachColumn = eachColumn; + this.columnIndex = columnIndex; } @Override protected void merge(Sheet sheet, Cell cell, Head head, int relativeRowIndex) { - if (relativeRowIndex % eachRow == 0 && head.getColumnIndex() % eachColumn == 0) { + if (head.getColumnIndex() == columnIndex && relativeRowIndex % eachRow == 0) { CellRangeAddress cellRangeAddress = new CellRangeAddress(cell.getRowIndex(), - cell.getRowIndex() + eachRow - 1, cell.getColumnIndex(), cell.getColumnIndex() + eachColumn - 1); + cell.getRowIndex() + eachRow - 1, cell.getColumnIndex(), cell.getColumnIndex()); sheet.addMergedRegion(cellRangeAddress); } } diff --git a/src/main/java/com/alibaba/excel/write/metadata/holder/WriteSheetHolder.java b/src/main/java/com/alibaba/excel/write/metadata/holder/WriteSheetHolder.java index 432c622..7cce9f1 100644 --- a/src/main/java/com/alibaba/excel/write/metadata/holder/WriteSheetHolder.java +++ b/src/main/java/com/alibaba/excel/write/metadata/holder/WriteSheetHolder.java @@ -7,6 +7,7 @@ import org.apache.poi.ss.usermodel.Sheet; import com.alibaba.excel.enums.HolderEnum; import com.alibaba.excel.enums.WriteLastRowType; +import com.alibaba.excel.support.ExcelTypeEnum; import com.alibaba.excel.write.metadata.WriteSheet; /** @@ -60,9 +61,9 @@ public class WriteSheetHolder extends AbstractWriteHolder { this.parentWriteWorkbookHolder = writeWorkbookHolder; this.hasBeenInitializedTable = new HashMap(); if (writeWorkbookHolder.getTemplateInputStream() == null && writeWorkbookHolder.getTemplateFile() == null) { - writeLastRowType = WriteLastRowType.EMPTY; + writeLastRowType = WriteLastRowType.COMMON_EMPTY; } else { - writeLastRowType = WriteLastRowType.HAVE_DATA; + writeLastRowType = WriteLastRowType.TEMPLATE_EMPTY; } } @@ -122,6 +123,36 @@ public class WriteSheetHolder extends AbstractWriteHolder { this.writeLastRowType = writeLastRowType; } + /** + * Get the last line of index,you have to make sure that the data is written next + * + * @return + */ + public int getNewRowIndexAndStartDoWrite() { + // 'getLastRowNum' doesn't matter if it has one or zero,is's zero + int newRowIndex = 0; + switch (writeLastRowType) { + case TEMPLATE_EMPTY: + if (parentWriteWorkbookHolder.getExcelType() == ExcelTypeEnum.XLSX) { + if (parentWriteWorkbookHolder.getTemplateLastRowMap().containsKey(sheetNo)) { + newRowIndex = parentWriteWorkbookHolder.getTemplateLastRowMap().get(sheetNo); + } + } else { + newRowIndex = sheet.getLastRowNum(); + } + newRowIndex++; + break; + case HAS_DATA: + newRowIndex = sheet.getLastRowNum(); + newRowIndex++; + break; + default: + break; + } + writeLastRowType = WriteLastRowType.HAS_DATA; + return newRowIndex; + } + @Override public HolderEnum holderType() { return HolderEnum.SHEET; diff --git a/src/main/java/com/alibaba/excel/write/metadata/holder/WriteWorkbookHolder.java b/src/main/java/com/alibaba/excel/write/metadata/holder/WriteWorkbookHolder.java index 36a6ada..d3c07f5 100644 --- a/src/main/java/com/alibaba/excel/write/metadata/holder/WriteWorkbookHolder.java +++ b/src/main/java/com/alibaba/excel/write/metadata/holder/WriteWorkbookHolder.java @@ -72,6 +72,11 @@ public class WriteWorkbookHolder extends AbstractWriteHolder { * prevent duplicate creation of sheet objects */ private Map hasBeenInitializedSheet; + /** + * When using SXSSFWorkbook, you can't get the actual last line.But we need to read the last line when we are using + * the template, so we cache it + */ + private Map templateLastRowMap; public WriteWorkbookHolder(WriteWorkbook writeWorkbook) { super(writeWorkbook, null, writeWorkbook.getConvertAllFiled()); @@ -114,6 +119,7 @@ public class WriteWorkbookHolder extends AbstractWriteHolder { this.mandatoryUseInputStream = writeWorkbook.getMandatoryUseInputStream(); } this.hasBeenInitializedSheet = new HashMap(); + this.templateLastRowMap = new HashMap(8); } public Workbook getWorkbook() { @@ -196,6 +202,14 @@ public class WriteWorkbookHolder extends AbstractWriteHolder { this.mandatoryUseInputStream = mandatoryUseInputStream; } + public Map getTemplateLastRowMap() { + return templateLastRowMap; + } + + public void setTemplateLastRowMap(Map templateLastRowMap) { + this.templateLastRowMap = templateLastRowMap; + } + @Override public HolderEnum holderType() { return HolderEnum.WORKBOOK; diff --git a/src/main/java/com/alibaba/excel/write/property/ExcelWriteHeadProperty.java b/src/main/java/com/alibaba/excel/write/property/ExcelWriteHeadProperty.java index 8d05424..fbd33c4 100644 --- a/src/main/java/com/alibaba/excel/write/property/ExcelWriteHeadProperty.java +++ b/src/main/java/com/alibaba/excel/write/property/ExcelWriteHeadProperty.java @@ -7,9 +7,13 @@ import java.util.List; import java.util.Map; import java.util.Set; +import com.alibaba.excel.annotation.format.NumberFormat; import com.alibaba.excel.annotation.write.style.ColumnWidth; import com.alibaba.excel.annotation.write.style.ContentRowHeight; import com.alibaba.excel.annotation.write.style.HeadRowHeight; +import com.alibaba.excel.converters.ConverterKeyBuild; +import com.alibaba.excel.converters.DefaultConverterLoader; +import com.alibaba.excel.enums.CellDataTypeEnum; import com.alibaba.excel.enums.HeadKindEnum; import com.alibaba.excel.metadata.CellRange; import com.alibaba.excel.metadata.Head; @@ -48,8 +52,16 @@ public class ExcelWriteHeadProperty extends ExcelHeadProperty { columnWidth = parentColumnWidth; } headData.setColumnWidthProperty(ColumnWidthProperty.build(columnWidth)); - } + // If have @NumberFormat, 'NumberStringConverter' is specified by default + if (excelContentPropertyData.getConverter() == null) { + NumberFormat numberFormat = field.getAnnotation(NumberFormat.class); + if (numberFormat != null) { + excelContentPropertyData.setConverter(DefaultConverterLoader.loadAllConverter() + .get(ConverterKeyBuild.buildKey(field.getType(), CellDataTypeEnum.STRING))); + } + } + } } public RowHeightProperty getHeadRowHeightProperty() { diff --git a/src/test/java/com/alibaba/easyexcel/test/core/template/TemplateDataTest.java b/src/test/java/com/alibaba/easyexcel/test/core/template/TemplateDataTest.java index 07f2c15..369bc8b 100644 --- a/src/test/java/com/alibaba/easyexcel/test/core/template/TemplateDataTest.java +++ b/src/test/java/com/alibaba/easyexcel/test/core/template/TemplateDataTest.java @@ -42,14 +42,14 @@ public class TemplateDataTest { EasyExcel.write(file, TemplateData.class) .withTemplate(TestFileUtil.readFile("template" + File.separator + "template07.xlsx")).sheet() .doWrite(data()); - EasyExcel.read(file, TemplateData.class, new TemplateDataListener()).headRowNumber(2).sheet().doRead(); + EasyExcel.read(file, TemplateData.class, new TemplateDataListener()).headRowNumber(3).sheet().doRead(); } private void readAndWrite03(File file) { EasyExcel.write(file, TemplateData.class) .withTemplate(TestFileUtil.readFile("template" + File.separator + "template03.xls")).sheet() .doWrite(data()); - EasyExcel.read(file, TemplateData.class, new TemplateDataListener()).headRowNumber(2).sheet().doRead(); + EasyExcel.read(file, TemplateData.class, new TemplateDataListener()).headRowNumber(3).sheet().doRead(); } private List data() { diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/poi/PoiTest.java b/src/test/java/com/alibaba/easyexcel/test/demo/poi/PoiTest.java new file mode 100644 index 0000000..dab6c99 --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/demo/poi/PoiTest.java @@ -0,0 +1,54 @@ +package com.alibaba.easyexcel.test.demo.poi; + +import java.io.File; +import java.io.IOException; + +import org.apache.poi.xssf.streaming.SXSSFRow; +import org.apache.poi.xssf.streaming.SXSSFSheet; +import org.apache.poi.xssf.streaming.SXSSFWorkbook; +import org.apache.poi.xssf.usermodel.XSSFRow; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.junit.Ignore; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.easyexcel.test.util.TestFileUtil; + +/** + * 测试poi + * + * @author Jiaju Zhuang + **/ +@Ignore +public class PoiTest { + private static final Logger LOGGER = LoggerFactory.getLogger(PoiTest.class); + + @Test + public void lastRowNum() throws IOException { + String file = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; + SXSSFWorkbook xssfWorkbook = new SXSSFWorkbook(new XSSFWorkbook(file)); + SXSSFSheet xssfSheet = xssfWorkbook.getSheetAt(0); + LOGGER.info("一共行数:{}", xssfSheet.getLastRowNum()); + SXSSFRow row = xssfSheet.getRow(0); + LOGGER.info("第一行数据:{}", row); + xssfSheet.createRow(20); + LOGGER.info("一共行数:{}", xssfSheet.getLastRowNum()); + } + + @Test + public void lastRowNumXSSF() throws IOException { + String file = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; + XSSFWorkbook xssfWorkbook = new XSSFWorkbook(file); + LOGGER.info("一共:{}个sheet", xssfWorkbook.getNumberOfSheets()); + XSSFSheet xssfSheet = xssfWorkbook.getSheetAt(0); + LOGGER.info("一共行数:{}", xssfSheet.getLastRowNum()); + XSSFRow row = xssfSheet.getRow(0); + LOGGER.info("第一行数据:{}", row); + xssfSheet.createRow(20); + LOGGER.info("一共行数:{}", xssfSheet.getLastRowNum()); + + + } +} diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java b/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java index 8a671eb..4bbae8a 100644 --- a/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java +++ b/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java @@ -13,6 +13,8 @@ import com.alibaba.easyexcel.test.util.TestFileUtil; import com.alibaba.excel.EasyExcelFactory; import com.alibaba.excel.ExcelReader; import com.alibaba.excel.annotation.ExcelProperty; +import com.alibaba.excel.annotation.format.DateTimeFormat; +import com.alibaba.excel.annotation.format.NumberFormat; import com.alibaba.excel.converters.DefaultConverterLoader; import com.alibaba.excel.read.metadata.ReadSheet; import com.alibaba.fastjson.JSON; @@ -50,7 +52,10 @@ public class ReadTest { /** * 指定列的下标或者列名 - *
  • 使用{@link com.alibaba.excel.annotation.ExcelProperty}注解即可 + * + *
  • 1. 创建excel对应的实体对象,并使用{@link ExcelProperty}注解. 参照{@link IndexOrNameData} + *
  • 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link IndexOrNameDataListener} + *
  • 3. 直接读即可 */ @Test public void indexOrNameRead() { @@ -78,10 +83,10 @@ public class ReadTest { } /** - * 日期、数字或者自定义格式转换。 + * 日期、数字或者自定义格式转换 *

    * 默认读的转换器{@link DefaultConverterLoader#loadDefaultReadConverter()} - *

  • 1. 创建excel对应的实体对象 参照{@link ConverterData}.里面可以使用注解. + *
  • 1. 创建excel对应的实体对象 参照{@link ConverterData}.里面可以使用注解{@link DateTimeFormat}、{@link NumberFormat}或者自定义注解 *
  • 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link ConverterDataListener} *
  • 3. 直接读即可 */ @@ -98,7 +103,7 @@ public class ReadTest { } /** - * 多行头. + * 多行头 * *
  • 1. 创建excel对应的实体对象 参照{@link DemoData} *
  • 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener} @@ -134,7 +139,6 @@ public class ReadTest { Map data = (Map)obj; LOGGER.info("读取到数据:{}", JSON.toJSONString(data)); } - } } diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java b/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java index c70414e..37287d3 100644 --- a/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java +++ b/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java @@ -26,7 +26,7 @@ public class WebTest { * 文件下载 *
  • 1. 创建excel对应的实体对象 参照{@link DownloadData} *
  • 2. 设置返回的 参数 - *
  • 3. 直接写,这里注意,finish的时候会自动关闭OutputStream,当然你外面再关闭异常问题不大 + *
  • 3. 直接写,这里注意,finish的时候会自动关闭OutputStream,当然你外面再关闭流问题不大 */ @GetMapping("download") public void download(HttpServletResponse response) throws IOException { diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/write/ComplexHeadData.java b/src/test/java/com/alibaba/easyexcel/test/demo/write/ComplexHeadData.java new file mode 100644 index 0000000..44ce5cc --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/demo/write/ComplexHeadData.java @@ -0,0 +1,22 @@ +package com.alibaba.easyexcel.test.demo.write; + +import java.util.Date; + +import com.alibaba.excel.annotation.ExcelProperty; + +import lombok.Data; + +/** + * 复杂头数据.这里最终效果是第一行就一个主标题,第二行分类 + * + * @author Jiaju Zhuang + **/ +@Data +public class ComplexHeadData { + @ExcelProperty({"主标题", "字符串标题"}) + private String string; + @ExcelProperty({"主标题", "日期标题"}) + private Date date; + @ExcelProperty({"主标题", "数字标题"}) + private Double doubleData; +} diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/write/ConverterData.java b/src/test/java/com/alibaba/easyexcel/test/demo/write/ConverterData.java new file mode 100644 index 0000000..864cfcb --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/demo/write/ConverterData.java @@ -0,0 +1,35 @@ +package com.alibaba.easyexcel.test.demo.write; + +import java.util.Date; + +import com.alibaba.excel.annotation.ExcelProperty; +import com.alibaba.excel.annotation.format.DateTimeFormat; +import com.alibaba.excel.annotation.format.NumberFormat; + +import lombok.Data; + +/** + * 基础数据类.这里的排序和excel里面的排序一致 + * + * @author Jiaju Zhuang + **/ +@Data +public class ConverterData { + /** + * 我想所有的 字符串起前面加上"自定义:"三个字 + */ + @ExcelProperty(value = "字符串标题", converter = CustomStringStringConverter.class) + private String string; + /** + * 我想写到excel 用年月日的格式 + */ + @DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒") + @ExcelProperty("日期标题") + private Date date; + /** + * 我想写到excel 用百分比表示 + */ + @NumberFormat("#.##%") + @ExcelProperty(value = "数字标题") + private Double doubleData; +} diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/write/CustomStringStringConverter.java b/src/test/java/com/alibaba/easyexcel/test/demo/write/CustomStringStringConverter.java new file mode 100644 index 0000000..45ebc8c --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/demo/write/CustomStringStringConverter.java @@ -0,0 +1,59 @@ +package com.alibaba.easyexcel.test.demo.write; + +import com.alibaba.excel.converters.Converter; +import com.alibaba.excel.enums.CellDataTypeEnum; +import com.alibaba.excel.metadata.CellData; +import com.alibaba.excel.metadata.GlobalConfiguration; +import com.alibaba.excel.metadata.property.ExcelContentProperty; + +/** + * String and string converter + * + * @author Jiaju Zhuang + */ +public class CustomStringStringConverter implements Converter { + @Override + public Class supportJavaTypeKey() { + return String.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.STRING; + } + + /** + * 这里是读的时候会调用 不用管 + * + * @param cellData + * NotNull + * @param contentProperty + * Nullable + * @param globalConfiguration + * NotNull + * @return + */ + @Override + public String convertToJavaData(CellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return cellData.getStringValue(); + } + + /** + * 这里是写的时候会调用 不用管 + * + * @param value + * NotNull + * @param contentProperty + * Nullable + * @param globalConfiguration + * NotNull + * @return + */ + @Override + public CellData convertToExcelData(String value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return new CellData("自定义:" + value); + } + +} diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/write/IndexData.java b/src/test/java/com/alibaba/easyexcel/test/demo/write/IndexData.java new file mode 100644 index 0000000..81c2d4e --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/demo/write/IndexData.java @@ -0,0 +1,25 @@ +package com.alibaba.easyexcel.test.demo.write; + +import java.util.Date; + +import com.alibaba.excel.annotation.ExcelProperty; + +import lombok.Data; + +/** + * 基础数据类 + * + * @author Jiaju Zhuang + **/ +@Data +public class IndexData { + @ExcelProperty(value = "字符串标题", index = 0) + private String string; + @ExcelProperty(value = "日期标题", index = 1) + private Date date; + /** + * 这里设置3 会导致第二列空的 + */ + @ExcelProperty(value = "数字标题", index = 3) + private Double doubleData; +} diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/write/WidthAndHeightData.java b/src/test/java/com/alibaba/easyexcel/test/demo/write/WidthAndHeightData.java new file mode 100644 index 0000000..42a7f88 --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/demo/write/WidthAndHeightData.java @@ -0,0 +1,32 @@ +package com.alibaba.easyexcel.test.demo.write; + +import java.util.Date; + +import com.alibaba.excel.annotation.ExcelProperty; +import com.alibaba.excel.annotation.write.style.ColumnWidth; +import com.alibaba.excel.annotation.write.style.ContentRowHeight; +import com.alibaba.excel.annotation.write.style.HeadRowHeight; + +import lombok.Data; + +/** + * 基础数据类 + * + * @author Jiaju Zhuang + **/ +@Data +@ContentRowHeight(10) +@HeadRowHeight(20) +@ColumnWidth(25) +public class WidthAndHeightData { + @ExcelProperty("字符串标题") + private String string; + @ExcelProperty("日期标题") + private Date date; + /** + * 宽度为50 + */ + @ColumnWidth(50) + @ExcelProperty("数字标题") + private Double doubleData; +} diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java b/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java index 24b49c7..c3c19ad 100644 --- a/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java +++ b/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java @@ -1,16 +1,30 @@ package com.alibaba.easyexcel.test.demo.write; +import java.io.File; import java.util.ArrayList; import java.util.Date; import java.util.List; +import org.apache.poi.ss.usermodel.FillPatternType; +import org.apache.poi.ss.usermodel.IndexedColors; import org.junit.Ignore; import org.junit.Test; import com.alibaba.easyexcel.test.util.TestFileUtil; import com.alibaba.excel.EasyExcelFactory; import com.alibaba.excel.ExcelWriter; +import com.alibaba.excel.annotation.ExcelProperty; +import com.alibaba.excel.annotation.format.DateTimeFormat; +import com.alibaba.excel.annotation.format.NumberFormat; +import com.alibaba.excel.annotation.write.style.ColumnWidth; +import com.alibaba.excel.annotation.write.style.ContentRowHeight; +import com.alibaba.excel.annotation.write.style.HeadRowHeight; +import com.alibaba.excel.write.merge.LoopMergeStrategy; import com.alibaba.excel.write.metadata.WriteSheet; +import com.alibaba.excel.write.metadata.WriteTable; +import com.alibaba.excel.write.metadata.style.WriteCellStyle; +import com.alibaba.excel.write.metadata.style.WriteFont; +import com.alibaba.excel.write.style.HorizontalCellStyleStrategy; /** * 写的常见写法 @@ -21,19 +35,19 @@ import com.alibaba.excel.write.metadata.WriteSheet; public class WriteTest { /** * 最简单的写 - *
  • 1. 创建excel对应的实体对象 参照{@link com.alibaba.easyexcel.test.demo.write.DemoData} + *
  • 1. 创建excel对应的实体对象 参照{@link DemoData} *
  • 2. 直接写即可 */ @Test public void simpleWrite() { // 写法1 - String fileName = TestFileUtil.getPath() + "write" + System.currentTimeMillis() + ".xlsx"; + String fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx"; // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 // 如果这里想使用03 则 传入excelType参数即可 EasyExcelFactory.write(fileName, DemoData.class).sheet("模板").doWrite(data()); // 写法2 - fileName = TestFileUtil.getPath() + "write" + System.currentTimeMillis() + ".xlsx"; + fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx"; // 这里 需要指定写用哪个class去读 ExcelWriter excelWriter = EasyExcelFactory.write(fileName, DemoData.class).build(); WriteSheet writeSheet = EasyExcelFactory.writerSheet("模板").build(); @@ -42,12 +56,175 @@ public class WriteTest { excelWriter.finish(); } + /** + * 指定写入的列 + *
  • 1. 创建excel对应的实体对象 参照{@link IndexData} + *
  • 2. 使用{@link ExcelProperty}注解指定写入的列 + *
  • 3. 直接写即可 + */ + @Test + public void indexWrite() { + String fileName = TestFileUtil.getPath() + "indexWrite" + System.currentTimeMillis() + ".xlsx"; + // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + EasyExcelFactory.write(fileName, IndexData.class).sheet("模板").doWrite(data()); + } + + /** + * 复杂头写入 指定写入的列 + *
  • 1. 创建excel对应的实体对象 参照{@link ComplexHeadData} + *
  • 2. 使用{@link ExcelProperty}注解指定复杂的头 + *
  • 3. 直接写即可 + */ + @Test + public void complexHeadWrite() { + String fileName = TestFileUtil.getPath() + "complexHeadWrite" + System.currentTimeMillis() + ".xlsx"; + // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + EasyExcelFactory.write(fileName, ComplexHeadData.class).sheet("模板").doWrite(data()); + } + + /** + * 重复多次写入 + *
  • 1. 创建excel对应的实体对象 参照{@link ComplexHeadData} + *
  • 2. 使用{@link ExcelProperty}注解指定复杂的头 + *
  • 3. 直接调用二次写入即可 + */ + @Test + public void repeatedWrite() { + String fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx"; + // 这里 需要指定写用哪个class去读 + ExcelWriter excelWriter = EasyExcelFactory.write(fileName, DemoData.class).build(); + WriteSheet writeSheet = EasyExcelFactory.writerSheet("模板").build(); + // 第一次写入会创建头 + excelWriter.write(data(), writeSheet); + // 第二次写入会在上一次写入的最后一行后面写入 + excelWriter.write(data(), writeSheet); + /// 千万别忘记finish 会帮忙关闭流 + excelWriter.finish(); + + } + + /** + * 日期、数字或者自定义格式转换。 + *
  • 1. 创建excel对应的实体对象 参照{@link ConverterData} + *
  • 2. 使用{@link ExcelProperty}配合使用注解{@link DateTimeFormat}、{@link NumberFormat}或者自定义注解 + *
  • 3. 直接写即可 + */ + @Test + public void converterWrite() { + String fileName = TestFileUtil.getPath() + "converterWrite" + System.currentTimeMillis() + ".xlsx"; + // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + EasyExcelFactory.write(fileName, ConverterData.class).sheet("模板").doWrite(data()); + } + + /** + * 指定写入的列 + *
  • 1. 创建excel对应的实体对象 参照{@link IndexData} + *
  • 2. 使用{@link ExcelProperty}注解指定写入的列 + *
  • 3. 使用withTemplate 读取模板 + *
  • 4. 直接写即可 + */ + @Test + public void templateWrite() { + String templateFileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; + String fileName = TestFileUtil.getPath() + "templateWrite" + System.currentTimeMillis() + ".xlsx"; + // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + EasyExcelFactory.write(fileName, DemoData.class).withTemplate(templateFileName).sheet().doWrite(data()); + } + + /** + * 列宽、行高 + *
  • 1. 创建excel对应的实体对象 参照{@link WidthAndHeightData} + *
  • 2. 使用注解{@link ColumnWidth}、{@link HeadRowHeight}、{@link ContentRowHeight}指定宽度或高度 + *
  • 3. 直接写即可 + */ + @Test + public void widthAndHeightWrite() { + String fileName = TestFileUtil.getPath() + "widthAndHeightWrite" + System.currentTimeMillis() + ".xlsx"; + // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + EasyExcelFactory.write(fileName, WidthAndHeightData.class).sheet("模板").doWrite(data()); + } + + /** + * 自定义样式 + *
  • 1. 创建excel对应的实体对象 参照{@link DemoData} + *
  • 2. 创建一个style策略 并注册 + *
  • 3. 直接写即可 + */ + @Test + public void styleWrite() { + String fileName = TestFileUtil.getPath() + "styleWrite" + System.currentTimeMillis() + ".xlsx"; + // 头的策略 + WriteCellStyle headWriteCellStyle = new WriteCellStyle(); + // 背景设置为红色 + headWriteCellStyle.setFillForegroundColor(IndexedColors.RED.getIndex()); + WriteFont headWriteFont = new WriteFont(); + headWriteFont.setFontHeightInPoints((short)20); + headWriteCellStyle.setWriteFont(headWriteFont); + // 内容的策略 + WriteCellStyle contentWriteCellStyle = new WriteCellStyle(); + // 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND 不然无法显示背景颜色.头默认了 FillPatternType所以可以不指定 + contentWriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND); + // 背景绿色 + contentWriteCellStyle.setFillForegroundColor(IndexedColors.GREEN.getIndex()); + WriteFont contentWriteFont = new WriteFont(); + // 字体大小 + contentWriteFont.setFontHeightInPoints((short)20); + contentWriteCellStyle.setWriteFont(contentWriteFont); + // 这个策略是 头是头的样式 内容是内容的样式 其他的策略可以自己实现 + HorizontalCellStyleStrategy horizontalCellStyleStrategy = + new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle); + + // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + EasyExcelFactory.write(fileName, DemoData.class).registerWriteHandler(horizontalCellStyleStrategy).sheet("模板") + .doWrite(data()); + } + + /** + * 合并单元格 + *
  • 1. 创建excel对应的实体对象 参照{@link DemoData} + *
  • 2. 创建一个merge策略 并注册 + *
  • 3. 直接写即可 + */ + @Test + public void mergeWrite() { + String fileName = TestFileUtil.getPath() + "mergeWrite" + System.currentTimeMillis() + ".xlsx"; + // 每隔2行会合并 把eachColumn 设置成 3 也就是我们数据的长度,所以就第一列会合并。当然其他合并策略也可以自己写 + LoopMergeStrategy loopMergeStrategy = new LoopMergeStrategy(2, 0); + // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + EasyExcelFactory.write(fileName, DemoData.class).registerWriteHandler(loopMergeStrategy).sheet("模板") + .doWrite(data()); + } + + /** + * 使用table去写入 + *
  • 1. 创建excel对应的实体对象 参照{@link DemoData} + *
  • 2. 然后写入table即可 + */ + @Test + public void tableWrite() { + String fileName = TestFileUtil.getPath() + "tableWrite" + System.currentTimeMillis() + ".xlsx"; + // 这里直接写多个table的案例了,如果只有一个 也可以直一行代码搞定,参照其他案例 + // 这里 需要指定写用哪个class去读 + ExcelWriter excelWriter = EasyExcelFactory.write(fileName, DemoData.class).build(); + // 把sheet设置为不需要头 不然会输出sheet的头 这样看起来第一个table 就有2个头了 + WriteSheet writeSheet = EasyExcelFactory.writerSheet("模板").needHead(Boolean.FALSE).build(); + // 这里必须指定需要头,table 会继承sheet的配置,sheet配置了不需要,table 默认也是不需要 + WriteTable writeTable0 = EasyExcelFactory.writerTable(0).needHead(Boolean.TRUE).build(); + WriteTable writeTable1 = EasyExcelFactory.writerTable(1).needHead(Boolean.TRUE).build(); + // 第一次写入会创建头 + excelWriter.write(data(), writeSheet, writeTable0); + // 第二次写如也会创建头,然后在第一次的后面写入数据 + excelWriter.write(data(), writeSheet, writeTable1); + /// 千万别忘记finish 会帮忙关闭流 + excelWriter.finish(); + + } private List data() { List list = new ArrayList(); for (int i = 0; i < 10; i++) { DemoData data = new DemoData(); - data.setString("字符串" + 0); + data.setString("字符串" + i); data.setDate(new Date()); data.setDoubleData(0.56); list.add(data); diff --git a/src/test/resources/demo/demo.xlsx b/src/test/resources/demo/demo.xlsx index 722ead1..303733c 100644 Binary files a/src/test/resources/demo/demo.xlsx and b/src/test/resources/demo/demo.xlsx differ diff --git a/src/test/resources/template/template03.xls b/src/test/resources/template/template03.xls index e8b1631..7c17eee 100644 Binary files a/src/test/resources/template/template03.xls and b/src/test/resources/template/template03.xls differ diff --git a/src/test/resources/template/template07.xlsx b/src/test/resources/template/template07.xlsx index 8fbb692..a046fbc 100644 Binary files a/src/test/resources/template/template07.xlsx and b/src/test/resources/template/template07.xlsx differ diff --git a/update.md b/update.md index 6058627..e76566b 100644 --- a/update.md +++ b/update.md @@ -3,6 +3,8 @@ * 优化读写对外接口 * 加入转换器,方便格式转换 * 极大优化读大文件的内存和效率 +* sheetNo 改成0开始 +* 读支持指定列名 # 1.2.4 修复read()方法存在的bug # 1.2.1