Alibaba Easy Excel快速入门

EasyExcel快速上手

一、Maven坐标

<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>easyexcel</artifactId>
  <version>2.1.6</version>
</dependency>

二、常见问题

  1. 填充和写的选择

    填充其实也不会占用大量内存,用的也是文件缓存,最后统一书写,如果导出的内容各种格式复杂,建议直接用模板然后填充(填充的数据会自动有格式)。如果格式相对简单,建议直接用写,相对来说,直接导出性能还是高一丢丢。

  2. 部分字段读取或者写入为空

    读写反射对象用到了Cglib,所以成员变量必须符合驼峰规范,而且使用@Data不能使用@Accessors(chain = true)。后续会考虑支持非驼峰。

  3. 其他问题参考官方文档

三、读Excel

1. 最简单的读
  • 对象

    @Data
    public class Student {
        private String stu_name;
        private Integer age;
        private String address;
        private Float money;
    }
    
  • 表格(Excel)

    姓名 年龄 地址 余额
    张三 22 广东省深圳市 6543
    李四 18 湖北省武汉市 4900
    王五 36 云南省昆明市 5200
  • 监听器

    监听器采用匿名内部类的形式提供。

  • 代码

    @Test
    public void testRead() {
    
      // 被读取的文件绝对路径
      String fileName = "C:/location/myFiles/temp/students.xlsx";
    
      // 接收解析出的目标对象(Student)
      List<Student> studentList = new ArrayList<>();
      
      // 这里需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
      // excel中表的列要与对象的字段相对应
      EasyExcel.read(fileName, Student.class, new AnalysisEventListener<Student>() {
        
        // 每解析一条数据都会调用该方法
        @Override
        public void invoke(Student student, AnalysisContext analysisContext) {
          System.out.println("解析一条Student对象:" + JSON.toJSONString(student));
          studentList.add(student);
        }
    
        // 解析完毕的回调方法
        @Override
        public void doAfterAllAnalysed(AnalysisContext analysisContext) {
          System.out.println("excel文件读取完毕!");
        }
      }).sheet().doRead();
      
    }
    
2. 指定列的下标或列名

读的方法都一样(包括监听器),只不过要给目标对象的类上加如下注解(demo来自官方文档)。

@Data
public class IndexOrNameData {
  /**
   * 强制读取第三个 这里不建议 index 和 name 同时用,要么一个对象只用index,要么一个对象只用name	 * 去匹配
   */
  @ExcelProperty(index = 2)
  private Double doubleData;
  
  /**
   * 用名字去匹配,这里需要注意,如果名字重复,会导致只有一个字段读取到数据
   */
  @ExcelProperty("字符串标题")
  private String string;
  
  @ExcelProperty("日期标题")
  private Date date;
}
3. 不创建对象的读

不创建对象读取的话,直接用Map接收数据。

@Test
public void readWithoutObj() {
  
  // 被读取的文件绝对路径
  String fileName = "C:/location/myFiles/temp/students.xlsx";
  
  // 接收结果集,为一个List列表,每个元素为一个map对象,key-value对为excel中每个列对应的值
  List<Map<Integer,String>> resultList = new ArrayList<>();
  
  EasyExcel.read(fileName, new AnalysisEventListener<Map<Integer,String>>() {

    @Override
    public void invoke(Map<Integer, String> map, AnalysisContext analysisContext) {
      System.out.println("解析到一条数据:" + JSON.toJSONString(map));
      resultList.add(map);
    }
    
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
      System.out.println("excel文件解析完毕!" + JSON.toJSONString(resultList));
    }
  }).sheet().doRead();
}
4. 其他复杂方式读

读取多个sheet、日期/数字和自定义格式转换、多行头、同步返回、读取表头、web中的读等读法参考官方文档

四、写Excel

1. 最简单的写
@Test
public void testWrite() {
  // 测试写
  String writePath = "C:\\location\\myFiles\\temp\\students_write.xlsx";
  Student student_1 = new Student("小明",22,"四川省雅安市",4800f);
  Student student_2 = new Student("小张",19,"河南省郑州市",5100f);
  Student student_3 = new Student("小吴",26,"陕西省西安市",6500f);
  List<Student> studentList = new ArrayList<>();
  studentList.add(student_1);
  studentList.add(student_2);
  studentList.add(student_3);
  EasyExcel.write(writePath,Student.class).sheet().doWrite(studentList);
}
2. 根据参数写(排除)指定的列
/**
 * 根据参数只写入(或排除)指定的列
 */
@Test
public void writeIncludeOrExclude() {

  Student student_1 = new Student("小明",22,"四川省雅安市",4800f);
  Student student_2 = new Student("小张",19,"河南省郑州市",5100f);
  Student student_3 = new Student("小吴",26,"陕西省西安市",6500f);
  List<Student> studentList = new ArrayList<>();
  studentList.add(student_1);
  studentList.add(student_2);
  studentList.add(student_3);

  // 根据用户传入字段 假设我们要忽略 money
  String writePathExclude = "C:/students_write_exclude.xlsx";
  Set<String> excludeColumnFiledNames = new HashSet<String>();
  excludeColumnFiledNames.add("money");
  EasyExcel.write(writePathExclude,Student.class).excludeColumnFiledNames(excludeColumnFiledNames).sheet().doWrite(studentList);

  // 根据用户传入字段 假设我们只要 name address
  String writePathInclude = "C:/students_write_include.xlsx";
  Set<String> includeColumnFiledNames = new HashSet<String>();
  includeColumnFiledNames.add("stu_name");
  includeColumnFiledNames.add("address");
  EasyExcel.write(writePathInclude,Student.class).includeColumnFiledNames(includeColumnFiledNames).sheet().doWrite(studentList);
}
3. 指定写入列

参考官方文档。

  • Excel

    字符串标题 日期标题 数字标题
    字符串0 2020/4/21 10:13 0.43
    字符串1 2020/4/22 10:13 0.43
    字符串2 2020/4/23 10:13 0.43
    字符串3 2020/4/24 10:13 0.43
    字符串4 2020/4/25 10:13 0.43
    字符串5 2020/4/26 10:13 0.43
    字符串6 2020/4/27 10:13 0.43
    字符串7 2020/4/28 10:13 0.43
  • 对象

    @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;
    }
    
  • 代码

    /**
     * 指定写入的列
     * <p>1. 创建excel对应的实体对象 参照{@link IndexData}
     * <p>2. 使用{@link ExcelProperty}注解指定写入的列
     * <p>3. 直接写即可
     */
    @Test
    public void indexWrite() {
      String fileName = TestFileUtil.getPath() + "indexWrite" + System.currentTimeMillis() + ".xlsx";
      // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
      EasyExcel.write(fileName, IndexData.class).sheet("模板").doWrite(data());
    }
    
4. 重复多次写入(写到单个或者多个Sheet)
/**
   * 重复多次写入(写到单个或者多个Sheet)
   * <p>
   * 1. 创建excel对应的实体对象 参照{@link ComplexHeadData}
   * <p>
   * 2. 使用{@link ExcelProperty}注解指定复杂的头
   * <p>
   * 3. 直接调用二次写入即可
   */
@Test
public void repeatedWrite() {
  // 方法1 如果写到同一个sheet
  String fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx";
  // 这里 需要指定写用哪个class去写
  ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build();
  // 这里注意 如果同一个sheet只要创建一次
  WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
  // 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来
  for (int i = 0; i < 5; i++) {
    // 分页去数据库查询数据 这里可以去数据库查询每一页的数据
    List<DemoData> data = data();
    excelWriter.write(data, writeSheet);
  }
  // 千万别忘记finish 会帮忙关闭流
  excelWriter.finish();

  // 方法2 如果写到不同的sheet 同一个对象
  fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx";
  // 这里 指定文件
  excelWriter = EasyExcel.write(fileName, DemoData.class).build();
  // 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来。这里最终会写到5个sheet里面
  for (int i = 0; i < 5; i++) {
    // 每次都要创建writeSheet 这里注意必须指定sheetNo
    writeSheet = EasyExcel.writerSheet(i, "模板").build();
    // 分页去数据库查询数据 这里可以去数据库查询每一页的数据
    List<DemoData> data = data();
    excelWriter.write(data, writeSheet);
  }
  // 千万别忘记finish 会帮忙关闭流
  excelWriter.finish();

  // 方法3 如果写到不同的sheet 不同的对象
  fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx";
  // 这里 指定文件
  excelWriter = EasyExcel.write(fileName).build();
  // 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来。这里最终会写到5个sheet里面
  for (int i = 0; i < 5; i++) {
    // 每次都要创建writeSheet 这里注意必须指定sheetNo。这里注意DemoData.class 可以每次都变,我这里为了方便 所以用的同一个class 实际上可以一直变
    writeSheet = EasyExcel.writerSheet(i, "模板").head(DemoData.class).build();
    // 分页去数据库查询数据 这里可以去数据库查询每一页的数据
    List<DemoData> data = data();
    excelWriter.write(data, writeSheet);
  }
  // 千万别忘记finish 会帮忙关闭流
  excelWriter.finish();
}
5. 不创建对象写
/**
     * 不创建对象的写
     */
@Test
public void noModleWrite() {
  // 写法1
  String fileName = TestFileUtil.getPath() + "noModleWrite" + System.currentTimeMillis() + ".xlsx";
  // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
  EasyExcel.write(fileName).head(head()).sheet("模板").doWrite(dataList());
}

private List<List<String>> head() {
  List<List<String>> list = new ArrayList<List<String>>();
  List<String> head0 = new ArrayList<String>();
  head0.add("字符串" + System.currentTimeMillis());
  List<String> head1 = new ArrayList<String>();
  head1.add("数字" + System.currentTimeMillis());
  List<String> head2 = new ArrayList<String>();
  head2.add("日期" + System.currentTimeMillis());
  list.add(head0);
  list.add(head1);
  list.add(head2);
  return list;
}

private List<List<Object>> dataList() {
  List<List<Object>> list = new ArrayList<List<Object>>();
  for (int i = 0; i < 10; i++) {
    List<Object> data = new ArrayList<Object>();
    data.add("字符串" + i);
    data.add(new Date());
    data.add(0.56);
    list.add(data);
  }
  return list;
}
6. 其他复杂的写操作

日期、数字的格式化,列宽、高,自定义样式、单元格合并,web中的写等复杂操作参考官方文档

五、填充Excel

1. 最简单的填充
  • 模板

    姓名 数字 复杂 忽略
    {name}今年{number}岁 \{name}\忽略,
  • 最终结果

    姓名 数字 复杂 忽略
    张三 5.4 张三今年5.4岁 {name}忽略,张三
  • 对象

    @Data
    public class FillData {
      private String name;
      private double number;
    }
    
  • 代码

    /**
     * 最简单的填充
     *
     * @since 2.1.1
     */
    @Test
    public void simpleFill() {
      // 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替
      String templateFileName =
        TestFileUtil.getPath() + "demo" + File.separator + "fill" + File.separator + "simple.xlsx";
    
      // 方案1 根据对象填充
      String fileName = TestFileUtil.getPath() + "simpleFill" + System.currentTimeMillis() + ".xlsx";
      // 这里 会填充到第一个sheet, 然后文件流会自动关闭
      FillData fillData = new FillData();
      fillData.setName("张三");
      fillData.setNumber(5.2);
      EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(fillData);
    
      // 方案2 根据Map填充
      fileName = TestFileUtil.getPath() + "simpleFill" + System.currentTimeMillis() + ".xlsx";
      // 这里 会填充到第一个sheet, 然后文件流会自动关闭
      Map<String, Object> map = new HashMap<String, Object>();
      map.put("name", "张三");
      map.put("number", 5.2);
      EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(map);
    }
    
2. 填充列表
  • 模板

    姓名 数字
  • 最终效果

    姓名 数字
    张三 5.6
    李四 7.9
    王五 8.8
    赵六 1.9
  • 代码

    /**
     * 填充列表
     *
     * @since 2.1.1
     */
    @Test
    public void listFill() {
      // 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替
      // 填充list 的时候还要注意 模板中{.} 多了个点 表示list
      String templateFileName =
        TestFileUtil.getPath() + "demo" + File.separator + "fill" + File.separator + "list.xlsx";
    
      // 方案1 一下子全部放到内存里面 并填充
      String fileName = TestFileUtil.getPath() + "listFill" + System.currentTimeMillis() + ".xlsx";
      // 这里 会填充到第一个sheet, 然后文件流会自动关闭
      EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(data());
    
      // 方案2 分多次 填充 会使用文件缓存(省内存)
      fileName = TestFileUtil.getPath() + "listFill" + System.currentTimeMillis() + ".xlsx";
      ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build();
      WriteSheet writeSheet = EasyExcel.writerSheet().build();
      excelWriter.fill(data(), writeSheet);
      excelWriter.fill(data(), writeSheet);
      // 千万别忘记关闭流
      excelWriter.finish();
    }
    
3. 复杂填充

复杂填充、大数据量填充、横向填充参考官方文档

六、常见API

参考官方文档

posted @ 2020-04-26 10:45  litooong  阅读(1152)  评论(0)    收藏  举报