admin管理员组文章数量:1609530
前段时间项目上需要导入一个3000多条数据的Excel耗时十几分钟,使用的是传统的Apache poi的,最后统一决定用阿里的开源easy excel。
阿里在18年3月份左右发布easyexcel,刚发布就是打着“低内存”解决POI的oom的口号,本人在测试过程中发现相比poi,确实在使用的体验、操作简便性上有不少提升,感谢阿里团队的努力,官方给出的项目demp的github地址:https://github/alibaba/easyexcel
easyexcel核心功能
- 读任意大小的03、07版Excel不会OOM
- 读Excel自动通过注解,把结果映射为java模型
- 读Excel支持多sheet
- 读Excel时候是否对Excel内容做trim()增加容错
- 写小量数据的03版Excel(不要超过2000行)
- 写任意大07版Excel不会OOM
- 写Excel通过注解将表头自动写入Excel
- 写Excel可以自定义Excel样式 如:字体,加粗,表头颜色,数据内容颜色
- 写Excel到多个不同sheet
- 写Excel时一个sheet可以写多个Table
- 写Excel时候自定义是否需要写表头
SpringBoot集成
1、pom依赖引入
<!--阿里easyexcel-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>1.1.2-beta5</version>
</dependency>
2、ExcelUtil工具类
package com.example.mybaties.utils;
import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.metadata.BaseRowModel;
import com.alibaba.excel.metadata.Sheet;
import com.alibaba.excel.util.CollectionUtils;
import com.alibaba.excel.util.StringUtils;
import com.example.mybaties.entity.UserExcelProperty;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.io.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @DESCRIPTION 阿里Excel工具类
* @Author lst
* @Date 2020-04-28 11:30
*/
@Slf4j
public class ExcelUtil {
private static Sheet initSheet;
/**
* 默认初始化
*/
static {
//sheetNo: sheet页码,默认为1
//headLineMun: 从第几行开始读取数据,默认为0, 表示从第一行开始读取
initSheet = new Sheet(1, 0);
initSheet.setSheetName("sheet");
//设置自适应宽度
initSheet.setAutoWidth(Boolean.TRUE);
}
/**
* @Description: 导入方法,读取少于1000行数据
* @author: lst
* @date: 2020-4-28 11:26
* @param filePath 文件绝对路径
* @return List<Object>
*/
public static List<Object> readLessThan1000Row(String filePath){
return readLessThan1000RowBySheet(filePath,null);
}
/**
* @Description: 导入方法,读小于1000行数据, 带样式
* @author: lst
* @date: 2020-4-28 11:27
* @param filePath 文件绝对路径
* @param sheet sheet对象
* sheetNo: sheet页码,默认为1
* headLineMun: 从第几行开始读取数据,默认为0, 表示从第一行开始读取
* clazz: 返回数据List<Object> 中Object的类名
* @return List<Object>
*/
public static List<Object> readLessThan1000RowBySheet(String filePath, Sheet sheet){
if(!StringUtils.hasText(filePath)){
return null;
}
//判断是否使用默认的sheet
sheet = sheet != null ? sheet : initSheet;
InputStream fileStream = null;
try {
fileStream = new FileInputStream(filePath);
return EasyExcelFactory.read(fileStream, sheet);
} catch (FileNotFoundException e) {
log.info("找不到文件或文件路径错误, 文件:{}", filePath);
}finally {
try {
if(fileStream != null){
fileStream.close();
}
} catch (IOException e) {
log.info("excel文件读取失败, 失败原因:{}", e);
}
}
return null;
}
/**
* @Description 导入方法,读大于1000行数据
* @author lst
* @date 2020-4-28 11:32
* @param filePath 文件绝对路径
* @return List<Object>
*/
public static List<Object> readMoreThan1000Row(String filePath){
return readMoreThan1000RowBySheet(filePath,null);
}
/**
* @Description 导入方法,读大于1000行数据, 带样式
* @author lst
* @date 2020-4-28 11:33
* @param filePath 文件绝对路径
* @param sheet sheet对象
* sheetNo: sheet页码,默认为1
* headLineMun: 从第几行开始读取数据,默认为0, 表示从第一行开始读取
* clazz: 返回数据List<Object> 中Object的类名
* @return List<Object>
*/
public static List<Object> readMoreThan1000RowBySheet(String filePath, Sheet sheet){
if(!StringUtils.hasText(filePath)){
return null;
}
//判断是否使用默认的sheet
sheet = sheet != null ? sheet : initSheet;
InputStream fileStream = null;
try {
fileStream = new FileInputStream(filePath);
ExcelListenerPlus excelListener = new ExcelListenerPlus();
EasyExcelFactory.readBySax(fileStream, sheet, excelListener);
return excelListener.getDatas();
} catch (FileNotFoundException e) {
log.error("找不到文件或文件路径错误, 文件:{}", filePath);
}finally {
try {
if(fileStream != null){
fileStream.close();
}
} catch (IOException e) {
log.error("excel文件读取失败, 失败原因:{}", e);
}
}
return null;
}
/**
* @Description 导出方法,生成excle
* @author lst
* @date 2020-4-28 11:37
* @param filePath 绝对路径, 如:/home/chenmingjian/Downloads/aaa.xlsx
* @param data 数据源
* @param head 表头
*/
public static void writeBySimple(String filePath, List<List<Object>> data, List<String> head){
writeSimpleBySheet(filePath,data,head,null);
}
/**
* @Description 导出方法,生成excle
* @author lst
* @date 2020-4-28 11:37
* @param filePath 绝对路径, 如:/home/chenmingjian/Downloads/aaa.xlsx
* @param data 数据源
* @param sheet excle页面样式
* @param head 表头
*/
public static void writeSimpleBySheet(String filePath, List<List<Object>> data, List<String> head, Sheet sheet){
//判断是否使用默认的sheet
sheet = (sheet != null) ? sheet : initSheet;
if(head != null){
List<List<String>> list = new ArrayList<>();
head.forEach(h -> list.add(Collections.singletonList(h)));
sheet.setHead(list);
}
OutputStream outputStream = null;
ExcelWriter writer = null;
try {
outputStream = new FileOutputStream(filePath);
writer = EasyExcelFactory.getWriter(outputStream);
writer.write1(data,sheet);
} catch (FileNotFoundException e) {
log.error("找不到文件或文件路径错误, 文件:{}", filePath);
}finally {
try {
if(writer != null){
writer.finish();
}
if(outputStream != null){
outputStream.close();
}
} catch (IOException e) {
log.error("excel文件导出失败, 失败原因:{}", e);
}
}
}
/**
* @Description 导出方法,生成excle
* @author lst
* @date 2020-4-28 11:39
* @param filePath 绝对路径, 如:/home/chenmingjian/Downloads/aaa.xlsx
* @param data 数据源
*/
public static void writeWithTemplate(String filePath, List<? extends BaseRowModel> data){
writeWithTemplateAndSheet(filePath,data,null);
}
/**
* @Description 导出方法,生成excle
* @author lst
* @date 2020-4-28 11:40
* @param filePath 绝对路径, 如:/home/chenmingjian/Downloads/aaa.xlsx
* @param data 数据源
* @param sheet excle页面样式
*/
public static void writeWithTemplateAndSheet(String filePath, List<? extends BaseRowModel> data, Sheet sheet){
if(CollectionUtils.isEmpty(data)){
return;
}
//判断是否使用默认的sheet
sheet = (sheet != null) ? sheet : initSheet;
sheet.setClazz(data.get(0).getClass());
OutputStream outputStream = null;
ExcelWriter writer = null;
try {
outputStream = new FileOutputStream(filePath);
writer = EasyExcelFactory.getWriter(outputStream);
writer.write(data,sheet);
} catch (FileNotFoundException e) {
log.error("找不到文件或文件路径错误, 文件:{}", filePath);
}finally {
try {
if(writer != null){
writer.finish();
}
if(outputStream != null){
outputStream.close();
}
} catch (IOException e) {
log.error("excel文件导出失败, 失败原因:{}", e);
}
}
}
/**
* @Description 导出方法,生成多Sheet的excle
* @author lst
* @date 2020-4-28 11:41
* @param filePath 绝对路径, 如:/home/chenmingjian/Downloads/aaa.xlsx
* @param multipleSheelPropetys
* @return
*/
public static void writeWithMultipleSheel(String filePath,List<MultipleSheelPropety> multipleSheelPropetys){
if(CollectionUtils.isEmpty(multipleSheelPropetys)){
return;
}
OutputStream outputStream = null;
ExcelWriter writer = null;
try {
outputStream = new FileOutputStream(filePath);
writer = EasyExcelFactory.getWriter(outputStream);
for (MultipleSheelPropety multipleSheelPropety : multipleSheelPropetys) {
Sheet sheet = multipleSheelPropety.getSheet() != null ? multipleSheelPropety.getSheet() : initSheet;
if(!CollectionUtils.isEmpty(multipleSheelPropety.getData())){
sheet.setClazz(multipleSheelPropety.getData().get(0).getClass());
}
writer.write(multipleSheelPropety.getData(), sheet);
}
} catch (FileNotFoundException e) {
log.error("找不到文件或文件路径错误, 文件:{}", filePath);
}finally {
try {
if(writer != null){
writer.finish();
}
if(outputStream != null){
outputStream.close();
}
} catch (IOException e) {
log.error("excel文件导出失败, 失败原因:{}", e);
}
}
}
/*********************匿名内部类开始,可以提取出去******************************/
@Data
public static class MultipleSheelPropety{
private List<? extends BaseRowModel> data;
private Sheet sheet;
}
/**
* 解析监听器,读取大于一千行的数据需要传入次参数
* 每解析一行会回调invoke()方法。
* 整个excel解析结束会执行doAfterAllAnalysed()方法
* @author lst
* @date 2020-4-28 11:42
*/
@Data
public static class ExcelListenerPlus extends AnalysisEventListener {
private List<Object> datas = new ArrayList<>();
/**
* @Description 逐行解析
* @author lst
* @date 2020-4-28 11:43
* @param object 当前行的数据
* @param context
*/
@Override
public void invoke(Object object, AnalysisContext context) {
//当前行
// context.getCurrentRowNum()
if (object != null) {
datas.add(object);
}
}
/**
* @Description 解析完所有数据后会调用该方法
* @author lst
* @date 2020-4-28 11:43
* @param context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
//解析结束销毁不用的资源
}
}
public static class ExcelListener extends AnalysisEventListener<UserExcelProperty> {
private List<UserExcelProperty> datas = new ArrayList<>();
private static final int BATCH_COUNT = 1;
@Override
public void invoke(UserExcelProperty userExcelProperty, AnalysisContext analysisContext) {
//数据存储到datas,供批量处理,或后续自己业务逻辑处理。
datas.add(userExcelProperty);
//达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if(datas.size() >= BATCH_COUNT){
data();
// 存储完成清理datas
datas.clear();
}
}
private void data() {
for(UserExcelProperty userExcelProperty : datas){
log.info("姓名:{},性别:{},年龄:{}",userExcelProperty.getName(),userExcelProperty.getSex(),userExcelProperty.getAge());
}
}
/**
* 所有数据解析完成了 都会来调用
*/
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
}
}
}
3、UserExcelProperty实体类
package com.example.mybaties.entity;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.metadata.BaseRowModel;
import lombok.Data;
/**
* @DESCRIPTION UserExcelProperty实体类
* @Author lst
* @Date 2020-04-28 10:30
*/
@Data
public class UserExcelProperty extends BaseRowModel {
@ExcelProperty(value = "姓名",index = 0)
private String name;
@ExcelProperty(value = "性别",index = 1)
private String sex;
@ExcelProperty(value = "年龄",index = 2)
private Integer age;
}
4、UserController控制层(测试导入和导出的接口)
package com.example.mybaties.controller;
import com.example.mybaties.service.UserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;
/**
* @author LST
* @version 1.0
* @Description: 用户管理
* @date 2019-12-28 16:22
*/
@RestController
@RequestMapping("/user")
@Api(value = "UserController", tags = "用户接口")
public class UserController {
@Autowired
private UserService userService;
/**
* @Description: excel导出
* @author: lst
* @date: 2020-4-28 11:00
*/
@GetMapping(value = "/user-export", produces = "application/json; charset=utf-8")
public void userExport() {
userService.userExport();
}
/**
* @Description 将用户信息导出下载
* @author lst
* @date 2020-4-28 14:54
* @param response
*/
@GetMapping(value = "/download-export", produces = "application/json; charset=utf-8")
public void downLoadExcel(HttpServletResponse response){
userService.downLoadExcel(response);
}
/**
* @Description Excel文件用户信息导入
* @author lst
* @date 2020-4-28 15:18
* @param file 文件流
*/
@PostMapping(value = "/user-import", produces = "application/json; charset=utf-8")
@ApiOperation(value = "Excel文件用户信息导入", notes = "Excel文件用户信息导入", produces = "application/json")
public void userImport(@RequestParam(value = "file") MultipartFile file) {
userService.userImport(file);
}
}
5、UserService接口和UserServiceImpl具体实现代码
package com.example.mybaties.service;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;
/**
* @author lst
* @version 1.0
* @Description: 用户表
* @date 2019年04月09日 18:23
*/
public interface UserService {
void userExport();
void downLoadExcel(HttpServletResponse response);
void userImport(MultipartFile file);
}
package com.example.mybaties.service.impl;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.metadata.Sheet;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.example.mybaties.entity.UserExcelProperty;
import com.example.mybaties.service.UserService;
import com.example.mybaties.utils.ExcelUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author lst
* @version 1.0
* @Description: 用户表实现层
* @date 2019年04月09日 18:23
*/
@Service
@Slf4j
public class UserServiceImpl implements UserService {
/**
* @Description: excel导出
* @author: lst
* @date: 2020-4-28 11:00
*/
@Override
public void userExport() {
Long startTime = System.currentTimeMillis();
//sheetNo: sheet页码,默认为1
//headLineMun: 从第几行开始读取数据,默认为0, 表示从第一行开始读取
Sheet sheet = new Sheet(1,0, UserExcelProperty.class,"用户信息",null);
sheet.setAutoWidth(Boolean.TRUE);
//设置列宽 设置每列的宽度
/*Map columnWidth = new HashMap();
columnWidth.put(0,10000);
columnWidth.put(1,40000);columnWidth.put(2,10000);columnWidth.put(3,10000);
sheet1.setColumnWidthMap(columnWidth);
sheet1.setHead(createTestListStringHead2());*/
List<UserExcelProperty> data = new ArrayList<>();
for(int i = 0; i < 2000; i++){
UserExcelProperty userExcelProperty = new UserExcelProperty();
userExcelProperty.setAge(i);
userExcelProperty.setName("测试"+i);
userExcelProperty.setSex("1");
data.add(userExcelProperty);
}
ExcelUtil.writeWithTemplateAndSheet("D:\\test.xls",data,sheet);
Long endTime = System.currentTimeMillis();
log.info("消耗时间:{}ms",endTime-startTime);
}
/**
* @Description 将用户信息导出下载
* @author lst
* @date 2020-4-28 14:54
* @param response
*/
@Override
public void downLoadExcel(HttpServletResponse response) {
Long startTime = System.currentTimeMillis();
ExcelWriter writer = null;
// 文件输出位置
OutputStream out = null;
try {
out = response.getOutputStream();
writer = new ExcelWriter(response.getOutputStream(), ExcelTypeEnum.XLSX, true);
Sheet sheet = new Sheet(1,0, UserExcelProperty.class,"用户信息",null);
sheet.setAutoWidth(Boolean.TRUE);
//得到要填充的数据
List<UserExcelProperty> data = new ArrayList();
for(int i = 0; i < 2000; i++){
UserExcelProperty userExcelProperty = new UserExcelProperty();
userExcelProperty.setAge(i);
userExcelProperty.setName("测试"+i);
userExcelProperty.setSex("1");
data.add(userExcelProperty);
}
writer.write(data, sheet);
response.setCharacterEncoding("utf-8");
response.setContentType("application/vnd.ms-excel;charset=utf-8");
response.setHeader("Content-Disposition", "attachment;filename=" + new String(("用户信息.xls").getBytes(), "ISO8859-1"));
out.flush();
} catch (IOException e) {
log.info("错误信息:{}",e.getMessage());
}finally {
writer.finish();
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
Long endTime = System.currentTimeMillis();
log.info("消耗时间:{}ms",endTime-startTime);
}
/**
* @Description Excel文件用户信息导入
* @author lst
* @date 2020-4-28 15:18
* @param file 文件流
*/
@Override
public void userImport(MultipartFile file) {
Long startTime = System.currentTimeMillis();
if(!file.isEmpty()){
try {
InputStream inputStream = new BufferedInputStream(file.getInputStream());
//实例化实现了AnalysisEventListener接口的类
ExcelUtil.ExcelListener excelListener = new ExcelUtil.ExcelListener();
ExcelReader reader = new ExcelReader(inputStream,null,excelListener);
//读取信息
reader.read(new Sheet(1,1,UserExcelProperty.class));
} catch (IOException e) {
log.info("错误信息:{}",e.getMessage());
}
}
Long endTime = System.currentTimeMillis();
log.info("消耗时间:{}ms",endTime-startTime);
}
}
6、生成Excel数据接口测试(http://127.0.0.1:8001/user/user-export)
使用Swagger测试如下图:
查看生成的Excel文件信息如下
7、导出Excel数据下载接口测试(http://127.0.0.1:8001/user/download-export)
使用Swagger测试如下图:
Excel文件数据就是和上个接口一样。
8、导入Excel数据接口测试(http://127.0.0.1:8001/user/user-import)
此接口我只做了打印,如需要插入数据可以在ExcelListener类中去实现
使用Swagger测试如下图:
后台打印了读取Excel的数据,如图
动态表头的导入和导出以项目实际需求进行开发。
版权声明:本文标题:阿里EasyExcel开源超简单入门操作Excel 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://www.elefans.com/xitong/1728571853a1164285.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论