Spring Boot下的一种导入Excel文件的代码框架

本文介绍了一种Spring Boot下可重用且灵活的Excel导入代码框架,解决了数据转换和处理复杂情况的问题。通过泛型和基类设计,支持多种实体类的导入,处理了数据列转换、缺失字段和数据错误处理等需求。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、前言

​ Spring Boot下如果只是导入一个简单的Excel文件,是容易的。网上类似的文章不少,有的针对具体的实体类,代码可重用性不高;有的利用反射机制,开发了Excel导入工具类,这样方法较好,但如果数据列有物理含义的转换,或需要进行计算处理等复杂情况,难以为继。
​ 针对上述不足之处,本文提出了一种可重用,并且具有数据处理灵活性的代码框架。

2、需求分析

​ 导入Excel表格数据,应解决下列问题:

  1. 访问Excel文件,并将指定Sheet页中的数据读出来。
  2. 识别并支持xls和xlsx格式的Excel文件。
  3. 使用实体类对象列表来存放表格数据,进而可以存入数据库表或其它业务处理。
  4. 实体类容易更换为其它实体类,无需大量重复代码,从而可以方便支持多种内容的表格数据导入。
  5. 表格数据列与实体类属性之间可能存在数据转换,常见的是物理意义的转换和数据类型转换。如性别,表格中标题为“性别”的数据列的取值为字符串“男”或“女”,而实体类中对应的属性字段名为“gender”,取值为整型数“1”或“2”。
  6. 表格数据的标题行可能存在下列情况:
    • 没有标题行,本模块不考虑支持此情况。
    • 数据列标题的次序不固定,并且可能中间有它无需导入的数据列标题。
    • 需要导入的数据列标题不全。分两种情况:关键数据列缺失、可选数据列缺失。
  7. 表格数据的数据块位置可能存在下列情况:
    • 数据块可能不是从第一行第一列开始,而是有偏移。
    • 数据行的列集合与标题行的列集合不一致,可能不是简单的包含关系。
  8. 表格数据行可能存在下列情况:
    • 空行。
    • 该数据行的某些列数据有问题,不能加载到实体类对象中。
  9. 错误信息处理:精确定位并记录数据错误信息,数据行错误,能定位到行号、列号,便于错误核查和处理。遇到数据行数据错误,记录错误信息并继续处理。

3、设计思路

​ 综合上述功能模块的需求分析,总体设计思路如下:

  1. 使用泛型T来代表实体类,这样可以方便支持更多实体类。
  2. 泛型T代表的实体类,必需提供某些接口方法,以便实现表格数据行的载入,表格数据行的载入实体类,有一些公共的处理代码和属性,这些可以封装在Excel导入对象基类BaseImportObj中。泛型T代表的实体类继承基类BaseImportObj,这样可以大幅度减少实体类的代码量。
  3. 泛型T代表的实体类,其属性字段集合应包括全部需要导入的字段集合,但不必完全一致,实体类的字段可以更多,以便不影响其它业务应用。
  4. 封装一个Excel导入处理类ExcelImportHandler,处理访问Excel文件并读取指定Sheet页的数据,返回List的列表数据。ExcelImportHandler类支持泛型T代表的实体类。
  5. ExcelImportHandler类中,为了返回List的列表数据,需要创建T类型对象,为了解决类似“new T()”问题,使用克隆(clone)方法,即要求BaseImportObj实现Cloneable。
  6. 为了描述各标题是必需字段,还是可选字段,使用导入字段定义类ImportFieldDef。

​ Excel文件导入功能模块的类关系图如下图所示:

如上图所示,ExcelImportHandler类调用实体类T,实体类T继承BaseImportObj类,BaseImportObj类实现Cloneable接口类,实体类T和BaseImportObj类引用ImportFieldDef类。如果不同的表格数据需要导入同一个实体类数据中,如另一份表格,对“性别”数据列的取值定义不一样,可以通过实体类的子类来实现。

4、代码实现

4.1、 导入依赖包

​ 要访问Excel文件,需要引入POI依赖包:

        <!-- excel-->
		<dependency>
		    <groupId>org.apache.poi</groupId>
		    <artifactId>poi</artifactId>
		    <version>3.10-FINAL</version>
		</dependency>
		<dependency>
		    <groupId>org.apache.poi</groupId>
		    <artifactId>poi-ooxml</artifactId>
		    <version>3.10-FINAL</version>
		</dependency> 

4.2、 导入字段定义类ImportFieldDef类

​ ImportFieldDef类代码如下:

package com.abc.questInvest.excel;

import lombok.Data;

/**
 * @className	: ImportFieldDef
 * @description	: 导入字段定义
 *
 */
@Data
public class ImportFieldDef {
   
   
	//字段名
	private String fieldName;
	//字段是否必需,1表示必需,0表示可选
	private Integer mandatory;
	
	public ImportFieldDef(String fieldName,Integer mandatory) {
   
   
		this.fieldName = fieldName;
		this.mandatory = mandatory;
	}
}

​ ImportFieldDef类是一个实体类,定义了2个属性字段:

  • fieldName字段,指实体类中属性字段的名称。
  • mandatory字段,表示该字段是必需字段,还是可选字段。必需字段要求数据列必需在导入表格中,可选字段,允许无相应数据。

​ ImportFieldDef类使用lombok的@Data注解,代替属性的getter/setter代码。

4.3、 Excel导入对象基类BaseImportObj类

​ BaseImportObj类代码如下:

package com.abc.questInvest.excel;

import java.util.HashMap;
import java.util.Map;

import com.abc.questInvest.entity.ThrowReceiveInfo;

/**
 * @className	: BaseImportObj
 * @description	: Excel导入数据对象基类
 *
 */
public class BaseImportObj implements Cloneable{
   
   
    //数据列下标与字段名的映射表,数据列下标从0开始
    //对于一次导入的行数据,columnIdxMap不变化,不必每个对象都创建,可以共享使用
    protected Map<Integer,String> columnIdxMap;
    
    //表格中数据区域的开始列号,0-based
    protected Integer firstColumnIdx; 
    
    // ========================================================
    // ===============公共方法实现===============================
	/**
	 * 
	 * @methodName		: inputTitles
	 * @description		: 导入标题行数据
	 * @param arrTitle	: 标题名数组,标题行按列序号顺序存放,第一个成员为开始列号,0-based
	 * @return			: 异常信息,空串表示无异常
	 *
	 */
	public String inputTitles(String[] arrTitle){
   
       	
		//标题名与导入字段定义对象的映射表
    	Map<String,ImportFieldDef> titleMap = new HashMap<String,ImportFieldDef>();
		//调用子类重载方法,设置标题名与导入字段定义对象的映射关系    	
    	setExcelTitles(titleMap);
    	
    	//创建columnIdxMap对象
    	columnIdxMap = new HashMap<Integer,String>();

    	//对于标题行,arrTitle的第一个成员为开始列号
    	firstColumnIdx = Integer.parseInt(arrTitle[0]);
    	
    	//遍历输入的标题数组,建立列下标与字段名的映射关系    	
    	for (int i = 1; i < arrTitle.length; i++) {
   
   
    		String title = arrTitle[i].trim();
    		//在titleMap中查询
    		if (titleMap.containsKey(title)) {
   
   
    			//如果为需要导入的列,加入columnIdxMap中
    			ImportFieldDef item = titleMap.get(title);
    			columnIdxMap.put((Integer)i, item.getFieldName());
    		}else {
   
   
    			//不需要导入的数据列,skip
    		}
    	}
    	
    	//检查必需字段是否都存在
    	//存放缺失的必需字段
    	String missingTitles = "";
    	for(Map.Entry<String,ImportFieldDef> item : titleMap.entrySet()) {
   
   
    		ImportFieldDef fieldItem = item.getValue();
    		if (fieldItem.getMandatory() == 0) {
   
   
    			//可选字段,跳过
    			continue;
    		}
    		boolean bFound = false;
    		for(String subItem : columnIdxMap.values()) {
   
   
    			if(subItem.equals(fieldItem.getFieldName())) {
   
   
    				//找到该字段
    				bFound = true;
    			}
    		}
    		if (!bFound) {
   
   
    			//如果必需字段缺失,加入缺失字段中
    			if(missingTitles.isEmpty()) {
   
   
    				//标题名
    				missingTitles = "数据缺失关键列名 : " + item.getKey();
    			}else 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值