EasyExcel-读
依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.39</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.1.6</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
编写导出的实体类:字段和顺序需要和表格一一对应:实体类的属性位置就是表格从左到右的属性
@Data@AllArgsConstructor@NoArgsConstructorpublic class Student {
/**
* 学生姓名
*/
private String name;
/**
* 学生出生日期
*/
private Date birthday;
/**
* 学生性别
*/
private String gender;
/**
* id
*/
private String id;}
读取Excel文件:
/**
* * 最简单的读
* * 1. 创建excel对应的实体对象
* *
2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,
* *
3. 直接读即可
*/
public class ExcelTest {
private static final Logger LOGGER = LoggerFactory.getLogger(ExcelTest.class);
/**
* 工作簿:bookwork
* 工作表:sheet
*/
@Test
public void test01() {
// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
//获得工作簿对象
/*
EasyExcel.read()参数:
pathName 文件路径;"d:\学员信息表.xlsx"
head 每行数据对应的实体;Student.class
readListener 读监听器,每读一样就会调用一次该监听器的invoke方法
*/
ExcelReaderBuilder excelReaderBuilder = EasyExcel.read("学员信息表.xlsx", Student.class, new StudentReadListener());
//获取一个工作表
ExcelReaderSheetBuilder sheet = excelReaderBuilder.sheet();
//读取工作表内容:sheet方法参数:工作表的顺序号(从0开始)或者工作表的名字,不传默认为0
sheet.doRead();
}}
读取Excel的监听器,用于处理读取产生的数据
/**
* 在读的时候,每读一行,就会自动调用监听器的invoke方法,并且把读取的内容自动封装成了一个对象
* @author JUNSHI 405773808@qq.com
* @create 2020-08-27 22:55
*/public class StudentReadListener extends AnalysisEventListener<Student> {
private static final Logger LOGGER = LoggerFactory.getLogger(StudentReadListener.class);
/**
* 每读一样会自动调用这个方法
* @param student 读取的内容自动封装成了一个对象
* @param context
*/
@Override
public void invoke(Student student, AnalysisContext context) {
LOGGER.info("解析到一条数据:{}", JSON.toJSONString(student));
System.out.println("student = " + student);
}
// 全部读完之后,会调用该方法
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
}}
运行结果:
2020-08-28 19:47:54.165 = [main] = [INFO ] = c.c.l.StudentReadListener - [invoke,26] - 解析到一条数据:{"birthday":1584288000000,"gender":"男","name":"学号00"}student = Student(name=学号00, birthday=Mon Mar 16 00:00:00 CST 2020, gender=男, id=null)2020-08-28 19:47:54.173 = [main] = [INFO ] = c.c.l.StudentReadListener - [invoke,26] - 解析到一条数据:{"birthday":1584288000000,"gender":"男","name":"学号01"}student = Student(name=学号01, birthday=Mon Mar 16 00:00:00 CST 2020, gender=男, id=null)2020-08-28 19:47:54.175 = [main] = [INFO ] = c.c.l.StudentReadListener - [invoke,26] - 解析到一条数据:{"birthday":1584288000000,"gender":"男","name":"学号02"}student = Student(name=学号02, birthday=Mon Mar 16 00:00:00 CST 2020, gender=男, id=null)2020-08-28 19:47:54.176 = [main] = [INFO ] = c.c.l.StudentReadListener - [invoke,26] - 解析到一条数据:{"birthday":1584288000000,"gender":"男","name":"学号03"}student = Student(name=学号03, birthday=Mon Mar 16 00:00:00 CST 2020, gender=男, id=null)2020-08-28 19:47:54.177 = [main] = [INFO ] = c.c.l.StudentReadListener - [invoke,26] - 解析到一条数据:{"birthday":1584288000000,"gender":"男","name":"学号04"}student = Student(name=学号04, birthday=Mon Mar 16 00:00:00 CST 2020, gender=男, id=null)2020-08-28 19:47:54.178 = [main] = [INFO ] = c.c.l.StudentReadListener - [invoke,26] - 解析到一条数据:{"birthday":1584288000000,"gender":"男","name":"学号05"}student = Student(name=学号05, birthday=Mon Mar 16 00:00:00 CST 2020, gender=男, id=null)2020-08-28 19:47:54.179 = [main] = [INFO ] = c.c.l.StudentReadListener - [invoke,26] - 解析到一条数据:{"birthday":1584288000000,"gender":"男","name":"学号06"}student = Student(name=学号06, birthday=Mon Mar 16 00:00:00 CST 2020, gender=男, id=null)2020-08-28 19:47:54.181 = [main] = [INFO ] = c.c.l.StudentReadListener - [invoke,26] - 解析到一条数据:{"birthday":1584288000000,"gender":"男","name":"学号07"}student = Student(name=学号07, birthday=Mon Mar 16 00:00:00 CST 2020, gender=男, id=null)2020-08-28 19:47:54.182 = [main] = [INFO ] = c.c.l.StudentReadListener - [invoke,26] - 解析到一条数据:{"birthday":1584288000000,"gender":"男","name":"学号08"}student = Student(name=学号08, birthday=Mon Mar 16 00:00:00 CST 2020, gender=男, id=null)2020-08-28 19:47:54.184 = [main] = [INFO ] = c.c.l.StudentReadListener - [invoke,26] - 解析到一条数据:{"birthday":1584288000000,"gender":"男","name":"学号09"}student = Student(name=学号09, birthday=Mon Mar 16 00:00:00 CST 2020, gender=男, id=null)
EasyExcel-写
/**
* 需求:单实体导出
* 导出多个学生对象到Excel表格
* 包含如下列:姓名、性别、出生日期
* 写不需要监听器
*/
@Test
public void test02(){
/*
* 工作簿对象
* @param pathName 文件路径名称
* @param head 封装写出的数据实体的类型
* @return 写出工作表对象
*/
ExcelWriterBuilder write = EasyExcel.write("学员信息表-write.xlsx", Student.class);
//工作表对象
ExcelWriterSheetBuilder sheet = write.sheet();
//需要写出的数据:
List<Student> students = initData();
//写出
sheet.doWrite(students);
}
/**
* 初始好数据
*/
private static List<Student> initData() {
ArrayList<Student> students = new ArrayList<Student>();
Student data = new Student();
for (int i = 0; i < 10; i++) {
data.setName("学号0" + i);
data.setBirthday(new Date());
data.setGender("男");
students.add(data);
}
return students;
}
实体类:
@Data@AllArgsConstructor@NoArgsConstructor//@ColumnWidth(20) //列宽//@HeadRowHeight(30) //列头行高//@ContentRowHeight 内容行高public class Student {
/**
* id
*/
@ExcelProperty(value = "学生编号")
private String id;
/**
* 学生姓名
*/
//@ExcelProperty(value = "学生姓名",index = 3) index是表格的索引地址
private String name;
/**
* 学生出生日期
*/
//@ColumnWidth(20)
//@DateTimeFormat("yyyy-MM-dd") 设置日期格式
@ExcelProperty(value = "出生日期", index = 1)
@ColumnWidth(20) //列宽
private Date birthday;
/**
* 学生性别
*/
private String gender;
/**
* 忽略这个字段
*/
@ExcelIgnore
private String ignore;}
EasyExcel-web
文件上传:依赖
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.39</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.1.6</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency></dependencies>
student实体类:
@Data@AllArgsConstructor@NoArgsConstructorpublic class Student {
/**
* id
*/
@ExcelIgnore
private String id;
/**
* 学生姓名
*/
private String name;
/**
* 学生出生日期
*/
private Date birthday;
/**
* 学生性别
*/
private String gender;}
service:
@Service("studentService")public class StudentServiceImpl implements StudentService {
private int i = 0;
@Override
public void save(List<Student> students) {
for (Student student : students) {
System.out.println(i++ + "学生" + "号:" + student);
}
}}
webListener:
@Component@Scope("prototype")public class WebStudentReadListener extends AnalysisEventListener<Student> {
private static final Logger LOGGER = LoggerFactory.getLogger(WebStudentReadListener.class);
@Autowired
private StudentService studentService;
private final int BATCH_SAVE_NUM = 5;
ArrayList<Student> students = new ArrayList<>();
@Override
public void invoke(Student student, AnalysisContext context) {
LOGGER.info("解析到一条数据:{}", JSON.toJSONString(student));
students.add(student);
if (students.size() % BATCH_SAVE_NUM == 0) {
studentService.save(students);
students.clear();
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
}}
Controller:
@Controller@RestControllerpublic class WebUploadAndDownload {
@Autowired
private WebStudentReadListener webStudentReadListener;
/**
* 文件上传
* 1. 编写excel中每一行对应的实体类
* 2. 由于默认异步读取excel,所以需要逐行读取的回调监听器
* 3. 开始读取Excel
*/
@PostMapping("upload")
public String upload(MultipartFile multipartFile) throws IOException{
ExcelReaderBuilder read = EasyExcel.read(multipartFile.getInputStream(), Student.class, webStudentReadListener);
read.sheet().doRead();
return "success";
}}
文件下载:controller:
@GetMapping("download")public void download(HttpServletResponse response) throws IOException {
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");
ExcelWriterBuilder write = EasyExcel.write(response.getOutputStream(), Student.class);
write.sheet("模板").doWrite(initData());}/**
* 初始好数据
*/private static List<Student> initData() {
ArrayList<Student> students = new ArrayList<Student>();
Student data = new Student();
for (int i = 0; i < 10; i++) {
data.setName("学号0" + i);
data.setBirthday(new Date());
data.setGender("男");
students.add(data);
}
return students;}
Excel填充
1、填充一组数据
1.1 准备模板
Excel表格中用{} 来表示包裹要填充的变量,如果单元格文本中本来就有{、}左右大括号,需要在括号前面使用斜杠转义{、}。
代码中被填充数据的实体对象的成员变量名或被填充map集合的key需要和Excel中被{}包裹的变量名称一致。
1.2 封装数据
编写封装填充数据的类或选用Map
/**
* 使用实体类封装填充数据
*
* 实体中成员变量名称需要和Excel表各种{}包裹的变量名匹配
*/@Datapublic class FillData {
private String name;
private int age;}
/**
* 生成多组数据代码
* /
private static List initFillData() {
ArrayList fillDatas = new ArrayList();
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 填充
准备数据并填充到文件
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", "Map");
mapFillData.put("age", "11");
// 获取第一个工作表填充并自动关闭流
workBookWriter.sheet().doFill(mapFillData);}
1.4 效果
2、填充多组数据
2.1 准备模板
Excel表格中用{.}来表示包裹要填充的变量,如果单元格文本中本来就有{、}左右大括号,需要在括号前面使用斜杠转义{、}。
代码中被填充数据的实体对象的成员变量名或被填充map集合的key需要和Excel中被{}包裹的变量名称一致。
2.2 封装数据
编写封装填充数据的类或选用Map
2.3 填充
准备数据并填充到文件
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
3.3 填充
准备数据并填充到文件
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();
// 填充并换行
excelWriter.fill(fillDatas, fillConfig, writeSheet);
HashMap<String, String> otherData = new HashMap<>();
otherData.put("date", "2020-03-14");
otherData.put("total", "100");
excelWriter.fill(otherData, writeSheet);
// 关闭
excelWriter.finish();}
4、水平填充
4.1 准备模板
水平填充和多组填充模板一样,不一样的地方在于,填充时需要通过FillConfig对象设置水平填充。
4.2 封装数据
编写封装填充数据的类或选用Map
4.3 填充
准备数据并填充到文件
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", "2020-03-14");
otherData.put("total", "100");
excelWriter.fill(otherData, writeSheet);
// 关闭
excelWriter.finish();}
5、 注意事项
为了节省内存,所以没有采用把整个文档在内存中组织好之后再整体写入到文件的做法,而是采用的是一行一行写入的方式,不能实现删除和移动行,也不支持备注写入。多组数据写入的时候,如果需要新增行,只能在最后一行增加,不能在中间位置添加。
6、填充综合练习
见report_template.xlsx
/**
* reprot综合练习
*/@Testpublic 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();}
EasyExcel常用API
1、常用类
EasyExcel注解
2、读取时的注解
@ExcelProperty
使用位置:标准作用在成员变量上 把实体类和Excel关联起来
可选属性:
属性名含义说明
index
对应Excel表中的列数
默认-1,建议指定时从0开始
value
对应Excel表中的列头
converter
成员变量转换器
自定义转换器需要实Converter接口
使用效果:index属性可以指定当前字段对应excel中的哪一列,可以根据列名value去匹配,也可以不写。
如果不使用@ExcelProperty注解,成员变量从上到下的顺序c读取excel单元格数据,对应表格中从左到右的顺序;
**使用建议:**要么全部不写,要么全部用indexc读取excel单元格数据,要么全部用value去匹配,尽量不要三个混着用。
代码演示:
// 1. 修改成员变量顺序读取Excel表格// 2. 修改index属性值读取Excel表格// 3. 修改value属性值读取Excel表格
@ExcelIgnore
标注在成员变量上,默认所有字段都会和excel去匹配,加了这个注解会忽略该字段
代码演示:
// 4. 忽略id成员变量值读取Excel表格
@DateTimeFormat
标注在成员变量上,日期转换,代码中用String类型的成员变量去接收excel中日期格式的数据会调用这个注解。里面的value参照java.text.SimpleDateFormat
// 5. 按照指定的格式写入Excel内容
@NumberFormat
标注在成员变量上,数字转换,代码中用String类型的成员变量去接收excel数字格式的数据会调用这个注解。里面的value参照java.text.DecimalFormat
@ExcelIgnoreUnannotated
标注在类上。
不标注该注解时,默认类中所有成员变量都会参与读写,无论是否在成员变量上加了@ExcelProperty的注解。
标注该注解后,类中的成员变量如果没有标注@ExcelProperty注解将不会参与读写。
3、 读取时通用参数
ReadWorkbook,ReadSheet都会有的参数,如果为空,默认使用上级。
4、ReadWorkbook(工作簿对象)参数
5、ReadSheet(工作表对象)参数
6、写入时的注解注解
@ExcelProperty
使用位置:标准作用在成员变量上
可选属性:
属性名含义说明
index
对应Excel表中的列数
默认-1,指定时建议从0开始
value
对应Excel表中的列头
converter
成员变量转换器
自定义转换器需要实Converter接口
使用效果:index指定写到第几列,如果不指定则根据成员变量位置排序;
value指定写入的列头,如果不指定则使用成员变量的名字作为列头;
如果要设置复杂的头,可以为value指定多个值。
代码演示:
// 5. 为《学员表.xlsx》文件中学生信息设置一个统一的表头“学员信息表”
其他注解
基本和读取时一致
7、写入时通用参数
WriteWorkbook、WriteSheet都会有的参数,如果为空,默认使用上级。
8、WriteWorkbook(工作簿对象)参数
9、WriteSheet(工作表对象)参数
关注Java技术栈看更多干货
获取Spring Boot实战笔记!
限 时 特 惠: 本站每日持续更新海量各大内部创业教程,一年会员只需98元,全站资源免费下载 点击查看详情
站 长 微 信: muyang-0410