我是如何通过反射机制写一个通用excel导入导出的【上篇

阅读: 评论:0

我是如何通过反射机制写一个通用excel导入导出的【上篇

我是如何通过反射机制写一个通用excel导入导出的【上篇

快一年没写推文了,感觉肚子里也积了点东西,今天就搞一搞个人觉得还是挺酷的东西--结合反射机制搞一个通用Excel导入,通用导出功能。

想了解下反射api的使用可以看下JAVASchool的说明:.html

以前写POI导入Excel的时候,基本都是来一个模板,就写一个解析读取接口,就下面像这样:

一列一列的取,取完再赋值给定义的实体类,这样来一个模板写一段代码去解析读取呢,其实也没啥问题,在需要读取的Excel很少的时候,写也很快。但是当需要读取的Excel越来越多的时候,每来一个模板就写一个Excel,来一个写一个的话,那就很难受了,效率也很低!像之前我搞的一个项目,需要读取解析的Excel模板四十多个的,一个一个写的话,那我要写到吐了。

 

所以,写出一段可以解析不同Excel模板的代码,还是很有必要的,大体的实现思路如下:

1:根据不同的Excel文档模型,在数据库中定义一个解析模板

2:接口调用的时候把Excel文件与相应的Excel模板类型名传入,后台根据模板类型名找相应的解析模板去解析Excel

3:解析读取数据

4:拿到数据进行后续的处理

 

详细过程:

1:先在数据库中定义一个模板解析表(用来保存每个Excel的解析模板),sql如下:

CREATE TABLE `template_data` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `type` varchar(100) NOT NULL DEFAULT '' COMMENT '模板类型',
  `start_row` int(11) NOT NULL DEFAULT '0' COMMENT '开始行',
  `field` json DEFAULT NULL COMMENT '字段',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='模板数据表';

提示一下,filed字段是json类型的,mysql 5.7 以上的版本才支持json类型,低于5.7的版本sql会执行失败,需要升级mysql。

表字段说明:

type:用来存模板类型,也可以说是这个模板的名称

start_row:表示从第几行开始读取,一般的Excel表前几行都会是描述或者表头,所以需要定义好从第几行开始读取数据,像我用来做测试用的张大炮的Excel样例,就是从第三行开始读取

field:用来存每一列的数据的类型,对应的java类属性名(关键点,决定在利用反射的时候赋值给java类中的哪个属性),是否允许为空等。张大炮Excel文档的一条模板解析sql如下:

INSERT INTO `template_data`(`id`, `type`, `start_row`, `field`) VALUES (1, 'ZhangDaPao', 2, '[{"fieldLong": 50, "fieldName": "name", "fieldType": "String", "isNotNull": "是", "templateColumn": 0}, {"fieldLong": 11, "fieldName": "age", "fieldType": "Integer", "isNotNull": "是", "templateColumn": 1}, {"fieldLong": 10, "fieldName": "sex", "fieldType": "String", "isNotNull": "是", "templateColumn": 2}, {"fieldLong": 255, "fieldName": "address", "fieldType": "String", "isNotNull": "是", "templateColumn": 3}, {"fieldLong": 1024, "fieldName": "remark", "fieldType": "String", "isNotNull": "是", "templateColumn": 4}]');
 

filed字段里面的json数据说明

fieldLong-用来限制Excel列的数据长度

filedName-对应的类属性名

fieldType-属性类型

isNotNull-是否不能为空

templateColumn-数据在Excel行中的第几列,下标从0开始。

之后每新增一个Excel模板,就在数据库中配置多一条sql就好了,接下来就是具体的代码实现了。

项目结构如下:

pom文件依赖:

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><!--  web依赖  --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--  POI依赖  --><dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>3.17</version></dependency><dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>3.17</version></dependency><!--  lombok依赖  --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.6</version></dependency><!--  阿里数据源依赖  --><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.0</version></dependency><!-- mysql依赖--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>javax.persistence</groupId><artifactId>javax.persistence-api</artifactId><version>2.2</version></dependency><dependency><groupId>net.sf.json-lib</groupId><artifactId>json-lib</artifactId><version>2.4</version><classifier>jdk15</classifier></dependency><dependency><groupId>org.apachemons</groupId><artifactId>commons-lang3</artifactId></dependency><!-- mybatis-plus依赖--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.p</version></dependency><!--  json--><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.49</version></dependency><dependency><groupId>javax</groupId><artifactId>javaee-web-api</artifactId><version>7.0</version></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>

 

实体类准备:

TemplateData:模板数据类

import lombok.Data;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;/*** 模板数据*/
@Data
public class TemplateData {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;/**实体对象*/private String type;/**开始行*/private int startRow;/**字段*/private String field;
}

 

FieldTemplate:JSON字段实体类

import lombok.Data;/*** 字段模板*/
@Data
public class FieldTemplate {/**字段名*/private String fieldName;/**模板列*/private int templateColumn;/**字段类型*/private String fieldType;/**是否非空 (是  否)*/private String isNotNull;/**字段长度限制*/private int fieldLong;
}

 

ZhangDaPao:张大炮实体类

import lombok.Data;@Data
public class ZhangDaPao {private String name;private Integer age;private String sex;private String address;private String remark;
}

 

Mybatis映射类:

TemplateDataMapper:模板数据映射类

ity.TemplateData;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;@Mapper
@Repository
public interface TemplateDataMapper {@Select("select * from template_data where type = #{type}")TemplateData findByType(@Param("type") String type);}

 

业务层:ReportService,写了很多注释,希望对理解逻辑有帮助

主要逻辑:

1-获取解析模板

2-匹配模板字段与实体类的成员变量

3-校验数据列数据与赋值给成员变量

4-返回数据

import com.alibaba.fastjson.JSON;
ity.FieldTemplate;
ity.TemplateData;
ity.ZhangDaPao;
plate.mapper.TemplateDataMapper;
slf4j.Slf4j;
import net.sf.json.JSONArray;
import org.apachemons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;import flect.Field;
import java.util.ArrayList;
import java.util.List;@Service
@Slf4j
public class ReportService {@Autowiredprivate TemplateDataMapper templateDataMapper;/*** @param file:需要解析的Excel* @param type:文件模板名* @return*/public Object dataUpload(MultipartFile file, String type) {if (file == null || file.isEmpty()) {("上报文件为空");return "上报文件为空";}Workbook workbook = null;try {workbook = InputStream());} catch (Exception e) {e.printStackTrace();}if (workbook == null) {("workbook == null");return "workbook == null";}return execute(workbook, type);}/*** 上报处理*/public Object execute(Workbook hssfWorkbook, String type) {TemplateData templateData = null;switch (type) {case "ZhangDaPao"://根据模板类型去数据库取相应的解析模板templateData = templateDataMapper.findByType("ZhangDaPao");return addData(templateData, hssfWorkbook,new ZhangDaPao());("没有找到对应的模板...");}return null;}/*** @param templateData* @param hssfWorkbook* @param t 与Excel中数据对应的实体类* @param <T>* @return*/public <T> List<T> addData(TemplateData templateData, Workbook hssfWorkbook, T t) {JSONArray jsonArray = JSONArray.fromObject(new ArrayList<>());//读第一个Excel表Sheet hssfSheet = SheetAt(0);//拿解析模板中的field字段的json数据列,转为字段模板列表List<FieldTemplate> fieldTemplateList = JSON.Field(), FieldTemplate.class);//从模板中定义的开始行开始遍历读取行for (int rowNum = StartRow(); rowNum <= LastRowNum(); rowNum++) {Row hssfRow = Row(rowNum);if (hssfRow != null) {T data = t;//遍历field字段中的json数据列for (FieldTemplate sqlFt : fieldTemplateList) {//获取泛型的class,再用getDeclaredFields()获取类的成员变量//遍历成员变量for (Field field : t.getClass().getDeclaredFields()) {//如果成员变量跟数据库中定义的属性名匹配,进入数据读取解析//也就是sql中,field定义的 "fieldName" 与实体ZhangDaPao中的属性名相同,进入数据读取if (Name().FieldName())) {Cell cell = TemplateColumn());/*Excel数据列的校验,读取,赋值操作* cell:数据列* sqlFt:数据库中field字段中的json模板* field:反射中的成员变量* rowNum:读取的Excel是哪一行,用来抛异常的时候记录是第几行有问题* data:与Excel数据对应的实体类* */verifyType(cell, sqlFt, field, rowNum, data);}}}//把读取成功的data加到json数组中,用于后续泛型转换jsonArray.add(data);}}//转换为相应的实体列表返回return (List<T>) Collection(jsonArray, t.getClass());}/*** 校验,读取* @param cell:数据列** @param sqlFt:数据库中field字段中的json模板,格式是下面这样的* {"fieldLong": 50, "fieldName": "name", "fieldType": "String", "isNotNull": "是", "templateColumn": 0}** @param field:反射中获取到的成员变量* @param rowNum:读取的Excel是哪一行,用来抛异常的时候记录是第几行有问题* @param data:与Excel数据对应的实体类*/private <T> T verifyType(Cell cell, FieldTemplate sqlFt, Field field, int rowNum, T data) {// 校验非空if (IsNotNull().equals("是")) {if (cell == null || StringUtils.String())) {("数据格式错误:行:" + (rowNum + 1) + ";列:" + (TemplateColumn() + 1) + ",必要数据不能为空");}}// 校验长度if (cell != null && String().length() >= FieldLong()) {("数据格式错误:行:" + (rowNum + 1) + ";列:" + (TemplateColumn() + 1) + ",长度超出限制");}try {//打破封装//一般成员变量我们都是定义为private的,在反射机制中,需要设置该属性允许被操作field.setAccessible(true);//根据数据库中json数据中定义的该列是什么数据类型,进行相应的数据处理,这里只弄了String跟Integer的if (FieldType().equals("String")) {//String类型,直接toString赋值field.set(data, String());} else if (FieldType().equals("Integer")) {//Integer类型,读取到的格式是这样的 30.0 先转  double再转为int类型field.set(data, new String()).intValue());}} catch (Exception e) {("数据格式错误:行:" + (rowNum + 1) + ";列:" + (TemplateColumn() + 1));}return data;}
}

 

控制类:

plate.service.ReportService;
slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;@RestController
@Slf4j
public class TemplateUploadController {@Autowiredprivate ReportService reportService;@RequestMapping(value = "/upload", method = RequestMethod.POST)public void illegalBuildingUpload(MultipartFile file, @RequestParam String type) {log.info("文件上传---------");reportService.dataUpload(file, type);}
}

 

postman调用测试:调用成功,返回了张大炮等人的信息

接下来我们增加一个Excel模板,如下

新增Teacher实体类如下:

import lombok.Data;@Data
public class Teacher {private String name;private String school;private String course;
}

新增一条模板数据到数据库

INSERT INTO `template_data`(`id`, `type`, `start_row`, `field`) VALUES (2, 'Teacher', 2, '[{"fieldLong": 50, "fieldName": "name", "fieldType": "String", "isNotNull": "是", "templateColumn": 0}, {"fieldLong": 50, "fieldName": "school", "fieldType": "String", "isNotNull": "是", "templateColumn": 1}, {"fieldLong": 50, "fieldName": "course", "fieldType": "String", "isNotNull": "是", "templateColumn": 2}]');
 

在ReportService的execute中加入Teacher类型的捕获,如下:

具体代码:

case "Teacher":templateData = templateDataMapper.findByType("Teacher");return addData(templateData, hssfWorkbook,new Teacher());

postman调用:

获取数据成功,后续继续新增Excel模板的时候,只需要在数据库中配置新的模板,再建一个实体类就可以了。

通用的Excel导入就写到这,哪天心血来潮了再继续写通用导出的。

程序员-就得搞搞偷懒式的代码

 

本文发布于:2024-02-05 00:35:20,感谢您对本站的认可!

本文链接:https://www.4u4v.net/it/170720007861297.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:我是   反射   上篇   机制   excel
留言与评论(共有 0 条评论)
   
验证码:

Copyright ©2019-2022 Comsenz Inc.Powered by ©

网站地图1 网站地图2 网站地图3 网站地图4 网站地图5 网站地图6 网站地图7 网站地图8 网站地图9 网站地图10 网站地图11 网站地图12 网站地图13 网站地图14 网站地图15 网站地图16 网站地图17 网站地图18 网站地图19 网站地图20 网站地图21 网站地图22/a> 网站地图23