VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > Java教程 >
  • 阿里开源项目EasyExcel

官方网站: https://yuque.com/easyexcel
gitHub地址 https://github.com/alibaba/easyexcel

一、引入easyexcel包#

Gradle方式引入

implementation("com.alibaba:easyexcel:2.2.7")

实体模型

@Data
public class User {
    private Integer userId;
    private String name;
    private String phone;
    private String email;
    private Long createTime;
}

二、生成Excel#

1. 简单生成Excel文件到本地磁盘#

// 1. 获取用户
List<User> users = userDao.findAll();

// 2. 写文件
String fileName = "F://excel/" + System.currentTimeMillis() + ".xlsx";
EasyExcel.write(fileName, User.class).sheet("用户表").doWrite(users);

在写文件的时候必须要保证目录已经存在,否则会报错。

2. 修改列名称#

默认情况下,使用类的属性名作为Excel的列表,当然也可以使用@ExcelProperty 注解来重新指定属性名称。

@Data
public class User {
    @ExcelProperty(value = "用户Id")
    private Integer userId;
    @ExcelProperty(value = "姓名")
    private String name;
    @ExcelProperty(value = "手机")
    private String phone;
    @ExcelProperty(value = "邮箱")
    private String email;
    @ExcelProperty(value = "创建时间")
    private Long createTime;
}

这里我们为每一个属性指定了一个名称,重新执行前面的代码,看看效果如何。

3.合并表头#

我们查看@ExcelProperty源码value的注释,在写的时候,如果指定了多个值,会自动进行合并

 /**
 * The name of the sheet header.
 *
 * <p>
 * write: It automatically merges when you have more than one head
 * <p>
 * read: When you have multiple heads, take the first one
 *
 * @return The name of the sheet header
 */
String[] value() default {""};

我们修改一下user类

@Data
public class User {
    @ExcelProperty(value = "用户Id")
    private Integer userId;
    @ExcelProperty(value = {"用户基本信息", "姓名"})
    private String name;
    @ExcelProperty(value = {"用户基本信息", "手机"})
    private String phone;
    @ExcelProperty(value = {"用户基本信息", "邮箱"})
    private String email;
    @ExcelProperty(value = "创建时间")
    private Long createTime;
}

重新生成excel查看效果。

4. 指定列的位置和排序#

@ExcelProperty注解有两个属性index和order,不要使用错了。如果不指定则按照属性在类中的排列顺序来。

    /**
     * Index of column
     *
     * Read or write it on the index of column,If it's equal to -1, it's sorted by Java class.
     *
     * priority: index &gt; order &gt; default sort
     *
     * @return Index of column
     */
    int index() default -1;

    /**
     * Defines the sort order for an column.
     *
     * priority: index &gt; order &gt; default sort
     *
     * @return Order of column
     */
    int order() default Integer.MAX_VALUE;

通过注释我们知道index是指定改属性在Excel中列的下标,下标从0开始

@Data
public class User {
    @ExcelProperty(value = "用户Id", index = 10)
    private Integer userId;
    @ExcelProperty(value = "姓名", index = 11)
    private String name;
    @ExcelProperty(value = "手机")
    private String phone;
    @ExcelProperty(value = "邮箱")
    private String email;
    @ExcelProperty(value = "创建时间")
    private Long createTime;
}

从结果可以看到,用户Id和姓名这两列已经移动到设置的位置,其它列依次前移。

下面使用order来看看效果

@Data
public class User {
    @ExcelProperty(value = "用户Id")
    private Integer userId;
    @ExcelProperty(value = "姓名")
    private String name;
    @ExcelProperty(value = "手机", order = 11)
    private String phone;
    @ExcelProperty(value = "邮箱", order = 10)
    private String email;
    @ExcelProperty(value = "创建时间")
    private Long createTime;
}

直接贴图

在源码中我们知道order的默认值为Integer.MAX_VALUE,通过效果我们可以得出结论:order值越小,越排在前面

根据前面和注释总结得出结论:

  • 优先级:index > order > 默认配置
  • index相当于绝对位置,下标从0开始
  • order相当于相对位置,值越小的排在越前面

5. 自定义转换器#

在读写EXCEL时,有时候需要我们进行数据类型转换,例如我们这里的创建时间,在实体对象中是Long类型,但是这样直接导出到Excel中不太直观。我们需要转换成yyyy-MM-dd HH:mm:ss 格式,此时我们就可以用到转换器。
下面我们就来掩饰如何自定义转换器,只需要实现Converter接口,然后再@ExcelProperty中使用就可以了

public class DateTimeConverter implements Converter<Long> {
    private final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    @Override
    public Class supportJavaTypeKey() {
        return Long.class;
    }

    @Override
    public CellDataTypeEnum supportExcelTypeKey() {
        return CellDataTypeEnum.STRING;
    }

    @Override
    public Long convertToJavaData(CellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
        return;
    }

    @Override
    public CellData convertToExcelData(Long value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
        if (value == null) {
            return new CellData(CellDataTypeEnum.STRING, null);
        }
        LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(value), ZoneId.systemDefault());
        String dateStr = localDateTime.format(dateTimeFormatter);
        return new CellData(dateStr);
    }
}

在convertToExcelData方法中我们实现如何将Java对象转换成Excel对象,实现转换过程。

在实体类中引用

public class User {
    @ExcelProperty(value = "用户Id")
    private Integer userId;
    @ExcelProperty(value = "姓名")
    private String name;
    @ExcelProperty(value = "手机", order = 11)
    private String phone;
    @ExcelProperty(value = "邮箱", order = 10)
    private String email;
    @ExcelProperty(value = "创建时间", converter = DateTimeConverter.class)
    private Long createTime;
}

执行后贴图看效果

6. 大量数据如何写入Excel#

先看看另外一种写入Excel的方式

// 1. 获取用户
List<User> users = userDao.findAll();

// 2. 写文件
String fileName = "F://excel/" + System.currentTimeMillis() + ".xlsx";
ExcelWriter excelWriter = null;
try {
    excelWriter = EasyExcel.write(fileName, User.class).build();
    WriteSheet writeSheet = EasyExcel.writerSheet("用户表").build();
    excelWriter.write(users, writeSheet);
} finally {
    // 别忘了关闭流
    if (excelWriter != null) {
        excelWriter.finish();
    }
}

上面方式我们可以手工控制流的关闭,这样我们就可以实现多次写。

基于上面的方式,我们来实现分页获取数据,然后将数据写入Excel中,避免加载的数据过多,导致内存溢出。

Pageable pageable = PageRequest.of(0, 10);

String fileName = "F://excel/" + System.currentTimeMillis() + ".xlsx";
ExcelWriter excelWriter = null;
try {
    excelWriter = EasyExcel.write(fileName, User.class).build();
    WriteSheet writeSheet = EasyExcel.writerSheet("用户表").build();
    List<User> users;
    do {
        users = userDao.pageQuery(pageable);
        excelWriter.write(users, writeSheet);
        pageable = pageable.next();
    } while (!CollectionUtils.isEmpty(users));
} finally {
    if (excelWriter != null) {
        excelWriter.finish();
    }
}

在使用excelWriter.write方式时务必保证至少执行一次write,这样是为了将sheet和表头写入excel,否则打开excel时会报错。write的第一个参数可以为null。

7. 排除或包含指定字段#

1) 包含指定字段,这里字段的名称为类属性名称,而不是Excel中列头显示名称
// 1. 获取用户
List<User> users = userDao.findAll();

// 2. 写文件
String fileName = "F://excel/" + System.currentTimeMillis() + ".xlsx";
EasyExcel.write(fileName, User.class)
        .includeColumnFiledNames(Arrays.asList("userId", "name", "phone"))
        .sheet("用户表").doWrite(users);

2)排除指定字段

下面的效果等价于上面的效果

// 1. 获取用户
List<User> users = userDao.findAll();

// 2. 写文件
String fileName = "F://excel/" + System.currentTimeMillis() + ".xlsx";
EasyExcel.write(fileName, User.class)
        .excludeColumnFiledNames(Arrays.asList("createTime", "email"))
        .sheet("用户表").doWrite(users);
3)使用注解的方式

在需要排除的字段上添加@ExcelIgnore

@Data
public class User {
    @ExcelProperty(value = "用户Id")
    private Integer userId;
    @ExcelProperty(value = "姓名")
    private String name;
    @ExcelProperty(value = "手机")
    private String phone;
    @ExcelProperty(value = "邮箱")
    @ExcelIgnore
    private String email;
    @ExcelProperty(value = "创建时间", converter = DateTimeConverter.class)
    @ExcelIgnore
    private Long createTime;
}

8. 设置列宽和行高#

@Data
@ContentRowHeight(30)
@HeadRowHeight(40)
@ColumnWidth(20)
public class User {
    @ExcelProperty(value = "用户Id")
    private Integer userId;
    @ExcelProperty(value = "姓名")
    private String name;
    @ExcelProperty(value = "手机")
    private String phone;
    @ExcelProperty(value = "邮箱")
    private String email;
    @ColumnWidth(25)
    @ExcelProperty(value = "创建时间", converter = DateTimeConverter.class)
    private Long createTime;
}

9. 样式设置#

@Data
@ContentRowHeight(30)
@HeadRowHeight(40)
@ColumnWidth(15)
@HeadStyle(fillBackgroundColor = 14, fillPatternType = FillPatternType.SOLID_FOREGROUND)
@HeadFontStyle(fontName = "黑体", italic = true, color = 10)
public class User {
    @ExcelProperty(value = "用户Id")
    private Integer userId;
    @ExcelProperty(value = "姓名")
    private String name;
    @ExcelProperty(value = "手机")
    private String phone;
    @ExcelProperty(value = "邮箱")
    private String email;
    @ColumnWidth(25)
    @ExcelProperty(value = "创建时间", converter = DateTimeConverter.class)
    private Long createTime;
}

10. 在Web中生成Excel并下载#

1) 方式一:

@GetMapping("/download")
public void downloadExcel(HttpServletResponse response) throws IOException {
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");

List<User> users = getData();
// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系

String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
EasyExcel.write(response.getOutputStream(), User.class).sheet("模板").doWrite(users);

}

2)方式二:
@GetMapping("/download")
public void downloadExcel(HttpServletResponse response) throws IOException {
    response.setContentType("application/vnd.ms-excel");
    response.setCharacterEncoding("utf-8");

    // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系

    String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20");
    response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");

    Pageable pageable = PageRequest.of(0, 10);
    ExcelWriter excelWriter = null;
    try {
        excelWriter = EasyExcel.write(response.getOutputStream(), User.class).build();
        WriteSheet writeSheet = EasyExcel.writerSheet("用户表").build();
        List<User> users;
        do {
            users = pageData(pageable);
            excelWriter.write(users, writeSheet);
            pageable = pageable.next();
        } while (!CollectionUtils.isEmpty(users));
    } finally {
        if (excelWriter != null) {
            excelWriter.finish();
        }
    }
}

三、读取数据#

1. 分批读取并处理数据简单#

通常我们试用这种分批处理的方式,避免内存的消耗。

/**
 * 有个很重要的点 UserListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
 *
 * @author caoyongcheng
 */
public class UserListener extends AnalysisEventListener<User> {

    private static final Logger LOGGER = LoggerFactory.getLogger(UserListener.class);

    /**
     * 每隔10条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 10;
    List<User> list = new ArrayList<>();

    private final UserService userService;

    public UserListener(UserService userService) {
        this.userService = userService;
    }

    @Override
    public void invoke(User data, AnalysisContext context) {
        list.add(data);
        if (list.size() >= BATCH_COUNT) {
            saveData();
            // 存储完了之后,需要清空列表
            list.clear();
        }
    }

    /**
     * invoke方法执行完后都会调用该方法
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 这里也要保存数据,确保最后遗留的数据也存储到数据库
        saveData();
        LOGGER.debug("所有数据解析完成!");
    }

    /**
     * 保存数据
     */
    private void saveData() {
        LOGGER.debug("保存数据条数:{}", list.size());
        userService.save(list);
    }
}

调用方式

@PostMapping("/upload")
public void upload(@RequestParam("file") MultipartFile file) throws IOException {
    if (file.isEmpty()) {
        log.warn("文件为空");
        return;
    }
    EasyExcel.read(file.getInputStream(), User.class, new UserListener(userService)).doReadAll();
}

2. 同步读取#

将excel中的数据直接读取成list

@PostMapping("/upload")
public void upload(@RequestParam("file") MultipartFile file) throws IOException {
    if (file.isEmpty()) {
        log.warn("文件为空");
        return;
    }
    List<User> users = EasyExcel.read(file.getInputStream(),User.class,null).doReadAllSync();
}

read方法的第三个参数ReadListener直接设置为null,因为不需要处理

3. 指定head头的行数#

如果第二行才是标题,第三行是数据,可以通过headRowNumber(int headRowNumber)来指定

List<UserExcel> users = EasyExcel.read(file.getInputStream(), UserExcel.class, new UserListener())
  .sheet("成员")
  .headRowNumber(2)
  .doReadSync();

来源:https://www.cnblogs.com/cycheng/p/14131849.html


相关教程