Java对Excel的处理最主流估计是POI了,POI几乎涵盖了Excel所有的操作,但是很多时候并不是很需要那么多复杂操作,有时候仅仅是简单的导入和导出操作,也需要写一大堆代码,很难看,于是自己弄了一个简单的封装POI的Excel导入导出工具,暂且命名为Octopus。
项目地址:Octopus on github
引入maven依赖
增加仓库
<repositories>
<repository>
<id>chenhuanming-repo</id>
<name>chenhuanming-repo</name>
<url>https://raw.githubusercontent.com/zerouwar/my-maven-repo/master</url>
</repository>
</repositories>
引入依赖
<dependency>
<groupId>cn.chenhuanming</groupId>
<artifactId>octopus</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
导入
我们用一个简单的例子来说明————学生信息的导入。
下面是一个excel文件中sheet的数据,有四个学生信息.
studentId | name | sex | inTime | score |
---|---|---|---|---|
20134123 | John | M | 2013-9-1 | 89 |
20124524 | Joyce | F | 20123-8-31 | 79 |
20156243 | P | 2015-5-15 | 94 | |
20116522 | Nemo | F | 2011-2-26 |
我们需要用一个学生类,用来保存从excel中读取的学生信息.
//lombok annotations
@Getter
@Setter
@NoArgsConstructor
@ToString
public class Student {
@ModelLineNumber
private int lineNum;
@ModelProperty(value = "id",blankable = false)
private String studentId;
@ModelProperty(value = "name",defaultValue = "anonymous")
private String name;
@ModelProperty(value = "sex",wrongMsg = "sex must be M or F",pattern = "^M|F$")
private String sex;
@ModelProperty(value = "admission",wrongMsg = "admission must be a date")
private LocalDate inTime;
@ModelProperty(value = "score",wrongMsg = "score must be numeric",defaultValue = "100")
private Double score;
}
其中lineNum表示这个学生信息在Excel中的行数(用户在Excel看到的左边行树,从1开始)
准备就绪了,用代码读取excel,并输出学生信息:
InputStream is = getClass().getResourceAsStream("/test.xlsx");
Workbook workbook = WorkbookFactory.create(is);
Sheet sheet = workbook.getSheetAt(0);
//read students with ReusableSheetReader
SheetReader<ModelEntity<Student>> students = new ReusableSheetReader<>(sheet,1,0,Student.class);
//print students information
for (ModelEntity<Student> student:students) {
System.out.println(student.toString());
System.out.println();
}
前面三行代码都是通过POI获取Sheet对象,接着交给ReusableSheetReader
来把Excel数据读取出来并封装成ModelEntity
集合,可以遍历SheetReader
来读取ModelEntity
ModelEntity
主要有两个属性,一个是学生对象,也就是我们定义的Student类型对象,另一个就是导入时的异常集合,所有导入时发生的异常都封装成ExcelImportException
对象,其中包括数据类型不对,不能为空,正则检验不符合等。
下面是遍历输出的结果
SimpleModelEntity(entity=Student(lineNum=2, studentId=20134123, name=John, sex=M, inTime=2013-09-01, score=89.0, gradeAndClazz=null), exceptions=[])
SimpleModelEntity(entity=Student(lineNum=3, studentId=20124524, name=Joyce, sex=F, inTime=null, score=79.0, gradeAndClazz=null), exceptions=[cn.chenhuanming.octopus.exception.DataFormatException: in cell (3,4) ,20123-8-31 can not be formatted to class java.time.LocalDate])
SimpleModelEntity(entity=Student(lineNum=4, studentId=20156243, name=anonymous, sex=null, inTime=2015-05-15, score=94.0, gradeAndClazz=null), exceptions=[cn.chenhuanming.octopus.exception.PatternNotMatchException: P and ^M|F$ don't match!])
SimpleModelEntity(entity=Student(lineNum=5, studentId=20116522, name=Nemo, sex=F, inTime=2011-02-26, score=100.0, gradeAndClazz=null), exceptions=[])
注意的是,ReusableSheetReader
是重用了ModelEntity
,也就是每次循环读取数据,其实都是一个ModelEntity对象(出于效率考虑),这更适合在读取数据时候使用。如果需要把每一行excel数据都保存起来,SimpleSheetReader
会更适合,这个类每次循环遍历都是一个新的ModelEntity
对象
导入到此结束了,下面说一下可以通过注解来自定义导入功能
@ModelProperty
是核心注解,负责主要的导入功能定义
- value:被注解的类属性的实际含义
- defaultValue:当Excel中的单元格为空或者数值为空白的,并且blankable为true时,赋予被注解的类属性的默认值
- wrongMsg:错误信息。当被注解的类属性读取失败,例如单元格值无法变换为字符串或者正则检验失败时,把这个wrongMsg的值保存到
ExcelImportException
里 - pattern:正则表达式。用于整句正则检验
- blankable:是否为空(单元格为空或者单元格数值为空白),布尔类型
事实上,即使没有对类属性加@ModelProperty
,Octopus依然会处理并读取该类属性。
如果不想处理某个类属性,可以用@ModelIgnore
让Octopus忽略掉它。另外如果需要的话,可以给一个int
或者Integer
类型属性加上@ModelLineNumber
,Octopus会把行数赋值给这个属性
导出
导出比导入要简单很多,我们给上面的Student类加多一个引用类型属性GradeAndClazz
,GradeAndClazz
有年级和班级两个字符串属性,为了让这个Student类既可以用于导入也可以用于导出,做了一点小修改
@Getter
@Setter
@NoArgsConstructor
@ToString
public class Student {
@ModelLineNumber
private int lineNum;
@ModelProperty(value = "id",blankable = false)
private String studentId;
@ModelProperty(value = "name",defaultValue = "anonymous")
private String name;
@ModelProperty(value = "sex",wrongMsg = "sex must be M or F",pattern = "^M|F$")
private String sex;
//jackson annotation to format output
@JsonFormat(pattern = "yyyy-MM-dd")
@ModelProperty(value = "admission",wrongMsg = "admission must be a date")
private LocalDate inTime;
@ModelProperty(value = "score",wrongMsg = "score must be numeric",defaultValue = "100")
private Double score;
@ModelIgnore
private GradeAndClazz gradeAndClazz;
public Student(String studentId, String name, String sex, LocalDate inTime, Double score,GradeAndClazz gradeAndClazz) {
this.studentId = studentId;
this.name = name;
this.sex = sex;
this.inTime = inTime;
this.score = score;
this.gradeAndClazz = gradeAndClazz;
}
}
@Getter
@Setter
@AllArgsConstructor
public class GradeAndClazz{
private String grade;
private String clazz;
}
然后通过一个xml来指定导出的列和顺序
<?xml version="1.0" encoding="UTF-8"?>
<ExportModel class="entity.Student">
<Field name="studentId" description="id"></Field>
<Field name="name" description="name"></Field>
<Field name="sex" description="sex"></Field>
<Field name="inTime" description="admission"></Field>
<Field name="score" description="score"></Field>
<Field name="gradeAndClazz" description="class info">
<Field name="grade" description="grade"></Field>
<Field name="clazz" description="class"></Field>
</Field>
</ExportModel>
注意这里gradeAndClazz,由于它是Student的一个引用类型属性,所以需要嵌合<Field>
标签让Octopus也导出里面的grade和class属性
接着我们用代码来导出
//prepare workbook and stuednts objects
Workbook workbook = new XSSFWorkbook();
String rootPath = this.getClass().getClassLoader().getResource("").getPath();
FileOutputStream os = new FileOutputStream(rootPath+"/export.xlsx");
GradeAndClazz gradeAndClazz = new GradeAndClazz("2014","R6");
Student student1 = new Student("201223","John","M", LocalDate.now(),98.00,gradeAndClazz);
Student student2 = new Student("204354","Tony","M", LocalDate.now(),87.00,gradeAndClazz);
Student student3 = new Student("202432","Joyce","F", LocalDate.now(),90.00,gradeAndClazz);
//write excel with OneSheetExcelWriter
ExcelWriter<Student> studentExcelWriter = new OneSheetExcelWriter<>(getClass().getClassLoader().getResourceAsStream("studentExport.xml"));
studentExcelWriter.write(workbook,Arrays.asList(student1,student2,student3));
workbook.write(os);
前面一堆代码都是导出的一些准备,真正导出的代码就后面三句,通过OneSheetExcelWriter
来导出这三个学生信息,下面是导出的Excel
可以看到Octopus对于引用类型会做合并单元格处理,可以运行项目下的测试例子
这里可以看到Octopus对于时间格式化了,其实是在实体类那里,加了Jackson
的@JsonFormat
注解,Octopus本身不做对象序列化工作,委托给Jackson
把整个集合转换成树后再写入Excel,所以可以利用这点充分利用Jackson
来自定义序列化格式
最后~
Octopus只是个人意愿,心血来潮,临时起意写的玩具,如果有可以改善的建议或者issue,可以在项目github的Issue给我反映,本人尽量完善这个玩具ヾ( ̄▽ ̄)ByeBye~
网友评论