最近在工作遇到了Excel读写,但是先有的已poi为基准的框架不能满足需求。
要求
前言
最近在工作遇到了Excel读写,但是先有的已poi为基准的框架不能满足需求,网上也没有合适的车轮子,那只能自己造了。
一、需求?如图
要求读写这样的数据主体和关联数据,
1)主体数据有题头,除了通常读写数据,支持枚举。
2)存在任意关联数据,每种关联数据的第一行第一列有关联的数据的类型。
二、使用步骤
1.写入代码1
代码如下(示例):
private <T> void writeSheet(Workbook workbook, List<T> itemsList, Class<T> clzz, Boolean needHead){
// 获取T对应的sheetName
String sheetName = "sheet";
if(clzz.isAnnotationPresent(ExcelClass.class)){
// 获取 "类" 上的注解
sheetName = (clzz.getAnnotation(ExcelClass.class)).sheetName();
}
Sheet sheet = workbook.createSheet(sheetName);
try {
// 在相应的单元格进行赋值
Integer rowIndex = 0;
// 获取对象的上的ExcelConfig注解
List<Field> fields = getClzzOrdeByIndex(clzz);
// 创建文件题头
if(needHead){
// 创建行数据
Row row = sheet.getRow(rowIndex);
if (null == row) {
row = sheet.createRow(rowIndex);
}
for(int i = 0; i < fields.size(); i++){
if(fields.get(i).getAnnotation(ExcelConfig.class) != null){
// 获取表字段的名称
String headValue = fields.get(i).getAnnotation(ExcelConfig.class).value();
Cell cell = row.getCell(i);
if (null == cell) {
cell = row.createCell(i);
}
if(!StringUtils.isEmpty(headValue)){
cell.setCellValue(headValue);
}
}
}
rowIndex = rowIndex+1;
}
// 写入数据
for (int i = 0; i < itemsList.size() ; i++) {
// 在excel中写入值
T item = itemsList.get(i);
if (item != null) {
rowIndex = writeCell(sheet, item, rowIndex, clzz, null);
}
}
} catch (Exception e){
e.printStackTrace();
}
}
private <T> Integer writeCell(Sheet sheet, T item, Integer rowIndex, Class clzz, String relationTypeName) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
// 获取对象的上的ExcelConfig注解
List<Field> fields = getClzzOrdeByIndex(clzz);
// 创建行数据
Row row = sheet.getRow(rowIndex);
if (null == row) {
row = sheet.createRow(rowIndex);
}
Integer dataPos = 0;
if(relationTypeName != null){
// 添加占位符
Cell cell = row.getCell(dataPos);
if (null == cell) {
cell = row.createCell(dataPos);
}
cell.setCellValue(relationTypeName);
dataPos = 1;
}
// 处理需要写入excel的数据
for (int i = 0; i < fields.size(); i++) {
Field field = fields.get(i);
// 判断获取的字段必须要有ExcelConfig标识
if(field != null && field.getAnnotationsByType(ExcelConfig.class) != null){
// 获取改字段是否是需要经过枚举处理
Class enumClass = field.getAnnotation(ExcelConfig.class).enumClass();
Object value = null;
// 判断是否需要枚举
if(enumClass != EmptyEums.class){
// 获取枚举所需的值
String fieldName = field.getAnnotation(ExcelConfig.class).enumFieldName();
// 获取枚举的目标字段的值
Object needEnumKey = getFieldValueByName(fieldName, item);
// 获取枚举的中每个枚举类
Object[] objects = enumClass.getEnumConstants();
for (Object obj : objects) {
Class<?> enumClzz = obj.getClass();
// 每个枚举的getKey方法
Method getKeyMethod = enumClzz.getDeclaredMethod("getKey");
Object enumKey = getKeyMethod.invoke(obj);
// 如果枚举的key和需要处理的needEnumKey相同,则获取对应value为field字段的值
if(enumKey.toString().equals(needEnumKey.toString())){
Method getValueMethod = enumClzz.getDeclaredMethod("getValue");
value = getValueMethod.invoke(obj);
break;
}
}
} else {
value = getFieldValueByName(field.getName(), item);
}
// 获取注解字段对应的index值
Integer columnNum = field.getAnnotation(ExcelConfig.class).index() + dataPos;
Cell cell = row.getCell(columnNum);
if (null == cell) {
cell = row.createCell(columnNum);
}
// 判断数据类型
if (value != null){
parseFiledValue(cell, value,null);
}
}
}
rowIndex = rowIndex+1;
// 写入关系型数据
// clazz 集成了relations话,说明存在关系
if(clzz.getSuperclass() == Relations.class){
List<Relation> relations = (List<Relation>)getFieldValueByName("relations",item);
if(relations != null){
for (Relation relation : relations) {
List relationDatas = relation.getData();
for (int i = 0; i < relationDatas.size(); i++) {
// 判断是否要添加参数名称
String subRelationTypeName = "";
// 只有在第一行添加参数名称
if( i == 0){
subRelationTypeName = relation.getRelationTypeName();
}
rowIndex = writeCell(sheet, relationDatas.get(i), rowIndex, relation.getClazz(), subRelationTypeName);
}
}
}
}
return rowIndex;
}
大致思路
- 组装数据:
如上图:主体数据USER,关系数据:UserWallet 、UserFamily 。主体数据和关系数据通过Relation类进行关联的,降低主体数据与关系数据的耦合,提高代码重复利用性。public static List<User> createUserData(){ List<User> list = new ArrayList<>(); for (int i = 0; i < 5000; i++) { User user1 = new User(); user1.setId(i); user1.setUsername("张三"); user1.setHead("http://qzapp.qlogo.cn/qzapp/101357640/3C94155CAB4E28517D8435BF404B52F1/100"); user1.setSex(0); user1.setPhone("1880000" + i); user1.setAdress("1000001"); UserFamily father = new UserFamily("父子", user1.getUsername() + "的父亲", i + 20); UserFamily mother = new UserFamily("母亲", user1.getUsername() + "的母亲", i + 20); List<UserFamily> families = new ArrayList<>(); families.add(father); families.add(mother); Relation familyRelation = new Relation<UserFamily>(UserFamily.class, families, false, "UserFamily"); UserWallet rmb = new UserWallet(Currency.RMB.getKey(), 100 + i); UserWallet dollar = new UserWallet(Currency.DOLLAR.getKey(), 100 + i); List<UserWallet> wallets = new ArrayList<>(); wallets.add(rmb); wallets.add(dollar); Relation walletRelation = new Relation<>(UserWallet.class, wallets, false, "UserWallet"); List<Relation> relations = new ArrayList<>(); relations.add(familyRelation); relations.add(walletRelation); user1.setRelations(relations); list.add(user1); } return list; }
@ExcelClass 注解需要操作的excel数据类,value表示数据类型,如果数据是作为关系型数据,那么第一行第一列就会是value值,如下图public class Relations<T>{ // 关系数据 private List<Relation<T>> relations; public List<Relation<T>> getRelations() { return relations; } public void setRelations(List<Relation<T>> relations) { this.relations = relations; } } public class Relation<T>{ /** * 参数的数据类型 */ private Class clazz ; /** * 参数数据 */ private List<T> data; /** * 是否需要字段说明 */ private Boolean needHead; /** * 参数类型,用于区分类别 */ private String relationTypeName; public Relation(){}; public Relation(Class<T> clazz, List<T> data, Boolean needHead, String relationTypeName) { this.clazz = clazz; this.data = data; this.needHead = needHead; if(StringUtils.isEmpty(relationTypeName)){ if(clazz.isAnnotationPresent(ExcelClass.class)){ this.relationTypeName = clazz.getAnnotation(ExcelClass.class).value(); } } else { this.relationTypeName = relationTypeName; } } } @ExcelClass(value="User", sheetName="用户") public class User extends Relations { @ExcelConfig(value="序号", index =0) private Integer id; @ExcelConfig(value="姓名", index =1) private String username; @ExcelConfig(value="头部", index =2) private String head; @ExcelConfig(value="性别", index =3) private Integer sex; @ExcelConfig(value="手机号", index =4) } @ExcelClass(value="UserFamily", sheetName="家庭") public class UserFamily{ @ExcelConfig(value="关系", index =0) private String relation; @ExcelConfig(value="姓名", index =1) private String name; @ExcelConfig(value="年龄", index =2) private Integer age; } @ExcelClass(value="UserWallet", sheetName="钱包") public class UserWallet{ @ExcelConfig(value="钱包类型", index =0, enumClass= Currency.class, enumFieldName="type") private String typeName; @ExcelConfig(value="数量", index =1) private Integer num; // 钱包类型 private Integer type; }
@ExcelConfig字段 修饰excel需要导出的字段,value:excel上对应字段的题头,index: 对应的列数,enumClass:枚举字段类型,枚举必须是 (key value)方式,enumFieldName:枚举对应的key值,通过反射获取enumFieldName对应的属性通过枚举enumClass类获取其value值,赋值到当前被ExcelConfig注解的字段。public enum Currency implements ExcelFieldEunm { RMB(1, "人命币"), DOLLAR(2, "美元"); @Getter private Integer key; @Getter private String value; Currency(int key, String value) { this.key = key; this.value = value; } }
- 将数据导入excel中
代码能力一般啦,大致思路:private <T> void writeSheet(Workbook workbook, List<T> itemsList, Class<T> clzz, Boolean needHead){ // 获取T对应的sheetName String sheetName = "sheet"; if(clzz.isAnnotationPresent(ExcelClass.class)){ // 获取 "类" 上的注解 sheetName = (clzz.getAnnotation(ExcelClass.class)).sheetName(); } Sheet sheet = workbook.createSheet(sheetName); try { // 在相应的单元格进行赋值 Integer rowIndex = 0; // 获取对象的上的ExcelConfig注解 List<Field> fields = getClzzOrdeByIndex(clzz); // 创建文件题头 if(needHead){ // 创建行数据 Row row = sheet.getRow(rowIndex); if (null == row) { row = sheet.createRow(rowIndex); } for(int i = 0; i < fields.size(); i++){ if(fields.get(i).getAnnotation(ExcelConfig.class) != null){ // 获取表字段的名称 String headValue = fields.get(i).getAnnotation(ExcelConfig.class).value(); Cell cell = row.getCell(i); if (null == cell) { cell = row.createCell(i); } if(!StringUtils.isEmpty(headValue)){ cell.setCellValue(headValue); } } } rowIndex = rowIndex+1; } // 写入数据 for (int i = 0; i < itemsList.size() ; i++) { // 在excel中写入值 T item = itemsList.get(i); if (item != null) { rowIndex = writeCell(sheet, item, rowIndex, clzz, null); } } } catch (Exception e){ e.printStackTrace(); } } private <T> Integer writeCell(Sheet sheet, T item, Integer rowIndex, Class clzz, String relationTypeName) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { // 获取对象的上的ExcelConfig注解 List<Field> fields = getClzzOrdeByIndex(clzz); // 创建行数据 Row row = sheet.getRow(rowIndex); if (null == row) { row = sheet.createRow(rowIndex); } Integer dataPos = 0; if(relationTypeName != null){ // 添加占位符 Cell cell = row.getCell(dataPos); if (null == cell) { cell = row.createCell(dataPos); } cell.setCellValue(relationTypeName); dataPos = 1; } // 处理需要写入excel的数据 for (int i = 0; i < fields.size(); i++) { Field field = fields.get(i); // 判断获取的字段必须要有ExcelConfig标识 if(field != null && field.getAnnotationsByType(ExcelConfig.class) != null){ // 获取改字段是否是需要经过枚举处理 Class enumClass = field.getAnnotation(ExcelConfig.class).enumClass(); Object value = null; // 判断是否需要枚举 if(enumClass != EmptyEums.class){ // 获取枚举所需的值 String fieldName = field.getAnnotation(ExcelConfig.class).enumFieldName(); // 获取枚举的目标字段的值 Object needEnumKey = getFieldValueByName(fieldName, item); // 获取枚举的中每个枚举类 Object[] objects = enumClass.getEnumConstants(); for (Object obj : objects) { Class<?> enumClzz = obj.getClass(); // 每个枚举的getKey方法 Method getKeyMethod = enumClzz.getDeclaredMethod("getKey"); Object enumKey = getKeyMethod.invoke(obj); // 如果枚举的key和需要处理的needEnumKey相同,则获取对应value为field字段的值 if(enumKey.toString().equals(needEnumKey.toString())){ Method getValueMethod = enumClzz.getDeclaredMethod("getValue"); value = getValueMethod.invoke(obj); break; } } } else { value = getFieldValueByName(field.getName(), item); } // 获取注解字段对应的index值 Integer columnNum = field.getAnnotation(ExcelConfig.class).index() + dataPos; Cell cell = row.getCell(columnNum); if (null == cell) { cell = row.createCell(columnNum); } // 判断数据类型 if (value != null){ parseFiledValue(cell, value,null); } } } rowIndex = rowIndex+1; // 写入关系型数据 // clazz 集成了relations话,说明存在关系 if(clzz.getSuperclass() == Relations.class){ List<Relation> relations = (List<Relation>)getFieldValueByName("relations",item); if(relations != null){ for (Relation relation : relations) { List relationDatas = relation.getData(); for (int i = 0; i < relationDatas.size(); i++) { // 判断是否要添加参数名称 String subRelationTypeName = ""; // 只有在第一行添加参数名称 if( i == 0){ subRelationTypeName = relation.getRelationTypeName(); } rowIndex = writeCell(sheet, relationDatas.get(i), rowIndex, relation.getClazz(), subRelationTypeName); } } } } return rowIndex; }
主体数据,通过类的注解属性获取对应的注解的数据和属性的值, 根据注解ExcelConfig数据index的值,在excel中对应的列上写入主体数据(通过反射获取数据)。
关系数据:根据Relations字段判断该数据是否有关联数据,如果有的话,在处理完每一条主体数据后,处理关系数据,处理逻辑和主体数据一致。
2.读入数据
大致思路:获取被ExcelClass注解的类,根据主体数据的class的类上的注解,获取excel中对应的sheet,获取class上每个被excelconfig注解过的属性,根据属性上的index的位置,通过反射给属性赋值。
private <T extends Relations> List<T> readExcel() throws IOException, InvocationTargetException, NoSuchMethodException {
//获取
List<T> datas = new ArrayList<>();
Workbook workbook = null;
Sheet sheet = null;
Row row = null;
//读取Excel文件
File excelFile = new File(this.fileName.trim());
InputStream is = null;
try {
is = new FileInputStream(excelFile);
//获取Excel工作薄
if (excelFile.getName().endsWith("xlsx")) {
workbook = new XSSFWorkbook(is);
} else {
workbook = new HSSFWorkbook(is);
}
if (workbook == null) {
throw new ExcelFileNotFind(this.fileName+"未找到");
}
// 注解了ExcelClass的类,map的key为注解的valueh值
Map<String, Class<?>> classMap = annotationExcelClassToMap();
//获取Excel需要解析的sheet,根据注解的的sheetname获取,否则就取第一个
if(this.clazz.isAnnotationPresent(ExcelClass.class)){
// 获取 "类" 上的注解
String sheetName = (this.clazz.getAnnotation(ExcelClass.class)).sheetName();
String value = (this.clazz.getAnnotation(ExcelClass.class)).value();
// classMap移除表格主体数据
classMap.remove(value);
// 根据对象的注解找到对应的sheet
sheet = workbook.getSheet(sheetName);
} else {
sheet = workbook.getSheetAt(0);
}
// 主体数据解析
List<Field> tableFields = getClzzOrdeByIndex(this.clazz);
// 记录当前数据类型
Class<?> analysisClass = null;
// 记录上一个数据类型,用于判单当前数据类型和上一个数据类型是否一直
Class<?> lastAnalysisClass = null;
List<Field> analysisFields = tableFields;
// 读取数据
for(int rowNum = 1; rowNum <= sheet.getLastRowNum(); rowNum++) {
row = sheet.getRow(rowNum);
// 跳过空行
if (isRowEmpty(row)){
continue;
}
List<Field> needAnalysis = new ArrayList<>();
String firstCellValue = getCellStringValue(row.getCell(0));
// 第一个单元格是否为空
if(firstCellValue != null && !StringUtils.isEmpty(firstCellValue)){
// 是否是关系字段
if(classMap.keySet().contains(firstCellValue)){
analysisClass = classMap.get(firstCellValue);
analysisFields = getClzzOrdeByIndex(analysisClass);
} else {
// 设置为主题字段
analysisClass = this.clazz;
analysisFields = tableFields;
}
}
// 获取一行
// row = sheet.getRow(rowNum);
// 获取对象实列
Object instance = new Object();
// 是否为关系对象
if(analysisClass.equals(this.clazz)){
instance = this.clazz.newInstance();
} else {
instance = analysisClass.newInstance();
}
// 根据字段读取数据
for(int i =0; i < analysisFields.size(); i++){
Field field = analysisFields.get(i);
Short index = field.getAnnotation(ExcelConfig.class).index();
if(index != null && index > -1){
String methodName = MethodUtils.setMethodName(field.getName());
Method method = analysisClass.getMethod(methodName, field.getType());
// 获取字段对应的单元格
Cell cell = null;
// 判断是 数据本体,还是关系数据
if(!analysisClass.equals(this.clazz)){
// 关系数据,第一列为 关系名称,所以跳过
cell = row.getCell(index+1);
} else {
cell = row.getCell(index);
}
if(cell != null){
// 转为为字符串
cell.setCellType(Cell.CELL_TYPE_STRING);
// 日期字段担负处理
if (DateUtil.isDateFied(field)) {
Date date = cell.getDateCellValue();
if(date != null){
method.invoke(instance, cell.getDateCellValue());
}
} else {
String value = getCellStringValue(cell);
method.invoke(instance, convertType(field.getType(), value.trim()));
}
}
} else {
throw new ExcelNotFiledIndexException(field.getName()+"无index值,无法解析数据");
}
}
// 如果是关系数据
if(!analysisClass.equals(this.clazz)){
// 最后一个关系数据
List<Relation> lastDataRelations = datas.get(datas.size()-1).getRelations();
// 初始化 relations值
if(lastDataRelations == null){
datas.get(datas.size()-1).setRelations(new ArrayList<Relations>());
lastDataRelations = datas.get(datas.size()-1).getRelations();
}
// 获取最有一个本体数据的List的最后一个本体的关系数据
if(analysisClass.equals(lastAnalysisClass)){
// 获取关系中的最后一个类型,并把当前的relation添加进去
Relation dataRelation = lastDataRelations.get(lastDataRelations.size()-1);
dataRelation.getData().add(instance);
} else {
// 如果和上一个不同,说明是不同关系数据,创建
Object finalInstance = instance;
Relation relation = new Relation(analysisClass, new ArrayList<Object>(){{add(finalInstance);};}, null, null);
lastDataRelations.add(relation);
}
} else {
// 本体数据
datas.add((T)instance);
}
// 保存上一个数据解析的类型
lastAnalysisClass = analysisClass;
}
is.close();
} catch (IOException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
} finally {
if(is != null){
is.close();
}
}
return datas;
}
代码比较乱,懒得整理。