SpringBoot自定义注解+反射实现 excel 导入的数据组装及字段校验

在前段时间的开发工作中,接手了一个很简单,很普通的开发任务。

要求实现一个单表的基础数据的批量导入功能。

评估下来,用户每次批量导入的数据量也就几千条,也不大。

是不是很简单,没有骗你们吧。但是呢,我实际去看的时候发现,好家伙,表里竟然一百多个字段,全部是需要导入的。

PS:表字段过多为什么没有分表的问题属于历史遗留问题,这里不做评判。

并且我遍寻整个项目,却没有找到处理批量导入的公共方法,相似功能全部都是if...else...!!!???

图片

当时我的心理活动是这样的:

:???

:我*,不是吧,这咋搞。

:我总不能去写一百多个判断吧?这样搞估计能被锤死,在我写那么多判断好累的呀!!!

于是我果断仿照。。。不行,不能果断!

于是我就给项目简单写了批量导入的公共方法。

2思路

对于导入数据的校验来说,核心其实只有几个方面:

  • 必填校验

  • 判空

  • 格式,包含email,电话,身份证等特殊格式,长度等

  • 与excel列的对应关系

  • 字典:需要将导入数据中的内容转成字典值入库

  • index:和cell对应关系

  • 实体类数据组装

  • 校验失败提示

其实,我们写的每一个if判断,都是在做同一个事情。那吗,针对这个场景,我们就可以采用注解+反射的方式来解决。

3开搞

自定义注解

首先,我们需要添加一个自定义注解。该注解主要标记相应字段与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  
}  
效果

图片

4总结

通过自定义注解+反射的方式,实现对批量导入数据的校验及组装。

这是一个非常常规和简单的实现方式。

不得不说,SpringBoot自定义注解真的是个好东西。

如果有类似这种重复工作的场景,不妨多考虑考虑,是否可以通过该机制实现.

最后说一句(求关注!别白嫖!)

如果这篇文章对您有所帮助,或者有所启发的话,求一键三连:点赞、转发、在看。

关注公众号:woniuxgg,在公众号中回复:笔记  就可以获得蜗牛为你精心准备的java实战语雀笔记,回复面试、开发手册、有超赞的粉丝福利!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/722569.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

为什么说重写equals方法也要重写hashCode方法。

equals与hashCode 在Java中&#xff0c;重写 equals() 方法时&#xff0c;通常也需要重写 hashCode() 方法&#xff0c;这是因为在Java中&#xff0c;如果两个对象相等&#xff08;即 equals() 方法返回true&#xff09;&#xff0c;它们的哈希码应该相等&#xff0c;即 hashCo…

嵌入式系统中集成人工智能和机器学习算法来实现智能决策和自主学习

嵌入式系统中集成人工智能和机器学习算法来实现智能决策和自主学习 1. 引言 嵌入式系统是一种专门设计用于特定应用的计算机系统&#xff0c;它通常内置在其他设备中。随着人工智能和机器学习技术的快速发展&#xff0c;将这些先进的算法集成到嵌入式系统中已成为可能。通过集…

TB-03F-Kit 基础AT指令的使用

文章目录 前言一、使用前准备1. 确保TB-03F-Kit已经连接到电脑&#xff0c;并且已经烧录好固件。2. 准备号调试助手&#xff0c;并且电脑上已经安装好了串口驱动3. 打开串口调试助手并复位模组 二、指令介绍三、基础指令1. 准备测试2. AT 测试指令3. ATHELP 查看指令4. ATRST 模…

阻塞队列介绍

阻塞队列 kafka是目前来说性能最好的消息队列服务器&#xff0c;能处理TB级别的数据 作用:点赞、评论时&#xff0c;服务器会自动给某个用户发送通知 kafka是个框架&#xff0c;如果不用框架还要解决类似问题&#xff0c;就要用到阻塞队列 BlockingQueue 阻塞队列就是一个…

AI加速引擎PAI-TorchAcc:整体介绍与性能概述

作者&#xff1a;沈雯婷、黄奕桐、艾宝乐、王昂、李永 1、简介 PAI-TorchAcc(Torch Accelerator)是阿里云人工智能平台开发的Pytorch上的大模型训练加速框架。 PAI-TorchAcc提供了一套基于Pytorch的简洁、易用的接口&#xff0c;无需进行模型转换就可以无缝地接入HuggingFac…

云平台部署与管理

云计算与虚拟化 01、云计算和虚拟化的关系是什么 虚拟化是一种技术,顾名思义,就是将不可拆分的实体资源变成可以自由划分的逻辑资源,从而实现资源的整合、隔离、在分配 云计算是一种服务模式,其思想就是把各种资源整合起来,然后租给有需要的用户 云计算就是利用了虚拟化…

题目 1600: 蓝桥杯-s01串

题目描述: s01串初始为" 0" 按以下方式变换 0变1&#xff0c;1变01 代码: public class Main {public static void main(String[] args) {Scanner scanner new Scanner(System.in);int n scanner.nextInt();String str "0";for (int i 0; i < n;…

Vue+OpenLayers7入门到实战目录

前言 本篇作为《VueOpenLayers7入门到实战》所有文章的二合一汇总目录&#xff0c;方便查找。 本专栏源码是由OpenLayers7.x版本结合Vue框架编写。 本专栏从Vue搭建脚手架到如何引入OpenLayers7依赖的每一步详细新手教程&#xff0c;再到通过各种入门案例和综合性的实战案例&a…

基于springboot+vue的体育馆管理系统

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

已经连接过github远程库,如何再次推送及删除远程库的内容

基于上次将文件推送到已经建好的github远程库上&#xff0c;此篇文章主要介绍如何再次推送文件去直接已经连接过的远程库&#xff0c;以此如何删除远程库中不想要的文件。 一、推送文件到远程库 1.将所需推送的文件拉入本地库所建的文件夹下&#xff1a;{ex&#xff1a;JVM相…

leetcode 121.买卖股票的最佳时机

声明&#xff1a;以下仅代表个人想法&#xff0c;非官方答案或最优题解&#xff01; 题目&#xff1a; 给定一个数组 prices &#xff0c;它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票&#xff0c;并选择在 未来的某一个不同的…

java面试(消息队列MQ)

MQ有什么用&#xff1f;有哪些场景&#xff1f; MQ&#xff08;MessageQueue&#xff09;消息队列。队列&#xff08;FIFO&#xff09;先进先出的数据结构&#xff0c;消息由生产者发送到MQ&#xff0c;后由消费者对消息进行处理。QQ&#xff0c;微信就是MQ场景。 MQ作用&…

搜索准确性提升 20%,Jina Reranker 成为 RAG 优化的新标杆!

在整合大型语言模型&#xff08;LLM&#xff09;到业务流程时&#xff0c;企业经常会遇到一些头疼的问题&#xff0c;比如怎样保持数据时效性、避免幻觉现象&#xff0c;以及如何保护数据安全等等。为了解决这些问题&#xff0c;检索增强生成&#xff08;RAG&#xff09;技术应…

盘点国内大厂的10个AI创作工具,看看你都用过哪些?

国内大厂的 AI 创作工具&#xff0c;目前已经非常多了&#xff0c;而且有很多都是大家耳熟能详的。 下面整理了一些&#xff0c;包含 AI 绘画、AI 视频、AI 智能体、AI 大模型等多个方向的国内大厂 AI 创作工具。 发现有几款 AI 工具&#xff0c;还真的非常好用。看看这些 AI…

如何改变ByteBuf里面的内容

前言 这两天学习Moquette&#xff0c;对ByteBuf做了点研究&#xff0c;对于maxCapacity有了进一步了解。 一、ByteBuf是什么&#xff1f; 最早是在netty中有接触&#xff0c;这算是是核心了&#xff0c;有很多文章进行介绍&#xff1a; Netty(7)源码-ByteBufJava ByteBuf 写…

深入浅出Redis(三):Redis数据的存储、删除以及淘汰

引言 Redis是一款基于键值对的数据结构存储系统&#xff0c;它的特点是基于内存操作、单线程处理命令、IO多路复用模型处理网络请求、键值对存储与简单丰富的数据结构等等 本篇文章不像以往文章围绕Redis某个特点来讲解&#xff0c;而是作为过渡介绍&#xff0c;来说一说Redi…

基于springboot的大型商场应急预案管理系统论文

大型商场应急预案管理系统 摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了大型商场应急预案管理系统的开发全过程。通过分析大型商场应急预案管理系统管理的不足&#xff0c;创建了一个计算机管理大型商场应急…

c语言中的likely 与 unlikely使用

__builtin_expect 是 GCC 编译器提供的一个内建函数&#xff0c;用于帮助编译器优化条件跳转&#xff0c;提升程序的运行效率。 __builtin_expect 接收两个参数&#xff0c;如 __builtin_expect(EXP, N)&#xff0c;其含义是 EXP N 的概率很大。也就是说&#xff0c;这个函数…

软件测试开发环境、测试环境、准生产环境、生成环境

在一个项目开发到发布的整个过程中&#xff0c;会使用到很多个环境进行测试和运行项目。最基本的开发环境、测试环境、准生产环境、生成环境 一、开发环境 开发环境顾名思义就是我们程序猿自己把项目放到自己的电脑上&#xff0c;配置好以后&#xff0c;跑起来项目&#xff0…

如何器测试IP池的质量?代理IP是怎么在问卷调查中应用的呢?

在数字时代&#xff0c;数据收集和分析变得日益重要&#xff0c;而问卷调查作为一种常见的数据收集工具&#xff0c;其效率和准确性直接影响着研究的可信度和有效性。为了实现这一目标&#xff0c;代理IP在问卷调查中的应用变得愈发关键。本文旨在探讨如何测试IP池的质量&#…