大家好,我是小胖。本次给大家带来的SpringBoot中经过自界说注解+反射完成excel导入数据拼装及字段校验的完成办法。这种完成办法其实是很一般、惯例的办法,但许多同学在开发过程中,可能却不太简略想到他。当然我也是许多同学中的一员。
题外话
在之前的文章中,我讲到了Springboot中运用自界说注解+反射机制,经过完成BeanPostProcessor
中postProcessBeforeInitialization
接口的办法,完成了装备类主动注入的问题。有兴趣的同学能够去看一看:
《SpringBoot经过自界说注解完成装备类的主动注入 》
背景
在前段时间的开发作业中,接手了一个很简略,很一般的开发任务。要求完成一个单表的根底数据的批量导入功用。评估下来,用户每次批量导入的数据量也就几千条,也不大。是不是很简略,没有骗你们吧。但是呢,我实践去看的时分发现,好家伙,表里居然一百多个字段,全部是需求导入的(PS:表字段过多为什么没有分表的问题属于历史遗留问题,这儿不做评判)。并且我遍寻整个项目,却没有找到处理批量导入的公共办法,类似功用全部都是if...else...
!!!???
当时我的心理活动是这样的:
:???
:我*,不是吧,这咋搞。
:我总不能去写一百多个判别吧?这样搞估计能被锤死,在我写那么多判别好累的呀!!!
所以我决断仿照。。。不可,不能决断!所以我就给项目简略写了批量导入的公共办法。
思路
关于导入数据的校验来说,核心其实只有几个方面:
- 必填校验
- 判空
- 格式,包括email,电话,身份证等特别格式,长度等
- 与excel列的对应联系
- 字典:需求将导入数据中的内容转成字典值入库
- index:和cell对应联系
- 实体类数据拼装
- 校验失利提示
其实,咱们写的每一个if
判别,都是在做同一个作业。那吗,针对这个场景,咱们就能够选用注解+反射的办法来解决。
开搞
自界说注解
首先,咱们需求添加一个自界说注解。该注解主要符号相应字段与cell的对应联系以及需求进行的处理。(PS:上面说到的特别格式的校验,这儿没有做完成,需求的增加一个字段保存正则表达式即可)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface ImportValidation {
//下标,与excel中列对应,从0开端
int index();
//是否必填,默认是必填
boolean nullAble() default true;
// 字典的Code,用于字典转化
String domainCode() default "";
//字典的名称,用于过错提示
String name() default "";
}
界说一个公共的静态办法
改公共办法需求包括三个参数:
-
class
:用于拼装数据 -
Map<Integer,String[]>
:我这儿是将excel的内容全部读取出来保存在了Map中。 -
domainCodes
:所有涉及的字段转化,调用方应将字段依照code拼装成Map的方式以供运用
public static Result assembleExcelData(Class entryClass, Map<Integer, String[]> excelData,
Map<String,Object> domainCodes){
....
数据拼装
这儿直接看代码
public static Result assembleExcelData(Class entryClass, Map<Integer, String[]> excelData,
Map<String,Object> domainCodes){
//保存回来的成果
Result result = new Result();
//拼装后的数据LIST
List<Object> returnList = new ArrayList<>();
//保存校验失利信息
StringBuilder errorMsg = new StringBuilder();
//循环excel数据
excelData.forEach((i,cells)->{
Object vo = null;
try {
//依照传入的Class,生成对应实例
vo= entryClass.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
//获取并循环Bean中的所有字段,进行校验和拼装
for (Field field : entryClass.getDeclaredFields()) {
//假如包括有ImportValidation注解的话,才进行处理。
if (field.isAnnotationPresent(ImportValidation.class)) {
ImportValidation annotation = field.getAnnotation(ImportValidation.class);
//cell下表
int index = annotation.index();
//字典Code
String domainCode = annotation.domainCode();
//是否必填
boolean nullAble = annotation.nullAble();
//字段名称
String name = annotation.name();
//获取单元格内容,并前后去空格处理
String cellData = cells[index].trim();
/*假如字段为空,且字段设置不能为空,则进行过错提示*/
try {
//若必填,则进行判别校验并提示
if (StringUtils.isEmpty(cellData) && !nullAble) {
errorMsg.append("第").append(i).append("行: ").append(name).append("字段不能为空!\r\n");
}
/*假如字典编码为空,则能够直接赋值*/
else if (StringUtils.isEmpty(domainCode) || StringUtils.isEmpty(cellData)) {
//给对应字段赋值
setFiled(field, vo, cellData);
} else {
//进行字典转化
List<Map> domains = (List<Map>) domainCodes.get(domainCode);
boolean match = false;
for (Map map : domains) {
if (map.get("TEXT").equals(cellData)) {
//给对应字段赋值
setFiled(field, vo, String.valueOf(map.get("VALUE")));
match = true;
break;
}
}
/*假如没有匹配,则转化失利*/
if (!match) {
errorMsg.append("第").append(i).append("行: ").append(name).append("字段字典值不存在!!\r\n");
}
}
} catch (Exception e) {
errorMsg.append("第").append(i).append("行: ").append(name).append("字段填写格式不正确!!\r\n");
}
}
}
//拼装LIST
returnList.add(vo);
});
//假如有过错信息的话,回来过错信息,回来过错符号
if (errorMsg.length()>0){
result = Result.buildError();
result.setMsg(errorMsg.toString());
}
//放入拼装后的LIST。校验失利的字段值为空
result.setData(returnList);
return result;
}
//反射给Filed赋值
public static void setFiled(Field filed,Object vo,String data) throws IllegalAccessException {
try {
//当单元格值不为空的时分才需求进行赋值操作
if (StringUtils.isNotEmpty(data)){
//获取Bean 属性字段的类型
Type fileType = filed.getGenericType();
filed.setAccessible(true);
//假如是String
if (fileType.equals(String.class)){
filed.set(vo,data);
}
//假如是int
else if(fileType.equals(int.class)||fileType.equals(Integer.class)){
filed.set(vo,Integer.valueOf(data));
}
//假如是Double
else if(fileType.equals(Double.class)||fileType.equals(double.class)){
filed.set(vo,Double.valueOf(data));
}
//假如是Long
else if(fileType.equals(Long.class)||fileType.equals(long.class)){
filed.set(vo,Long.valueOf(data));
}
//假如是BigDecimal
else if(fileType.equals(BigDecimal.class)){
filed.set(vo,new BigDecimal(data));
}
//假如是日期
else if(fileType.equals(Date.class)){
filed.set(vo, DateUtils.parseIso8601DateTime(data));
}
}
} catch (Exception e) {
throw e;
}
}
运用
我这儿假如校验失利的话是给前端回来一个过错提示内容的txt文件。可自行依据项目状况处理。校验成功则做刺进的操作。
String domainCodesStr = "MM_DIC_PART_ATTR,MM_DIC_PART_TYPE,MM_DIC_PART_BELONG,MM_DIC_BASE_UNIT," +
"MM_DIC_PART_SOURCE,MM_DIC_W_UNIT,MM_MIN_SHELF_LIFE_UNIT,MM_CURRENCY";
/*查询相关字典,进行校验和转化*/
Map<String, Object> domainsCodes = wsDataDomainService.getDataByDomainCodes(domainCodesStr.split(","));
/*校验并拼装数据*/
Result result = ExcelUtils.assembleExcelData(MmPartNumber.class, excelData, domainsCodes);
if (result.getCode() != 0) {
String realPath = SpringContextHolder.getServletContext().getRealPath("/");
String destination = realPath + "导入过错信息.txt";
/*回来过错信息文件*/
File file = new File(destination);
if (!file.exists()) {
file.createNewFile();
}
FileWriter fileWriter = new FileWriter(file);
fileWriter.write(result.getMsg());
fileWriter.close();
HttpServletResponse response = context.getHttpServletResponse();
FileDownload.fileDownload(response, realPath + "导入过错信息.txt", "导入过错信息.txt");
} else {
//TODO BatchInsert
}
作用
总结
经过自界说注解+反射的办法,完成对批量导入数据的校验及拼装。这是一个非常惯例和简略的完成办法。不得不说,SpringBoot自界说注解真的是个好东西。假如有类似这种重复作业的场景,不妨多考虑考虑,是否能够经过该机制完成。
最终
许多时分,技能便是层窗户纸,戳破了也就很简略了。不怕咱们技能不会,最怕的是咱们想不到~
感谢JRM进行浏览,期望能给一些同学带来帮助,哪怕一个也好。全剧终~