官方網站: 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();