-
阿里开源项目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 > order > default sort
*
* @return Index of column
*/
int index() default -1;
/**
* Defines the sort order for an column.
*
* priority: index > order > 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