封装Easyexcel工具类导入表格数据存入数据库

本文介绍了如何利用阿里巴巴的EasyExcel库处理Excel文件的导入和导出。通过创建一个工具类`ExcelUtil`,实现了监听器`ExcelListener`,用于读取Excel数据并存储到列表中。在Controller层,接收MultipartFile文件并调用工具类的方法将数据读取到内存,然后保存到数据库。在Postman中可以测试文件上传和导入功能。
部署运行你感兴趣的模型镜像

导入和导出方法类型,但导入需要定义一个工具类继承AnalysisEventListener监听器


一、引入依赖

 
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>3.1.1</version>
        </dependency>

二、编写ExcelUtil

/**
 * Excel表格工具类
 */
@Slf4j
public class ExcelUtil {

	//-------------------------------------------------------------- 导入文件解析监听类 start ----------------------------------------------------

	/**
	 * 读取文件解析监听类
	 * @param <T>
	 */
	public static class ExcelListener<T> extends AnalysisEventListener<T> {

		/**
		 * 存放读取后的数据
		 */
		public List<T> datas = new ArrayList<>();

		/**
		 * 逐条读取数据
		 */
		@Override
		public void invoke(T t, AnalysisContext analysisContext) {
			datas.add(t);
		}

		/**
		 * 解析读取数据总条数
		 */
		@Override
		public void doAfterAllAnalysed(AnalysisContext analysisContext) {
			log.info("读取数据条数:{}条!", datas.size());
		}

		public List<T> getDatas() {
			return this.datas;
		}

	}

	/**
	 * 读取Excel表格返回数据集合,默认读取第一个sheet表数据
	 * @param inputStream  输入流
	 * @param tClass  数据映射类
	 * @param excelListener  读取监听类
	 * @param <T> 泛型
	 * @return List结果集
	 */
	public static <T> List<T> readExcel(InputStream inputStream, Class<T> tClass, ExcelListener<T> excelListener) {
		if (inputStream == null || tClass == null || excelListener == null) {
			return null;
		}
		ExcelReaderBuilder read = EasyExcel.read(inputStream, tClass, excelListener);
		read.sheet().doRead();
		return excelListener.getDatas();
	}

	/**
	 * 读取Excel表格返回数据集合,设置读取第几个sheet表数据
	 * @param inputStream  输入流
	 * @param sheetNo  第X个sheet表
	 * @param tClass  数据映射类
	 * @param excelListener  读取监听类
	 * @param <T> 泛型
	 * @return List结果集
	 */
	public static <T> List<T> readExcel(InputStream inputStream, Integer sheetNo, Class<T> tClass, ExcelListener<T> excelListener) {
		if (inputStream == null || tClass == null || excelListener == null) {
			return null;
		}
		ExcelReaderBuilder read = EasyExcel.read(inputStream, tClass, excelListener);
		if (sheetNo != null) {
			read.sheet(sheetNo).doRead();
		} else {
			ExcelReader excelReader = read.build();
			excelReader.readAll();
			excelReader.finish();
		}
		return excelListener.getDatas();
	}

	//-------------------------------------------------------------- 导入文件解析监听类 end ----------------------------------------------------

}

三、编写接收层controller

RequestParam对应的参数请求类型content-type,可理解为媒体类型,比如文件的上传和下载,spring中专门有个支持的类MultipartFile来支持这种请求。

  @PostMapping("/import")
  public void import(@RequestParam(value = "file") MultipartFile file) {
      service.import(file);
  }

四、在serviceImpl里调用工具类读取表格数据

@Override
 public void import(MultipartFile file) throws IOException {
  //读取上传文件数据转换成
  List<Export> list = ExcelUtil.readExcel(file.getInputStream(), 1, Export.class, new ExcelUtil.ExcelListener<>());
// 拿到list后发送给mapper层存入数据库
  Mapper.insertImport(list);
 }

service层和mapper层省略不写,获取list之后按照批量存入数据库方法写入数据库内

五、在postman中进行导入测试

选择File类型,上传需要导入的文件

 

您可能感兴趣的与本文相关的镜像

LobeChat

LobeChat

AI应用

LobeChat 是一个开源、高性能的聊天机器人框架。支持语音合成、多模态和可扩展插件系统。支持一键式免费部署私人ChatGPT/LLM 网络应用程序。

<think>我们正在讨论如何使用EasyExcel实现多行动态表头的导入。根据之前提供的引用和问题,用户需要的是动态表头导入的实现方法,即表头的行数和列名可能不固定,需要根据实际情况动态生成。从引用[1]中,我们看到一个示例类`ExcelImportUtilOne`,它通过另一个工具类`ExcelImportUtil`来读取Excel文件,并获取表头和数据。这表明在实际项目中,我们可能会封装工具类来处理动态表头的导入。引用[2]展示了一个多级表头导入的示例,其中使用了`headRowNumber(2)`来指定表头的行数(这里是两行),然后读取数据到`UserData`类的对象列表。但用户需要的是动态表头,即表头结构不固定,可能无法预先定义好实体类(如`UserData`)。因此,我们需要使用无模型(Map)的方式读取数据。综合以上,实现动态表头导入的关键步骤:1.**确定表头的行数**:这可能需要用户指定,或者通过程序自动检测(例如,读取Excel前几行直到遇到空行等规则)。2.**构建动态表头结构**:我们可以使用`List<List<String>>`来表示多行表头,每一行是一个字符串列表,表示该行的表头内容。3.**读取数据**:使用EasyExcel的读取功能,并设置表头行数(`headRowNumber`)和自定义表头(如果需要,也可以不设置,直接读取多行表头),然后通过`AnalysisEventListener`逐行读取数据数据将以`Map`的形式出现,其中key为表头的组合(通常是最后一行的表头)或者索引,但为了更好的可读性,我们可以将多行表头组合成一个唯一标识的列名(例如将多行表头用下划线连接)。然而,根据用户的最新问题,他特别强调“动态表头”,即表头可能每次导入都不一样。因此,我们无法预定义实体类,而需要采用无模型的方式读取。下面是一个完整的实现示例,包括如何读取多行动态表头以及将每行数据转换为Map(key为表头组合字符串,value为单元格值):```javaimportcom.alibaba.excel.EasyExcel;importcom.alibaba.excel.context.AnalysisContext;importcom.alibaba.excel.read.listener.ReadListener;importcom.alibaba.excel.util.ListUtils;importjava.io.InputStream;importjava.util.ArrayList;importjava.util.LinkedHashMap;importjava.util.List;importjava.util.Map;publicclassDynamicHeaderImport{publicstaticvoidmain(String[]args){//假设我们有一个输入流(例如从上传的文件获取)InputStreaminputStream=getInputStream("test.xlsx");//假设我们知道表头有3行(动态表头的行数,这里需要根据实际情况确定)intheaderRowNumber=3;List<Map<String,String>>dataList=newArrayList<>();EasyExcel.read(inputStream,newReadListener<Map<Integer,String>>(){//用于保存表头的多行数据privatefinalList<List<String>>headerRows=newArrayList<>();//当前读取的行数(用于判断是否在表头范围内)privateintcurrentRowIndex=0;//最终组合好的表头(最后一行的列位置对应的多行表头组合)privateMap<Integer,String>headerTitles;@Overridepublicvoidinvoke(Map<Integer,String>data,AnalysisContextcontext){//如果还没有构建好最终表头,那么当前行属于表头,将其添加到headerRows中if(currentRowIndex< headerRowNumber){//将当前行转换为表头行(注意:data的key是列的索引,value是单元格值)List<String>rowHeader=newArrayList<>();//我们不知道有多少列,所以按照索引从0开始直到最大非空索引intmaxColIndex=data.keySet().stream().max(Integer::compareTo).orElse(0);for(inti=0;i<=maxColIndex;i++){rowHeader.add(data.getOrDefault(i,""));}headerRows.add(rowHeader);currentRowIndex++;//如果已经收集完所有表头行,构建最终表头if(currentRowIndex==headerRowNumber){buildHeaderTitles();}}else{//表头已经构建完毕,这里处理数据行//将当前行的数据(按列索引)与组合表头(headerTitles)对应起来Map<String,String>rowData=newLinkedHashMap<>();headerTitles.forEach((index,title)->{Stringvalue=data.getOrDefault(index,"");rowData.put(title,value);});dataList.add(rowData);}}privatevoidbuildHeaderTitles(){headerTitles=newLinkedHashMap<>();//假设headerRows中有headerRowNumber行,现在我们要将每一列的多个表头组合起来//遍历每一列(取最后一行的列数作为总列数)intcolCount=headerRows.get(headerRowNumber-1).size();for(intcolIndex=0;colIndex<colCount;colIndex++){//从0行到headerRowNumber-1行,取出每一行的当前列StringBuildersb=newStringBuilder();for(introwIndex=0;rowIndex<headerRowNumber;rowIndex++){//防止某一行该列没有数据if(colIndex< headerRows.get(rowIndex).size()){StringcellValue=headerRows.get(rowIndex).get(colIndex);if(cellValue!=null&&!cellValue.isEmpty()){if(sb.length()>0){sb.append("_");}sb.append(cellValue);}}}headerTitles.put(colIndex,sb.toString());}}@OverridepublicvoiddoAfterAllAnalysed(AnalysisContextcontext){//所有行解析完成System.out.println("解析完成");}}).headRowNumber(0)//设置headRowNumber为0,表示我们自己处理表头和数据.sheet().doRead();//打印读取的数据dataList.forEach(System.out::println);}privatestaticInputStreamgetInputStream(StringfileName){//这里应该返回一个输入流,例如从类路径或者上传的文件//此处仅作示例,实际使用中需要根据情况获取returnDynamicHeaderImport.class.getClassLoader().getResourceAsStream(fileName);}}```###关键点说明:1.**表头行数的确定**:示例中假设表头有3行(`headerRowNumber=3`),实际应用中可能需要用户指定或者通过规则自动判断。2.**读取监听器(ReadListener)**:我们通过实现`ReadListener`接口,并在`invoke`方法中处理每一行的数据。在表头行范围内,我们将每一行保存为表头行。在收集完所有表头行后,我们构建一个从列索引到组合表头字符串(例如:第一行表头、第二行表头等用下划线连接)的映射`headerTitles`。之后的数据行,则根据这个映射将列索引的值转换为列名(组合表头字符串)和值的键值对。3.**表头的组合**:在`buildHeaderTitles`方法中,我们对每一列的多行表头进行组合(用下划线连接),形成唯一的列名。注意,这里需要处理空值,避免出现多余的分隔符。4.**设置headRowNumber**:在读取时,我们设置`.headRowNumber(0)`,表示EasyExcel不会自动将任何行作为表头,而是由我们自己在监听器中进行处理。这样,我们就可以控制表头的读取。5.**数据存储**:我们将每行数据转换成一个Map(key为组合后的表头字符串,value为单元格值),然后添加到一个列表(`dataList`)中,这样整个Excel的数据就以`List<Map<String,String>>`的形式存储了。###注意事项:-这个示例假设表头行是连续的,且出现在数据之前。-对于合并单元格,EasyExcel在读取时会将合并区域的值放在左上角的单元格索引上,其他单元格可能为空。因此,在构建表头组合时,我们需要将合并单元格的值传递到后续行(或列),但上述示例没有处理合并单元格的情况。如果需要处理合并单元格,可能需要使用EasyExcel的额外功能或自定义合并策略。-如果表头非常复杂,可能需要更精细的处理,例如判断每一列是否为空,以及如何处理重复的组合表头(例如,为重复的表头添加后缀)等。###改进建议:-在实际应用中,可以将上述逻辑封装到一个工具类中,类似引用[1]中的`ExcelImportUtil`。-对于表头组合方式(如连接符),可以根据需要调整。-如果表头行数不确定,可以在监听器中加入自动检测表头行结束的规则(例如,遇到空行则认为表头结束)。以上实现可以满足动态表头导入的基本需求,但具体实现细节可能需要根据实际Excel表头结构进行调整。###§§相关问题§§1.如何自动检测Excel表头的行数?2.如何处理EasyExcel读取时遇到的合并单元格?3.如何优化大规模Excel文件的动态表头导入性能?</think> ### 使用 EasyExcel 实现多行动态表头导入 以下是完整的实现步骤和代码示例,基于 EasyExcel 实现多行动态表头导入--- #### 一、核心实现思路 1. **无实体类读取**:当表头动态变化时,使用 `Map<Integer, String>` 接收行数据 2. **动态设置表头行数**:通过 `headRowNumber()` 指定表头行数 3. **表头组合处理**:将多行表头拼接为统一标识(如用下划线连接) 4. **数据过滤**:跳过空表头和无效数据--- #### 二、完整实现代码 ```java import com.alibaba.excel.EasyExcel; import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.read.listener.ReadListener; import java.util.*; import java.util.stream.Collectors; public class DynamicHeaderImporter { // 存储最终数据:表头组合 → 数据列表 private final Map<String, List<String>> result = new LinkedHashMap<>(); private List<List<String>> headerRows = new ArrayList<>(); /** * 执行导入操作 * @param filePath Excel文件路径 * @param headerRowCount 表头行数 */ public void importExcel(String filePath, int headerRowCount) { EasyExcel.read(filePath, new ReadListener<Map<Integer, String>>() { private int currentRowIndex = 0; @Override public void invoke(Map<Integer, String> rowData, AnalysisContext context) { // 前headerRowCount行是表头 if (currentRowIndex < headerRowCount) { processHeaderRow(rowData); } else { processDataRow(rowData); } currentRowIndex++; } @Override public void doAfterAllAnalysed(AnalysisContext context) { System.out.println("导入完成,共读取行数: " + (currentRowIndex - headerRowCount)); } }) .headRowNumber(0) // 禁用默认表头处理 .sheet() .doRead(); } // 处理表头行 private void processHeaderRow(Map<Integer, String> rowData) { List<String> headerRow = new ArrayList<>(); // 按列索引排序后获取表头值 rowData.entrySet().stream() .sorted(Map.Entry.comparingByKey()) .map(Map.Entry::getValue) .filter(value -> value != null && !value.isEmpty()) .forEach(headerRow::add); headerRows.add(headerRow); } // 处理数据行 private void processDataRow(Map<Integer, String> rowData) { // 生成表头组合(列索引 → 多行表头组合) rowData.forEach((colIndex, cellValue) -> { if (colIndex < headerRows.get(0).size()) { String headerKey = generateHeaderKey(colIndex); result.computeIfAbsent(headerKey, k -> new ArrayList<>()) .add(cellValue); } }); } // 生成多行表头组合键(例如:部门_姓名_年龄) private String generateHeaderKey(int colIndex) { return headerRows.stream() .map(row -> row.size() > colIndex ? row.get(colIndex) : "") .filter(str -> !str.isEmpty()) .collect(Collectors.joining("_")); } // 获取导入结果 public Map<String, List<String>> getResult() { return result; } // 测试用例 public static void main(String[] args) { DynamicHeaderImporter importer = new DynamicHeaderImporter(); // 指定表头有3行(根据实际Excel调整) importer.importExcel("动态表头示例.xlsx", 3); // 打印结果 importer.getResult().forEach((header, values) -> System.out.println(header + " → " + values)); } } ``` --- #### 三、关键实现说明 1. **表头处理流程**: - 前 N 行作为表头存入 `headerRows` 列表 - 每列的多行表头通过 `generateHeaderKey()` 拼接 - 示例拼接结果:`基础信息_姓名_主表` 2. **数据存储结构**: ```java Map<String, List<String>> result = { "部门_姓名" : ["张三", "李四", "王五"], "部门_年龄" : ["28", "32", "25"], "考核_绩效分": ["A", "B+", "A"] } ``` 3. **跳过无效值**: ```java .filter(value -> value != null && !value.isEmpty()) ``` 4. **文件读取配置**: ```java .headRowNumber(0) // 禁用默认表头识别 .sheet() // 默认读取第一个Sheet ``` --- #### 四、实际应用场景示例 **Excel表示例**: ``` | 部门 | | 考核项 | |------|------|----------| | 姓名 | 年龄 | 第一季度 | | 张三 | 28 | A | ``` **转换后的数据结构**: ```java { "部门_姓名": ["张三"], "部门_年龄": ["28"], "考核项_第一季度": ["A"] } ``` --- #### 五、最佳实践建议 1. **表头规范化**: - 对空单元格自动填充默认值(如"未命名列1") ```java .map(row -> row.size() > colIndex ? row.get(colIndex) : "未命名列" + colIndex) ``` 2. **大数据量优化**: - 每处理1000行数据批量入库 ```java public void invoke(Map<Integer, String> rowData, AnalysisContext context) { if (dataCount++ % 1000 == 0) { batchSaveToDB(); result.clear(); } } ``` 3. **合并单元格处理**: ```java // 在processHeaderRow中处理合并单元格 if (currentRowIndex == 0) { mergedCellCache.put(colIndex, cellValue); } else if (cellValue.isEmpty()) { cellValue = mergedCellCache.get(colIndex); // 使用缓存值 } ``` --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值