首先,说到EasyExcel,有做过报表的导入以及导出的小伙伴一定不陌生。
比如,目前就职的公司所涉及的CRM类的诸多项目,就用到了此类功能。
利用Java来对Excel进行导入以及导出,现在常见的框架主要有两大类。
比如我就是先了解的Excel导出工具,就是POI,但是被POI的一些功能缺点所苦恼,众所周知,POI是一款基于内存的读写模式,在一些的大型项目,高并发场景下就显得吃力。
了解过POI的小伙伴都知道,POI操作Excel分为了两种模式(SAAX、Dom)
且SAX解析Excel较为复杂,且POI针对不同版本的Excel,读取和存储的方式也是不相同。
且代码两十分复杂,虽有一些规律可循,但是长期不巩固,很容易就会生疏
这张图,是EasyExcel官方的文档上的一张图,这张图很好的诠释了他高性能的地方,内存消耗低。
一个3M的excel用POI sax解析依然需要100M左右内存,改用easyexcel可以降低到几M,并且再大的excel也不会出现内存溢出;03版依赖POI的sax模式,在上层做了模型转换的封装,让使用者更加简单方便
官网 :[EasyExcel官网](https://easyexcel.opensource.alibaba.com/)
<dependency>
<groupId>com.alibabagroupId>
<artifactId>easyexcelartifactId>
<version>3.1.1version>
dependency>
create table `t_info` (
`nick_name` varchar (765),
`net_number` varchar (765),
`sign_time` date ,
`belong_org` varchar (765),
`order_num` bigint (20),
`income` bigint (20),
`in_month` int (11)
);
@Data
public class Information {
private String netNumber;
private String nickName;
private Date signTime;
private String belongOrg;
private Long orderNum;
private Long income;
private int month;
首先,我们需要认识一下Excel,在Excel里,每一个Excel都可以称作一个WorkBook,在WorkBook里面又可以分为多个sheet。
读取Excel里的文件,其实只需要用到一个方法EasyExcel.read()
即可
【read方法的源码】
/**
* @param pathName
* 读取文件的路径地址
* @param head
* 解析成Java实体类的类
* @param readListener
* 读的监听器
* @return Excel reader builder.
*/
public static ExcelReaderBuilder read(String pathName, Class head, ReadListener readListener) {
ExcelReaderBuilder excelReaderBuilder = new ExcelReaderBuilder();
excelReaderBuilder.file(pathName);
if (head != null) {
excelReaderBuilder.head(head);
}
if (readListener != null) {
excelReaderBuilder.registerReadListener(readListener);
}
return excelReaderBuilder;
}
【读的监听器】
public class EasyExcelListener extends AnalysisEventListener<Information> {
private static final Logger log = LoggerFactory.getLogger(EasyExcelListener.class);
/**
* invoke方法尾调用read读取文件的时候所执行
* T 为指定读取实体类型
*/
@Override
public void invoke(Information info, AnalysisContext context) {
// TODO Auto-generated method stub
log.info("开始读取数据============>>>");
log.info("information = " + info);
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {}
}
【测试案例】
@Test
public void testRead() {
ExcelReaderBuilder workBook = EasyExcel.read("测试EasyExcel简单的读取.xlsx",Information.class,new EasyExcelListener());
ExcelReaderSheetBuilder sheet = workBook.sheet();
sheet.doRead();
}
EasyExcel也提供了同样简单的写出操作的方法EasyExcel.Write()
【Write源码】
public static ExcelWriterBuilder write(String pathName, Class head) {
ExcelWriterBuilder excelWriterBuilder = new ExcelWriterBuilder();
excelWriterBuilder.file(pathName);
if (head != null) {
excelWriterBuilder.head(head);
}
return excelWriterBuilder;
}
【测试案例】
@Test
public void testEasyWrite() {
String fileName = "测试EasyExcel简单的写入01.xlsx";
QueryWrapper<Information> wrapper = new QueryWrapper<>();
// wrapper.like("belong_org", "小");
Page<Information> page = new Page<>(1, 10);
IPage<Information> iPage = inService.page(page, wrapper);
List<Information> list = iPage.getRecords();
EasyExcel.write(fileName, Information.class).sheet("某直播平台3、4月份数据信息").doWrite(list);
}
这里可以看到,这里的列宽,和表头都有点不太合适。为了可以更加可视化,方便阅读的展示数据,easyExcel提供了更多的注解
用于将实体类的名称与Excel导出的列名称对应,用于在属性字段上
【ExcelProperty源码】
public @interface ExcelProperty {
/**
* sheet表头的名称
*
* 写操作:可以自动地将表头合并,也可以单独显示
*
* 读操作: 当有多个表头,可以拿到最后面的
*/
String[] value() default {""};
/**
* 列的索引
*
* 读或写的时候,可以改便列的顺序,如果index = -1 则会按照order排序
*
* 优先级 index >= sort >= default sort
*/
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;
/**
* Force the current field to use this converter.
*
* @return Converter
*/
Class<? extends Converter<?>> converter() default AutoConverter.class;
/**
*
* default @see com.alibaba.excel.util.TypeUtil if default is not meet you can set format
*
* @return Format string
* @deprecated please use {@link com.alibaba.excel.annotation.format.DateTimeFormat}
*/
@Deprecated
String format() default "";
}
属性 | 功能 | 备注 |
---|---|---|
value | sheet的标头名称 | 是一个字符数组,如果写多个,相同的为主标头,其余为附表头 |
index | sheet列的索引(从0开始) | 控制列的顺序,通常不与value同用。如果不连续,则会出现空列 |
convert | 转化器 | 通常用于性别的值映射(1–男;0–女) |
【测试案例】
@TableName("t_info")
public class Information {
@ExcelProperty({"网站号","网站号"})
private String netNumber;
@ExcelProperty({"大神昵称","大神昵称"})
private String nickName;
@ExcelProperty({"进站时间","进站时间"})
private Date signTime;
@ExcelProperty({"所属大神团","所属大神团"})
private String belongOrg;
@ExcelProperty({"营收信息","订单数量"})
private Long orderNum;
@ExcelProperty({"营收信息","收入"})
private Long income;
@ExcelProperty({"月份","月份"})
@TableField("in_month")
private int month;
}
说明:如果是在里面写一个数组的形式,则会把相同的表头合并表头
设置列宽度,只有一个参数value,取值范围为0-255
。因为Excel一个单元格的取值范围就为0-255个字符。
【测试案例】
public class Information {
@ExcelProperty({"网站号","网站号"})
@ColumnWidth(20)
private String netNumber;
@ExcelProperty({"大神昵称","大神昵称"})
@ColumnWidth(20)
private String nickName;
@ExcelProperty({"进站时间","进站时间"})
@ColumnWidth(20)
private Date signTime;
@ColumnWidth(20)
@ExcelProperty({"所属大神团","所属大神团"})
private String belongOrg;
@ExcelProperty({"营收信息","订单数量"})
@ColumnWidth(20)
private Long orderNum;
@ExcelProperty({"营收信息","收入"})
@ColumnWidth(20)
private Long income;
@ExcelProperty({"月份","月份"})
@TableField("in_month")
private int month;
}
ContentFontStyle:用于设置单元格内容字体格式的注解。
HeadFontStyle:用于设置表头单元格字体格式的注解
属性 | 功能 | 备注 |
---|---|---|
fontName | 字体名称 | |
fontHeightInPoints | 字体高度 | |
italic | 是否斜体 | |
color | 颜色 | |
typeOffset | 偏移量 | |
blod | 是否加粗 | |
charset | 编码格式 | |
underline | 下划线 |
@ContentFontStyle 用于设置单元格内容字体格式的注解
【测试案例】
@TableName("t_info")
@ContentFontStyle(color = 14,fontHeightInPoints = 18,fontName = "宋体")
@HeadFontStyle(fontName = "华文中宋",color = 10,fontHeightInPoints = 24)
public class Information {
@ExcelProperty({"网站号","网站号"})
@ColumnWidth(20)
private String netNumber;
@ExcelProperty({"大神昵称","大神昵称"})
@ColumnWidth(20)
private String nickName;
@ExcelProperty({"进站时间","进站时间"})
@ColumnWidth(20)
private Date signTime;
@ColumnWidth(20)
@ExcelProperty({"所属大神团","所属大神团"})
private String belongOrg;
@ExcelProperty({"营收信息","订单数量"})
@ColumnWidth(20)
private Long orderNum;
@ExcelProperty({"营收信息","收入"})
@ColumnWidth(20)
private Long income;
@ExcelProperty({"月份","月份"})
@TableField("in_month")
private int month;
}
用于设置单元格合并
属性 | 功能 | 备注 |
---|---|---|
eachRow | ||
columnExtend |
全局设置行高,以及设置表头的行高。 通常用于类上
属性 | 功能 | 备注 |
---|---|---|
value | 行高 | -1表示自动行高 |
ContentStyle:设置内容的样式,通常用于字段值上,表示设置某一列单元格的样式。
HeadStyle:设置表的表头的样式,通常用于类上。
属性 | 功能 | 备注 |
---|---|---|
dateFormat | 日期格式 | |
hidden | 设置单元格使用此样式隐藏 | |
locked | 设置单元格使用此样式锁定 | |
quotePrefix | 在单元格前面增加`符号,数字或者公式将以文符串的形式展示 | |
horizontalAlignment | 是否水平居中 | |
wrapped | 是否换行 | 为true是,表示多行上显示内容 |
fillFocegroundColor | 设置单元格填充的前景色 | 值为short类型的值 |
这里需要注意一下,Excel里的所有颜色,对应到代码里,都是由ICellStyle这个接口里的FillFoceGroundColor属性所设置。如果需要颜色的取值,可以参考:https://www.cnblogs.com/byxxw/p/5265127.html,这边博客。
用于设置时间类型的格式化操作
常用的参数就一个value,指定格式化时间的格式
@ExcelProperty({"进站时间","进站时间"})
@DateTimeFormat("yyyy-MM-dd")
@ColumnWidth(20)
private Date signTime;
用于设置数值类型的数据单元格格式化
同上,也是一个value参数
@ExcelProperty({"营收信息","收入"})
@ColumnWidth(20)
@NumberFormat("#.0")
private Long income;
说明:数字、时间格式和Excel里面设置单元格格式一样,#表示任意数字,没有不会填充;0表示一个数字,没有补0。
首先由于我们使用的是SpringBoot的方式完成,所以,我们需要导入相关的文件上传下载依赖文件fileupload
<dependency>
<groupId>commons-fileuploadgroupId>
<artifactId>commons-fileuploadartifactId>
<version>1.3.3version>
dependency>
其次,需要使用springmvc的相关控制层,完成文件上传的测试,springmvc配置:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan
base-package="com.wei.controller" />
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8" />
bean>
beans>
web.xml
DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<context-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:spring-mvc.xmlparam-value>
context-param>
<filter>
<filter-name>characterEncodingFilterfilter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilterfilter-class>
<init-param>
<param-name>encodingparam-name>
<param-value>UTF-8param-value>
init-param>
<init-param>
<param-name>forceRequestEncodingparam-name>
<param-value>trueparam-value>
init-param>
<init-param>
<param-name>forceResponseEncodingparam-name>
<param-value>trueparam-value>
init-param>
filter>
<filter-mapping>
<filter-name>characterEncodingFilterfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
listener>
<servlet>
<servlet-name>springmvcservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:spring-mvc.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>springmvcservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
web-app>
Service层创建ReadExcel()方法完成文件上传时候数据读取操作
private static final Logger log = LoggerFactory.getLogger(ImformationServiceImpl.class);
@Override
public void readExcelFile(List<Information> infos) {
// TODO Auto-generated method stub
log.info("Information = " + infos);
}
controller层完成读取调用
@RestController
@RequestMapping("/file")
public class InfomationController {
@Autowired
private InformationListener listener;
@RequestMapping("/fileUpload")
public String testFileUpload(MultipartFile file, HttpSession session) {
try {
// 获取到工作普
ExcelReaderBuilder workbook = EasyExcel.read(file.getInputStream(),
Information.class,
listener);
// 工作表
ExcelReaderSheetBuilder sheet = workbook.sheet();
// 执行监听器里的invoke方法读取数据
sheet.doRead();
return "success";
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return "fail";
}
}
配置监听器,完成数据的读取
@Component
@Scope("prototype")
public class InformationListener extends AnalysisEventListener<Information>{
@Autowired
private InformationService inService;
private ArrayList<Information> list = new ArrayList<>();
@Override
public void invoke(Information data, AnalysisContext context) {
list.add(data);
if (list.size() % 5 == 0) {
inService.readExcelFile(list);
list.clear();
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {}
}
@RequestMapping("/fileDownload")
public void download(HttpServletResponse response) throws Exception{
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("UTF-8");
String filename = URLEncoder.encode("测试文件下载","UTF-8");
response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''"+filename+".xlsx");
ServletOutputStream outputStream = response.getOutputStream();
ExcelWriterBuilder workbook = EasyExcel.write(outputStream, Information.class);
ExcelWriterSheetBuilder sheet = workbook.sheet("测试写入数据");
List<Information> infos = createData();
sheet.doWrite(infos);
}
private List<Information> createData() {
ArrayList<Information> list = new ArrayList<>();
Information info = null;
for (int i = 0; i <= 10; i++) {
info = new Information();
info.setBelongOrg("测试大神团"+i);
Random random = new Random();
int s = random.nextInt(1000000)%(1000000-10000+1) + 10000;
info.setIncome(Integer.toUnsignedLong(s));
info.setMonth(3);
info.setNetNumber("测试哈哈哈哈");
info.setNetNumber("46565465");
info.setOrderNum(45763L);
info.setSignTime(new Date());
list.add(info);
}
return list;
}