EasyExcel
一、初识EasyExcel
1. Apache POI
先说POI
,有过报表导入导出经验的同学,应该听过或者使用。
Apache POI
是Apache软件基金会的开源函式库,提供跨平台的Java API
实现Microsoft Office
格式档案读写。但是存在如下一些问题:
1.1 学习使用成本较高
对POI有过深入了解的才知道原来POI还有SAX模式(Dom解析模式)。但SAX模式相对比较复杂,excel有03和07两种版本,两个版本数据存储方式截然不同,sax解析方式也各不一样。
想要了解清楚这两种解析方式,才去写代码测试,估计两天时间是需要的。再加上即使解析完,要转换到自己业务模型还要很多繁琐的代码。总体下来感觉至少需要三天,由于代码复杂,后续维护成本巨大。
POI的SAX模式的API可以一定程度的解决一些内存溢出的问题,但是POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大,一个3M的Excel用POI的SAX解析,依然需要100M左右内存。
1.2 POI的内存消耗较大
大部分使用POI都是使用他的userModel模式。userModel的好处是上手容易使用简单,随便拷贝个代码跑一下,剩下就是写业务转换了,虽然转换也要写上百行代码,相对比较好理解。然而userModel模式最大的问题是在于非常大的内存消耗,一个几兆的文件解析要用掉上百兆的内存。现在很多应用采用这种模式,之所以还正常在跑一定是并发不大,并发上来后一定会OOM或者频繁的full gc。
总体上来说,简单写法重度依赖内存,复杂写法学习成本高。
特点
-
功能强大
-
代码书写冗余繁杂
-
读写大文件耗费内存较大,容易OOM
2. EasyExcel
2.1 重写了POI对07版Excel的解析
-
EasyExcel重写了POI对07版Excel的解析,可以把内存消耗从100M左右降低到10M以内,并且再大的Excel不会出现内存溢出,03版仍依赖POI的SAX模式。
-
下图为64M内存1分钟内读取75M(46W行25列)的Excel(当然还有急速模式能更快,但是内存占用会在100M多一点)
- 在上层做了模型转换的封装,让使用者更加简单方便
特点
- 在数据模型层面进行了封装,使用简单
- 重写了07版本的Excel的解析代码,降低内存消耗,能有效避免OOM
- 只能操作Excel
- 不能读取图片
二、快速入门--QuickStart
0、导入依赖坐标
xml
<!-- EasyExcel -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.1.6</version>
</dependency>
<!-- lombok 优雅编程 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.20</version>
</dependency>
<!-- junit单元测试:可以让每个方法独立运行,不要一直改主方法 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
导入
easyexcel-2.1.6
坐标的时候,已依赖传递导入poi-3.17
的POI。
1、最简单的读
1.1、需求、准备工作
java
/**
* 需求:单实体导入(从磁盘将文件导入到应用程序)
* 导入Excel学员信息到系统。
* 包含如下列:姓名、性别、出生日期
* 模板详见:学员信息.xlsx
*/
学员信息.xlsx
学生姓名 | 学生出生日期 | 学生性别 |
---|---|---|
学号00 | 2020-03-16 | 男 |
学号01 | 2020-03-16 | 男 |
学号02 | 2020-03-16 | 男 |
学号03 | 2020-03-16 | 男 |
学号04 | 2020-03-16 | 男 |
学号05 | 2020-03-16 | 男 |
学号06 | 2020-03-16 | 男 |
学号07 | 2020-03-16 | 男 |
学号08 | 2020-03-16 | 男 |
学号09 | 2020-03-16 | 男 |
1.2、编写导出数据的实体类
java
package com.taotie.test;
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc
*/
// 基于lombok
@Data
@NoArgsConstructor
@AllArgsConstructor
@HeadRowHeight(20) // 指定列头行高
@ColumnWidth(20) // 指定列宽
public class Student {
/**
* 学生姓名
*/
@ExcelProperty(value = "学生姓名", index = 0)
private String name;
/**
* 学生性别
*/
@ExcelProperty(value = "学生性别", index = 2)
private String gender;
/**
* 学生出生日期
*/
@ExcelProperty(value = "学生出生日期", index = 1)
private Date birthday;
/**
* id
*/
// @ExcelProperty(value = "编号",index = 3)
@ExcelIgnore // 忽略,不读取
private String id;
}
注解: 文章后面有详解
1.3、 读取Excel
文件
调用EasyExcel
的API
读取的Excel
文件的测试类StudentReadDemo
java
package com.taotie.test;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.read.builder.ExcelReaderBuilder;
import com.alibaba.excel.read.builder.ExcelReaderSheetBuilder;
import org.junit.Test;
/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc
*/
public class TestEasyExcel {
/**
* 测试读取数据
*/
@Test
public void testRead() {
// 读取文件,读取完之后会自动关闭
/**
* 参数1:pathName 文件路径;"d:\\学员信息.xls"
* 参数2:head 每行数据对应的实体;Student.class
* 参数3:readListener 读监听器,每读一样就会调用一次该监听器的invoke方法
* 参数4:sheet方法参数: 工作表的顺序号(从0开始)或者工作表的名字,不传默认为0
*/
// // 封装工作簿对象
// ExcelReaderBuilder workBook = EasyExcel.read
// ("E:\\学员信息.xlsx",
// Student.class,
// new StudentReadListener( ));
// // 封装工作表
// ExcelReaderSheetBuilder sheet1 = workBook.sheet( );
// // 读取
// sheet1.doRead( );
// 最简单的写法
EasyExcel.read("E:\\学员信息.xlsx",Student.class,new StudentReadListener()).sheet().doRead();
}
}
读取Excel的监听器,用于处理读取产生的数据
java
package com.taotie.listener;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.taotie.domain.Student;
/**
* @Author Vsunks.v
* @Description:
*/
public class StudentReadListener extends AnalysisEventListener<Student> {
// 每读一行,会调用该invoke方法一次
@Override
public void invoke(Student data, AnalysisContext context) {
System.out.println("data = " + data);
log.info(data + "保存成功");
}
// 全部读完之后,会调用该方法
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// TODO......
}
}
2、最简单的写
2.1 需求、准备工作
java
/**
* 需求:单实体导出(从应用程序将文件导入到磁盘)
* 导出多个学生对象到Excel表格
* 包含如下列:姓名、性别、出生日期
* 模板详见:学员信息.xlsx
*/
2.2、编写导出数据的实体
java
// 还是上个实体类...
2.3、 准备数据并写入到文件
java
/**
* 测试写出数据
*/
@Test
public void simpleWrite() {
// 创造数据
ArrayList<Student> list = new ArrayList<>( );
list.add(new Student("张三","男",new Date(),"1001"));
list.add(new Student("李四","女",new Date(),"1002"));
list.add(new Student("王五","男",new Date(),"1003"));
list.add(new Student("赵六","女",new Date(),"1004"));
list.add(new Student("周期","男",new Date(),"1005"));
list.add(new Student("茅十八","女",new Date(),"1006"));
// 写出的文件路径,当前项目下
String fileName = "student.xlsx";
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
// 如果这里想使用03 则 传入excelType参数即可
EasyExcel.write(fileName, Student.class).sheet("学生信息").doWrite(list);
System.out.println("导出OK" );
}
3、web文件上传和下载[重点]
基于springboot的文件上传和下载
0. 导入依赖
xml
<!-- EasyExcel -->
<!-- lombok -->
<!-- junit -->
3.1 文件上传
需求: 批量插入(导入excel数据到项目中)
思路:
- 前端设计文件上传组件 ,点击开始文件上传
- 后端接收文件,使用工具解析数据
- 插入数据库
3.1.1 前端
在页面设计上传的组件
1.设置上传按钮,显示上传的对话框
2.设置对话框+上传组件
vue
<!-- 上传excel对话框 -->
<el-dialog :visible.sync="importExcelVisible" width="30%">
<el-upload
class="upload-demo"
drag
action="http://localhost:9999/api/sysUser/import"
name="file"
method="post"
accept=".xlsx,.xls"
:on-success="importSuccessFunction"
>
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
</el-upload>
</el-dialog>
3.1.2 后端-实体类注解
3.1.3 后端-监听器
java
package com.qf.util;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.qf.entity.SysUser;
import com.qf.service.SysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc
*/
@Component
@Scope("prototype") // 作者要求每次读取都要使用新的Listener
public class SysUserReadListener extends AnalysisEventListener<SysUser> {
// 有个很重要的点 DemoDataListener 不能被spring管理,
// 要每次读取excel都要new,然后里面用到spring可以构造方法传进去
private SysUserService sysUserService;
public SysUserReadListener(){} // 空参构造
// 有参构造
public SysUserReadListener(SysUserService sysUserService){
this.sysUserService = sysUserService;
}
// 每隔5条存储数据库,实际项目中使用时可以100条,然后清理list ,方便内存回收
private final int BATCH_COUNT = 5;
private ArrayList<SysUser> list = new ArrayList<>( );
// 每读一样,会调用该invoke方法一次
@Override
public void invoke(SysUser data, AnalysisContext context) {
list.add(data);
// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (list.size( ) >= BATCH_COUNT) {
saveData( );
// 存储完成清理 list
list.clear();
}
}
// 全部读完之后,会调用该方法
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 这里也要保存数据,确保最后遗留的数据也存储到数据库
saveData( );
System.out.println("所有数据解析完成!");
}
private void saveData() {
System.out.println("开始存储数据库!");
// 调用业务层存数据库
sysUserService.saveBatch(list);
System.out.println("存储数据库成功!");
}
}
3.1.4 后端-Controller接收
java
@PostMapping("/import")
public R importExcel(MultipartFile file) throws IOException {
// 别忘了给SysUserReadListener构造方法传入SysUserService对象
EasyExcel.read(file.getInputStream(),
SysUser.class,
new SysUserReadListener(sysUserService)).sheet().doRead();
return R.ok();
}
3.1.5 后端-Service
// 需要添加批量插入的方法
3.1.6 后端-Mapper
xml
<insert id="saveBatch">
insert into sys_user(user_name, nick_name, user_type, email, phonenumber, sex, avatar, password, status, del_flag, login_ip, login_date, create_by, create_time, update_by, update_time, remark)
values
<foreach collection="list" item="entity" separator=",">
(#{entity.userName}, #{entity.nickName}, #{entity.userType}, #{entity.email}, #{entity.phonenumber}, #{entity.sex}, #{entity.avatar}, #{entity.password}, #{entity.status}, #{entity.delFlag}, #{entity.loginIp}, #{entity.loginDate}, #{entity.createBy}, #{entity.createTime}, #{entity.updateBy}, #{entity.updateTime}, #{entity.remark})
</foreach>
</insert>
3.1.7 测试
本地创建一个excel表格,按照实体类中定义的列名填充数据
点击上传
3.2 文件导出
需求: 将项目中的数据导出到本地excel表格
思路:
- 前端设计按钮,点击开始导出
- 后端请求,查询出数据,
- 使用工具导出
实体类于之前一样
java
// 略
Controller层
java
/**
* 导出excel
*/
@GetMapping("/export")
public void exportExcel(HttpServletResponse response) throws IOException {
// 调用业务层,查询全部数据
List<SysUser> list = sysUserService.queryAll();
// 下面这个注释是前端使用方式1,即a标签发请求时采用
// response.setContentType("application/vnd.ms-excel");
// response.setCharacterEncoding("utf-8");
// // 这里URLEncoder.encode可以防止中文乱码
// String fileName = URLEncoder.encode("系统用户信息", "UTF-8");
// response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
// 这里需要设置不关闭流
EasyExcel.write(response.getOutputStream(), SysUser.class).autoCloseStream(Boolean.FALSE).sheet("系统用户信息")
.doWrite(list);
}
// todo servicce+mapper实现查询全部
前端
1.按钮发请求导出
2调用自己的封装的函数
js
// 导出excel
exportUserExcel(){
this.$confirm('确定要导出数据吗?', '信息', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 方案1: 原生js方式发起导出excel请求,后端设置响应格式
// window.location.href = "http://localhost:8888/api/sysUser/export"
// 方案1的话后端需要设置响应格式和内容
// 方案2: 使用axios默认是不行的,需要额外配置
// 方案2的话后端就不需要设置响应,直接导出就行
axios.request({
url: '/api/sysUser/export',
method: 'get',
responseType: 'blob',
}).then(ret=>{
this.download(ret,"系统用户信息")
})
}).catch(() => {
this.$message({
type: 'info',
message: '已取消导出数据!'
})
})
},
// 下载excel的工具方法
download(res, name) {
if (!res) {
return;
}
console.log("res",res)
let url = window.URL.createObjectURL(new Blob([res.data],
{ type: "application/vnd.ms-excel" }));
let link = document.createElement('a');
link.style.display = 'none';
link.href = url;
link.setAttribute('download', name);
document.body.appendChild(link);
link.click();
},
4、自定义单元格样式
EasyExcel支持调整行高、列宽、背景色、字体大小等内容,但是控制方式与使用原生POI无异,比较繁琐,不建议使用。
但是可以使用模板填充的方式,向预设样式的表格中直接写入数据,写入数据的时候会保持原有样式。
三、填充-自学
1、填充一组数据
1.1 准备模板
Excel表格中用{} 来表示包裹要填充的变量,如果单元格文本中本来就有{
、}
左右大括号,需要在括号前面使用斜杠转义\{
、\}
。
代码中被填充数据的实体对象的成员变量名或被填充map集合的key需要和Excel中被{}包裹的变量名称一致。
1.2 封装数据
编写封装填充数据的类或选用Map
java
/**
* 使用实体类封装填充数据
*
* 实体中成员变量名称需要和Excel表各种{}包裹的变量名匹配
*/
@Data
public class FillData {
private String name;
private int age;
}
java
/**
* 生成多组数据代码
* /
private static List<FillData> initFillData() {
ArrayList<FillData> fillDatas = new ArrayList<FillData>();
for (int i = 0; i < 10; i++) {
FillData fillData = new FillData();
fillData.setName("学生0" + i);
fillData.setAge(10 + i);
fillDatas.add(fillData);
}
return fillDatas;
}
1.3 填充
准备数据并填充到文件
java
public static void main(String[] args) {
// 加载模板
InputStream templateFile = FillData.class.getClassLoader().getResourceAsStream(
"fill_data_template1" +
".xlsx");
// 写入文件
String targetFileName = "单组数据填充.xlsx";
// 准备对象数据填充
FillData fillData = new FillData();
fillData.setName("小熊");
fillData.setAge(10);
// 生成工作簿对象
ExcelWriterBuilder workBookWriter = EasyExcel.write(targetFileName).withTemplate(templateFile);
// 获取工作表并填充
//workBookWriter.sheet().doFill(fillData);
// 使用Map数据填充
HashMap<String, String> mapFillData = new HashMap<>();
mapFillData.put("name", "小虎");
mapFillData.put("age", "11");
// 获取第一个工作表填充并自动关闭流
workBookWriter.sheet().doFill(mapFillData);
}
1.4 效果
2、填充多组数据
2.1 准备模板
Excel表格中用{.}
来表示包裹要填充的变量,如果单元格文本中本来就有{
、}
左右大括号,需要在括号前面使用斜杠转义\{
、\}
。
代码中被填充数据的实体对象的成员变量名或被填充map集合的key需要和Excel中被{}包裹的变量名称一致。
2.2 封装数据
编写封装填充数据的类或选用Map
java
// 同上
2.3 填充
准备数据并填充到文件
java
public static void main(String[] args) {
// 加载模板
InputStream templateFile = FillData.class.getClassLoader().getResourceAsStream(
"fill_data_template2.xlsx");
// 写入文件
String targetFileName = "多组数据填充.xlsx";
List<FillData> fillDatas = initData();
// 生成工作簿对象
ExcelWriterBuilder workBookWriter =
EasyExcel.write(targetFileName).withTemplate(templateFile);
// 获取第一个工作表填充并自动关闭流
workBookWriter.sheet().doFill(fillDatas);
}
3、组合填充
3.1 准备模板
即有多组数据填充,又有单一数据填充,为了避免两者数据出现冲突覆盖的情况,在多组填充时需要通过FillConfig
对象设置换行。
3.2 封装数据
编写封装填充数据的类或选用Map
java
// 同上
3.3 填充
准备数据并填充到文件
java
public static void main(String[] args) {
// 加载模板
InputStream templateFile = FillData.class.getClassLoader().getResourceAsStream(
"fill_data_template3.xlsx");
// 目标文件
String targetFileName = "组合数据填充.xlsx";
List<FillData> fillDatas = initData();
// 生成工作簿对象
ExcelWriter excelWriter = EasyExcel.write(targetFileName).withTemplate(templateFile).build();
// 生成工作表对象
WriteSheet writeSheet = EasyExcel.writerSheet().build();
// 组合填充时,因为多组填充的数据量不确定,需要在多组填充完之后另起一行
FillConfig fillConfig = FillConfig.builder().forceNewRow(true).build();
// 填充查看生成Excel出问题
excelWriter.fill(fillDatas, writeSheet); //查看生成Excel出问题
// 填充并换行
//excelWriter.fill(fillDatas, fillConfig, writeSheet);
HashMap<String, String> otherData = new HashMap<>();
otherData.put("date", "2022-03-14");
otherData.put("total", "100");
excelWriter.fill(otherData, writeSheet);
// 一定关闭
excelWriter.finish();
}
4、水平填充
4.1 准备模板
水平填充和多组填充模板一样,不一样的地方在于,填充时需要通过FillConfig
对象设置水平填充。
4.2 封装数据
编写封装填充数据的类或选用Map
java
// 同上
4.3 填充
准备数据并填充到文件
java
public static void main(String[] args) {
// 加载模板
InputStream templateFile = FillData.class.getClassLoader().getResourceAsStream(
"fill_data_template4.xlsx");
// 写入文件
String targetFileName = "easyExcelDemo\\水平数据填充.xlsx";
List<FillData> fillDatas = initData();
// 生成工作簿对象
ExcelWriter excelWriter = EasyExcel.write(targetFileName).withTemplate(templateFile).build();
// 生成工作表对象
WriteSheet writeSheet = EasyExcel.writerSheet().build();
// 组合填充时,因为多组填充的数据量不确定,需要在多组填充完之后另起一行
FillConfig fillConfig = FillConfig.builder().direction(WriteDirectionEnum.HORIZONTAL).build();
// 填充
excelWriter.fill(fillDatas, fillConfig, writeSheet);
HashMap<String, String> otherData = new HashMap<>();
otherData.put("date", "2022-03-14");
otherData.put("total", "100");
excelWriter.fill(otherData, writeSheet);
// 关闭
excelWriter.finish();
}
5、 注意事项
为了节省内存,所以没有采用把整个文档在内存中组织好之后再整体写入到文件的做法,而是采用的是一行一行写入的方式,不能实现删除和移动行,也不支持备注写入。多组数据写入的时候,如果需要新增行,只能在最后一行增加,不能在中间位置添加。
6、填充综合练习
见report_template.xlsx
java
/**
* reprot综合练习
*/
@Test
public void test06() {
InputStream templateInputStream = this.getClass().getClassLoader().getResourceAsStream(
"report_template.xlsx");
// 目标文件
String targetFile = "模板写入6-report.xlsx";
// 写入workbook对象
ExcelWriter workBook =
EasyExcel.write(targetFile, FillData.class).withTemplate(templateInputStream).build();
WriteSheet sheet = EasyExcel.writerSheet().build();
// 填充配置,开启组合填充换行
//FillConfig fillConfig = FillConfig.builder().forceNewRow(true).build();
// ****** 准备数据 *******
// 日期
HashMap<String, String> dateMap = new HashMap<String, String>();
dateMap.put("date", "2020-03-16");
// 总会员数
HashMap<String, String> totalCountMap = new HashMap<String, String>();
dateMap.put("totalCount", "1000");
// 新增员数
HashMap<String, String> increaseCountMap = new HashMap<String, String>();
dateMap.put("increaseCount", "100");
// 本周新增会员数
HashMap<String, String> increaseCountWeekMap = new HashMap<String, String>();
dateMap.put("increaseCountWeek", "50");
// 本月新增会员数
HashMap<String, String> increaseCountMonthMap = new HashMap<String, String>();
dateMap.put("increaseCountMonth", "100");
// 新增会员数据
List<Student> students = initData();
// **** 准备数据结束****
// 写入统计数据
workBook.fill(dateMap, sheet);
workBook.fill(totalCountMap, sheet);
workBook.fill(increaseCountMap, sheet);
workBook.fill(increaseCountWeekMap, sheet);
workBook.fill(increaseCountMonthMap, sheet);
// 写入新增会员
workBook.fill(students, sheet);
workBook.finish();
}
四、常用API及注解-了解
1、常用类
- EasyExcel 入口类,用于构建开始各种操作;
- ExcelReaderBuilder 构建出一个ReadWorkbook对象,即一个工作簿对象,对应的是一个Excel文件;
- ExcelWriterBuilder 构建出一个WriteWorkbook对象,即一个工作簿对象,对应的是一个Excel文件;
- ExcelReaderSheetBuilder 构建出一个ReadSheet对象,即一个工作表的对象,对应的Excel中的每个sheet,一个工作簿可以有多个工作表;
- ExcelWriterSheetBuilder 构建出一WriteSheet对象,即一个工作表的对象,对应的Excel中的每个sheet,一个工作簿可以有多个工作表;
- ReadListener 在每一行读取完毕后都会调用ReadListener来处理数据,我们可以把调用service的代码可以写在其invoke方法内部;
- WriteHandler 在每一个操作包括创建单元格、创建表格等都会调用WriteHandler来处理数据,对使用者透明不可见;
- 所有配置都是继承的 Workbook的配置会被Sheet继承。所以在用EasyExcel设置参数的时候,在EasyExcel...sheet()方法之前作用域是整个sheet,之后针对单个sheet。
2、读取时的注解
@ExcelProperty
使用位置:标准作用在成员变量上
可选属性:
属性名 | 含义 | 说明 |
---|---|---|
index | 对应Excel表中的列数 | 默认-1,建议指定时从0开始 |
value | 对应Excel表中的列头 | |
converter | 成员变量转换器 | 自定义转换器需要实Converter接口 |
使用效果:index属性可以指定当前字段对应excel中的哪一列,可以根据列名value去匹配,也可以不写。
如果不使用@ExcelProperty注解,成员变量从上到下的顺序,对应表格中从左到右的顺序;
**使用建议:**要么全部不写,要么全部用index,要么全部用名字去匹配,尽量不要三个混着用。
代码演示:
java
// 1. 修改成员变量顺序读取Excel表格
// 2. 修改index属性值读取Excel表格
// 3. 修改value属性值读取Excel表格
@ExcelIgnore
标注在成员变量上,默认所有字段都会和excel去匹配,加了这个注解会忽略该字段
代码演示:
java
// 4. 忽略id成员变量值读取Excel表格
@DateTimeFormat
标注在成员变量上,日期转换,代码中用String类型的成员变量
去接收excel中日期格式的数据
会调用这个注解。里面的value
参照java.text.SimpleDateFormat
java
// 5. 按照指定的格式写入Excel内容
@NumberFormat
标注在成员变量上,数字转换,代码中用String类型的成员变量
去接收excel数字格式的数据
会调用这个注解。里面的value
参照java.text.DecimalFormat
@ExcelIgnoreUnannotated
标注在类上。
不标注该注解时,默认类中所有成员变量都会参与读写,无论是否在成员变量上加了@ExcelProperty
的注解。
标注该注解后,类中的成员变量如果没有标注@ExcelProperty
注解将不会参与读写。
3、 读取时通用参数
ReadWorkbook
,ReadSheet
都会有的参数,如果为空,默认使用上级。
-
converter
转换器,默认加载了很多转换器。也可以自定义。 -
readListener
监听器,在读取数据的过程中会不断的调用监听器。 -
headRowNumber
指定需要读表格的 列头行数。默认有一行头,也就是认为第二行开始起为数据。 -
head
与clazz
二选一。读取文件头对应的列表,会根据列表匹配数据。建议使用clas,就是文件中每一行数据对应的代码中的实体类型。 -
clazz
与head
二选一。读取文件的头对应的class,也可以使用注解。如果两个都不指定,则会读取全部数据。 -
autoTrim
字符串、表头等数据自动trim -
password
读的时候是否需要使用密码
4、ReadWorkbook(工作簿对象)参数
excelType
当前excel的类型,读取时会自动判断,无需设置。inputStream
与file
二选一。建议使用file。file
与inputStream
二选一。读取文件的文件。autoCloseStream
自动关闭流。readCache
默认小于5M用 内存,超过5M会使用EhCache
,不建议使用这个参数。useDefaultListener
@since 2.1.4
默认会加入ModelBuildEventListener
来帮忙转换成传入class
的对象,设置成false
后将不会协助转换对象,自定义的监听器会接收到Map<Integer,CellData>
对象,如果还想继续接听到class
对象,请调用readListener
方法,加入自定义的beforeListener
、ModelBuildEventListener
、 自定义的afterListener
即可。
5、ReadSheet(工作表对象)参数
sheetNo
需要读取Sheet的编号,建议使用这个来指定读取哪个SheetsheetName
根据名字去匹配Sheet,excel 2003不支持根据名字去匹配
6、写入时的注解注解
@ExcelProperty
使用位置:标准作用在成员变量上
可选属性:
属性名 | 含义 | 说明 |
---|---|---|
index | 对应Excel表中的列数 | 默认-1,指定时建议从0开始 |
value | 对应Excel表中的列头 | |
converter | 成员变量转换器 | 自定义转换器需要实Converter接口 |
使用效果 :index
指定写到第几列,如果不指定则根据成员变量位置排序;
value
指定写入的列头,如果不指定则使用成员变量的名字作为列头;
如果要设置复杂的头,可以为value指定多个值。
代码演示:
java
// 5. 为《学员表.xlsx》文件中学生信息设置一个统一的表头"学员信息表"
其他注解:
基本和读取时一致
-
@ContentRowHeight() 标注在类上或属性上,指定内容行高
-
@HeadRowHeight() 标注在类上或属性上,指定列头行高
-
@ColumnWidth() 标注在类上或属性上,指定列宽
-
ExcelIgnore` 默认所有字段都会写入excel,这个注解会忽略这个字段
-
DateTimeFormat
日期转换,将Date
写到excel会调用这个注解。里面的value
参照java.text.SimpleDateFormat
是com.alibaba.excel.annotation.format.DateTimeFormat;包下的
-
NumberFormat
数字转换,用Number
写excel会调用这个注解。里面的value
参照java.text.DecimalFormat
-
ExcelIgnoreUnannotated
默认不加ExcelProperty
的注解的都会参与读写,加了不会参与
7、写入时通用参数
WriteWorkbook
、WriteSheet
都会有的参数,如果为空,默认使用上级。
-
converter
转换器,默认加载了很多转换器。也可以自定义。 -
writeHandler
写的处理器。可以实现WorkbookWriteHandler
,SheetWriteHandler
,RowWriteHandler
,CellWriteHandler
,在写入excel的不同阶段会调用,对使用者透明不可见。 -
relativeHeadRowIndex
距离多少行后开始。也就是开头空几行 -
needHead
是否导出头 -
head
与clazz
二选一。写入文件的头列表,建议使用class。 -
clazz
与head
二选一。写入文件的头对应的class,也可以使用注解。 -
autoTrim
字符串、表头等数据自动trim
8、WriteWorkbook(工作簿对象)参数
-
excelType
当前excel的类型,默认为xlsx
-
outputStream
与file
二选一。写入文件的流 -
file
与outputStream
二选一。写入的文件 -
templateInputStream
模板的文件流 -
templateFile
模板文件 -
autoCloseStream
自动关闭流。 -
password
写的时候是否需要使用密码 -
useDefaultStyle
写的时候是否是使用默认头
9、WriteSheet(工作表对象)参数
-
sheetNo
需要写入的编号。默认0 -
sheetName
需要些的Sheet名称,默认同sheetNo -
DateTimeFormat
日期转换,将Date
写到excel会调用这个注解。里面的value
参照java.text.SimpleDateFormat
是com.alibaba.excel.annotation.format.DateTimeFormat;包下的
-
NumberFormat
数字转换,用Number
写excel会调用这个注解。里面的value
参照java.text.DecimalFormat
-
ExcelIgnoreUnannotated
默认不加ExcelProperty
的注解的都会参与读写,加了不会参与
7、写入时通用参数
WriteWorkbook
、WriteSheet
都会有的参数,如果为空,默认使用上级。
-
converter
转换器,默认加载了很多转换器。也可以自定义。 -
writeHandler
写的处理器。可以实现WorkbookWriteHandler
,SheetWriteHandler
,RowWriteHandler
,CellWriteHandler
,在写入excel的不同阶段会调用,对使用者透明不可见。 -
relativeHeadRowIndex
距离多少行后开始。也就是开头空几行 -
needHead
是否导出头 -
head
与clazz
二选一。写入文件的头列表,建议使用class。 -
clazz
与head
二选一。写入文件的头对应的class,也可以使用注解。 -
autoTrim
字符串、表头等数据自动trim
8、WriteWorkbook(工作簿对象)参数
-
excelType
当前excel的类型,默认为xlsx
-
outputStream
与file
二选一。写入文件的流 -
file
与outputStream
二选一。写入的文件 -
templateInputStream
模板的文件流 -
templateFile
模板文件 -
autoCloseStream
自动关闭流。 -
password
写的时候是否需要使用密码 -
useDefaultStyle
写的时候是否是使用默认头
9、WriteSheet(工作表对象)参数
-
sheetNo
需要写入的编号。默认0 -
sheetName
需要些的Sheet名称,默认同sheetNo