赞
踩
先说POI
,有过报表导入导出经验的同学,应该听过或者使用。
Apache POI
是Apache软件基金会的开源函式库,提供跨平台的Java API
实现Microsoft Office
格式档案读写。但是存在如下一些问题:
对POI有过深入了解的才知道原来POI还有SAX模式(Dom解析模式)。但SAX模式相对比较复杂,excel有03和07两种版本,两个版本数据存储方式截然不同,sax解析方式也各不一样。
想要了解清楚这两种解析方式,才去写代码测试,估计两天时间是需要的。再加上即使解析完,要转换到自己业务模型还要很多繁琐的代码。总体下来感觉至少需要三天,由于代码复杂,后续维护成本巨大。
POI的SAX模式的API可以一定程度的解决一些内存溢出的问题,但是POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大,一个3M的Excel用POI的SAX解析,依然需要100M左右内存。
大部分使用POI都是使用他的userModel模式。userModel的好处是上手容易使用简单,随便拷贝个代码跑一下,剩下就是写业务转换了,虽然转换也要写上百行代码,相对比较好理解。然而userModel模式最大的问题是在于非常大的内存消耗,一个几兆的文件解析要用掉上百兆的内存。现在很多应用采用这种模式,之所以还正常在跑一定是并发不大,并发上来后一定会OOM或者频繁的full gc。
总体上来说,简单写法重度依赖内存,复杂写法学习成本高。
功能强大
代码书写冗余繁杂
读写大文件耗费内存较大,容易OOM
EasyExcel重写了POI对07版Excel的解析,可以把内存消耗从100M左右降低到10M以内,并且再大的Excel不会出现内存溢出,03版仍依赖POI的SAX模式。
下图为64M内存1分钟内读取75M(46W行25列)的Excel(当然还有急速模式能更快,但是内存占用会在100M多一点)
<!-- 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。
/**
* 需求:单实体导入(从磁盘将文件导入到应用程序)
* 导入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 | 男 |
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;
}
注解: 文章后面有详解
Excel
文件调用EasyExcel
的API
读取的Excel
文件的测试类StudentReadDemo
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() {
// 读取文件,读取完之后会自动关闭
/**
* pathName 文件路径;"d:\\学员信息.xls"
* head 每行数据对应的实体;Student.class
* readListener 读监听器,每读一样就会调用一次该监听器的invoke方法
* 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的监听器,用于处理读取产生的数据
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......
}
}
/**
* 需求:单实体导出(从应用程序将文件导入到磁盘)
* 导出多个学生对象到Excel表格
* 包含如下列:姓名、性别、出生日期
* 模板详见:学员信息.xlsx
*/
// 还是上个实体类...
/**
* 测试写出数据
*/
@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" );
}
基于SpringMVC的文件上传和下载(以ssm项目为例演示)
0. 导入依赖
<!-- EasyExcel -->
<!-- lombok -->
<!-- junit -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>1.7.2</version>
</dependency>
<!-- SpringMVC(Spring) -->
<!-- Servlet -->
<!-- 文件上传 -->
<!-- 其他ssm开发相关的 -->
编写excel中每一行对应的实体类
package com.taotie.model;
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 org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
@HeadRowHeight(20) // 指定列头行高
@ColumnWidth(20) // 指定列宽
public class House {
@ExcelProperty("编号")
private int id;
@ExcelProperty("房屋地址")
private String address;
@ExcelProperty("楼层")
private int floor;
@ExcelProperty("面积")
private String area;
@ExcelProperty("朝向")
private String dir;
@ExcelProperty("房间号")
private int roomNum;
@ExcelProperty("装修类型")
private int deco;
@ExcelProperty("是否双气")
private int air;
@ExcelProperty("价格")
private double price;
@ExcelProperty("出租状态")
private int rentStatus;
@ExcelIgnore
private String image;
@DateTimeFormat(pattern = "yyyy-MM-dd")
@ExcelProperty("添加时间")
private Date addTime;
@DateTimeFormat(pattern = "yyyy-MM-dd")
@ExcelProperty("更新时间")
private Date updateTime;
@ExcelProperty("数据状态")
private int status;
// set get toString
}
编写回调监听器HouseReadListener
package com.taotie.util;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.taotie.model.House;
import com.taotie.service.HouseService;
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 HouseReadListener extends AnalysisEventListener<House> {
// 有个很重要的点 DemoDataListener 不能被spring管理,
// 要每次读取excel都要new,然后里面用到spring可以构造方法传进去
private HouseService houseService;
public HouseReadListener(){} // 空参构造
// 有参构造
public HouseReadListener(HouseService houseService){
this.houseService = houseService;
}
// 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
private final int BATCH_COUNT = 5;
private ArrayList<House> list = new ArrayList<>( );
// 每读一样,会调用该invoke方法一次
@Override
public void invoke(House 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("开始存储数据库!");
// 调用业务层存数据库
houseService.saveBatch(list);
System.out.println("存储数据库成功!");
}
}
业务代码接口HouseService和实现类HouseServiceImpl
public interface HouseService {
// ...其他略
ResultData saveBatch(ArrayList<House> list);
}
@Service
public class HouseServiceImpl implements HouseService{
@Autowired
private HouseMapper houseMapper;
// 其他略
@Override
public ResultData saveBatch(ArrayList<House> list) {
boolean isok = houseMapper.saveBatch(list);
if (isok) {
return ResultData.ok();
}
return ResultData.fail();
}
}
持久层Mapper接口和Mapper映射文件(批量添加)
// 参考https://blog.csdn.net/m0_57781768/article/details/128229047
public interface HouseMapper {
boolean saveBatch(ArrayList<House> list);
}
<insert id="saveBatch">
<!-- 没有图片路径,因为excel表格中忽略了 -->
insert into house (address, floor,roomNum, area, dir,deco, air, price, rentStatus, addTime, updateTime,status)
VALUES
<foreach collection ="list" item="house" separator =",">
(#{house.address}, #{house.floor},#{house.roomNum}, #{house.area},
#{house.dir},#{house.deco}, #{house.air}, #{house.price},
#{house.rentStatus},#{house.addTime}, #{house.updateTime},#{house.status})
</foreach>
</insert>
SpringMVC配置文件
<!-- 组件扫描-->
<context:component-scan base-package="com.taotie.controller"/>
<!-- MVC文件上传多部件解析器 -->
<bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver"
id="multipartResolver"/>
编码读取上传的Excel文件
package com.taotie.controller;
import com.alibaba.excel.EasyExcel;
import com.taotie.model.House;
import com.taotie.service.HouseService;
import com.taotie.util.HouseReadListener;
import com.taotie.util.ResultData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc
*/
@RestController
@RequestMapping("/house")
public class HouseController {
@Autowired
private HouseService houseService;
// 其他略
/**
* 上传解析excel
* 参数是MultipartFile,参数名固定file,
* 因为layui文件上传组件渲染的名字叫file
*/
@RequestMapping("/upload/excel")
public ResultData uploadExcel(MultipartFile file) throws IOException {
// 别忘了给HouseReadListener构造方法传入houseService对象
EasyExcel.read(file.getInputStream(),House.class,new HouseReadListener(houseService)).sheet().doRead();
return ResultData.ok();
}
}
前端页面
<!-- 设置上传excel表格按钮 -->
<script type="text/html" id="toolbarDemo">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-normal layui-btn-sm data-add-btn" lay-event="add"> 添加</button>
<button class="layui-btn layui-btn-sm layui-btn-danger data-delete-btn" lay-event="deleteBatch"> 删除</button>
<button id="uploadExcel" class="layui-btn layui-btn-sm layui-btn-normal"> 导入excel </button>
</div>
</script>
<!-- layui上传组件 -->
<script>
// 此段代码要放在table.render的下方,先渲染表格,再渲染表格里面的组件
// 文件上传
upload.render({
elem: '#uploadExcel' //绑定上传按钮元素
, url: '/house/upload/excel.do' //上传接口
,accept:'file' // 接收任意文件类型,默认只能接收图片类型
,exts:'xlsx|xls' // 指定接收的文件类型扩展名
, done: function (res) { // 上传成功的回调函数
if (res.code == 0) {
layer.msg("上传成功!")
}
table.reload("currentTableId");
}
, error: function () {
layer.msg("上传失败!")
}
});
</script>
测试
本地创建一个excel表格,按照实体类中定义的列名填充数据
点击上传
查看数据库,上传成功
暂时有个bug,就是第二次上传会失效,这个是layui-upload组件的bug,还没解决
Layui上传文件时choose事件只触发一次的问题 · Issue #I45Z8T · Layui/layui - Gitee.com
ps: layui自带就有导出功能…
自己实现导出全部数据
实体类于之前一样
// 略
Controller层
/**
* 导出excel表格
*/
@RequestMapping("/export/excel")
public void exportExcel(HttpServletResponse response) throws IOException {
// 调用业务层,查询全部数据
List<House> list = houseService.findAll4Excel();
try {
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(), House.class).autoCloseStream(Boolean.FALSE).sheet("房屋信息")
.doWrite(list);
} catch (Exception e) {
// 重置response
response.reset();
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
Map<String, String> map = new HashMap<String, String>();
map.put("code", "500");
map.put("msg", "下载文件失败" + e.getMessage());
// 需要FastJson依赖
response.getWriter().println(JSON.toJSONString(map));
}
}
前端
<script type="text/html" id="toolbarDemo">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-normal layui-btn-sm data-add-btn" lay-event="add"> 添加</button>
<button class="layui-btn layui-btn-sm layui-btn-danger data-delete-btn" lay-event="deleteBatch"> 删除
</button>
<button id="uploadExcel" class="layui-btn layui-btn-sm layui-btn-normal"> 导入excel</button>
<a class="layui-btn layui-btn-sm layui-btn-normal" href="/house/export/excel.do"> 导出全部数据
</a>
</div>
</script>
EasyExcel支持调整行高、列宽、背景色、字体大小等内容,但是控制方式与使用原生POI无异,比较繁琐,不建议使用。
但是可以使用模板填充的方式,向预设样式的表格中直接写入数据,写入数据的时候会保持原有样式。
Excel表格中用{} 来表示包裹要填充的变量,如果单元格文本中本来就有{
、}
左右大括号,需要在括号前面使用斜杠转义\{
、\}
。
代码中被填充数据的实体对象的成员变量名或被填充map集合的key需要和Excel中被{}包裹的变量名称一致。
编写封装填充数据的类或选用Map
/**
* 使用实体类封装填充数据
*
* 实体中成员变量名称需要和Excel表各种{}包裹的变量名匹配
*/
@Data
public class FillData {
private String name;
private int age;
}
/**
* 生成多组数据代码
* /
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;
}
准备数据并填充到文件
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 效果
Excel表格中用{.}
来表示包裹要填充的变量,如果单元格文本中本来就有{
、}
左右大括号,需要在括号前面使用斜杠转义\{
、\}
。
代码中被填充数据的实体对象的成员变量名或被填充map集合的key需要和Excel中被{}包裹的变量名称一致。
编写封装填充数据的类或选用Map
// 同上
准备数据并填充到文件
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);
}
即有多组数据填充,又有单一数据填充,为了避免两者数据出现冲突覆盖的情况,在多组填充时需要通过FillConfig
对象设置换行。
编写封装填充数据的类或选用Map
// 同上
准备数据并填充到文件
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();
}
水平填充和多组填充模板一样,不一样的地方在于,填充时需要通过FillConfig
对象设置水平填充。
编写封装填充数据的类或选用Map
// 同上
准备数据并填充到文件
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();
}
为了节省内存,所以没有采用把整个文档在内存中组织好之后再整体写入到文件的做法,而是采用的是一行一行写入的方式,不能实现删除和移动行,也不支持备注写入。多组数据写入的时候,如果需要新增行,只能在最后一行增加,不能在中间位置添加。
见report_template.xlsx
/**
* 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();
}
使用位置:标准作用在成员变量上
可选属性:
属性名 | 含义 | 说明 |
---|---|---|
index | 对应Excel表中的列数 | 默认-1,建议指定时从0开始 |
value | 对应Excel表中的列头 | |
converter | 成员变量转换器 | 自定义转换器需要实Converter接口 |
使用效果:index属性可以指定当前字段对应excel中的哪一列,可以根据列名value去匹配,也可以不写。
如果不使用@ExcelProperty注解,成员变量从上到下的顺序,对应表格中从左到右的顺序;
**使用建议:**要么全部不写,要么全部用index,要么全部用名字去匹配,尽量不要三个混着用。
代码演示:
// 1. 修改成员变量顺序读取Excel表格
// 2. 修改index属性值读取Excel表格
// 3. 修改value属性值读取Excel表格
标注在成员变量上,默认所有字段都会和excel去匹配,加了这个注解会忽略该字段
代码演示:
// 4. 忽略id成员变量值读取Excel表格
标注在成员变量上,日期转换,代码中用String类型的成员变量
去接收excel中日期格式的数据
会调用这个注解。里面的value
参照java.text.SimpleDateFormat
// 5. 按照指定的格式写入Excel内容
标注在成员变量上,数字转换,代码中用String类型的成员变量
去接收excel数字格式的数据
会调用这个注解。里面的value
参照java.text.DecimalFormat
标注在类上。
不标注该注解时,默认类中所有成员变量都会参与读写,无论是否在成员变量上加了@ExcelProperty
的注解。
标注该注解后,类中的成员变量如果没有标注@ExcelProperty
注解将不会参与读写。
ReadWorkbook
,ReadSheet
都会有的参数,如果为空,默认使用上级。
converter
转换器,默认加载了很多转换器。也可以自定义。
readListener
监听器,在读取数据的过程中会不断的调用监听器。
headRowNumber
指定需要读表格的 列头行数。默认有一行头,也就是认为第二行开始起为数据。
head
与clazz
二选一。读取文件头对应的列表,会根据列表匹配数据。建议使用clas,就是文件中每一行数据对应的代码中的实体类型。
clazz
与head
二选一。读取文件的头对应的class,也可以使用注解。如果两个都不指定,则会读取全部数据。
autoTrim
字符串、表头等数据自动trim
password
读的时候是否需要使用密码
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
即可。sheetNo
需要读取Sheet的编号,建议使用这个来指定读取哪个SheetsheetName
根据名字去匹配Sheet,excel 2003不支持根据名字去匹配使用位置:标准作用在成员变量上
可选属性:
属性名 | 含义 | 说明 |
---|---|---|
index | 对应Excel表中的列数 | 默认-1,指定时建议从0开始 |
value | 对应Excel表中的列头 | |
converter | 成员变量转换器 | 自定义转换器需要实Converter接口 |
使用效果:index
指定写到第几列,如果不指定则根据成员变量位置排序;
value
指定写入的列头,如果不指定则使用成员变量的名字作为列头;
如果要设置复杂的头,可以为value指定多个值。
代码演示:
// 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
的注解的都会参与读写,加了不会参与
WriteWorkbook
、WriteSheet
都会有的参数,如果为空,默认使用上级。
converter
转换器,默认加载了很多转换器。也可以自定义。
writeHandler
写的处理器。可以实现WorkbookWriteHandler
,SheetWriteHandler
,RowWriteHandler
,CellWriteHandler
,在写入excel的不同阶段会调用,对使用者透明不可见。
relativeHeadRowIndex
距离多少行后开始。也就是开头空几行
needHead
是否导出头
head
与clazz
二选一。写入文件的头列表,建议使用class。
clazz
与head
二选一。写入文件的头对应的class,也可以使用注解。
autoTrim
字符串、表头等数据自动trim
excelType
当前excel的类型,默认为xlsx
outputStream
与file
二选一。写入文件的流
file
与outputStream
二选一。写入的文件
templateInputStream
模板的文件流
templateFile
模板文件
autoCloseStream
自动关闭流。
password
写的时候是否需要使用密码
useDefaultStyle
写的时候是否是使用默认头
sheetNo
需要写入的编号。默认0
sheetName
需要些的Sheet名称,默认同sheetNo
头行高
@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
的注解的都会参与读写,加了不会参与
WriteWorkbook
、WriteSheet
都会有的参数,如果为空,默认使用上级。
converter
转换器,默认加载了很多转换器。也可以自定义。
writeHandler
写的处理器。可以实现WorkbookWriteHandler
,SheetWriteHandler
,RowWriteHandler
,CellWriteHandler
,在写入excel的不同阶段会调用,对使用者透明不可见。
relativeHeadRowIndex
距离多少行后开始。也就是开头空几行
needHead
是否导出头
head
与clazz
二选一。写入文件的头列表,建议使用class。
clazz
与head
二选一。写入文件的头对应的class,也可以使用注解。
autoTrim
字符串、表头等数据自动trim
excelType
当前excel的类型,默认为xlsx
outputStream
与file
二选一。写入文件的流
file
与outputStream
二选一。写入的文件
templateInputStream
模板的文件流
templateFile
模板文件
autoCloseStream
自动关闭流。
password
写的时候是否需要使用密码
useDefaultStyle
写的时候是否是使用默认头
sheetNo
需要写入的编号。默认0
sheetName
需要些的Sheet名称,默认同sheetNo
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。