当前位置: 首页 > news >正文

FastExcel + Java:打造高效灵活的Excel数据导入导出解决方案

作者:后端小肥肠

🍇 我写过的文章中的相关代码放到了gitee,地址:xfc-fdw-cloud: 公共解决方案

🍊 有疑问可私信或评论区联系我。

🥑  创作不易未经允许严禁转载。

姊妹篇:

基于AOP的数据字典实现:实现前端下拉框的可配置更新_数据字典下拉框怎么写-CSDN博客

目录

1. 前言

2. Fastexcel介绍

3. 技术实现

3.1. 表结构及实体类说明 

3.2. 适配PostgresSQL中text[]类型handler编写

3.3. 注解编写

3.4. 动态设置下拉框核心工具类编写

 3.5. 制作多选下拉框exel模板

3.6. 导出模板方法 

3.7 导入数据方法

4. 源码地址

5. 结语


1. 前言

在当今的软件开发中,数据的导入与导出是常见的需求,尤其是在企业级应用中,Excel文件作为数据交互的一种重要形式被广泛使用。传统的Excel导入导出功能虽然基本满足需求,但在处理大数据量或需要动态配置时,往往显得效率低下且灵活性不足。

本文将围绕在Java中基于实体类的高效Excel数据导入导出展开,介绍如何利用FastExcel这一库实现高性能和灵活的Excel处理。同时,结合动态下拉框多选下拉框的设置,使得数据的导入导出不仅高效,还能具备较强的可定制性和交互性。通过本篇文章,你将能够掌握在Java中使用实体类驱动Excel导入导出的技术,并学会如何在系统中动态生成下拉框和多选框的配置

2. Fastexcel介绍

FastExcel 是一个高性能的 Java 库,旨在提供高效的 Excel 文件操作,尤其是在处理大数据量时,其性能远超常见的POI库。相比其他 Excel 处理库,FastExcel 采用了更加优化的内存管理和流式处理方式,使得它在内存占用和速度上具有显著优势。尤其在导入导出大量数据时,FastExcel 可以有效避免内存溢出和性能瓶颈,保证程序的稳定运行。

FastExcel 的特点:

  • 高性能: 快速的读取和写入速度,特别适用于大数据量的 Excel 文件。
  • 低内存占用: 采用流式读取和写入的方式,极大地减少了内存的使用。
  • 简洁易用: 相较于其他复杂的Excel操作库,FastExcel提供了简洁的API接口,易于上手。
  • Excel文件格式支持: 支持 .xlsx 格式的文件,且兼容大部分常见的 Excel 文件操作需求。
  • 动态功能扩展: 可以灵活地与 Java 实体类进行绑定,支持动态生成表头、表格内容和格式设置。

为什么选择FastExcel?

在实际开发中,Excel文件的导入导出经常用于大规模的数据交换,尤其是在财务、报表等领域。对于这些场景,传统的 Excel 处理库(如 Apache POI)可能在面对大数据量时会出现性能瓶颈,尤其是在需要频繁进行读写操作的情况下。而FastExcel通过采用流式读取和写入的方式,有效解决了这一问题,并且通过内存管理优化,使得应用能够在处理大量数据时仍保持高效运行。

由于FastExcel的这些优势,它成为了许多Java开发者在实现Excel数据导入导出时的首选工具,尤其是对于需要处理海量数据或需要提高导入导出性能的应用场景。

3. 技术实现

模拟需求:本案例模拟导出学生的相关信息,包括学号、姓名、性别、父母职业类型和家庭住址所属区域等内容。具体来说,学生信息包含以下字段:

  • 学号唯一标识学生的编号,作为数据的主键。
  • 姓名学生的姓名,文本类型字段。
  • 性别此字段通过下拉框进行选择,支持男、女等选项,方便用户快速选择。
  • 父母职业类型此字段也是一个下拉框,列出了多种常见的职业类型,便于系统自动识别父母的职业分类。
  • 家庭住址所属区域该字段设置为多选下拉框,支持学生家庭地址涉及多个区域的情况。例如,假设某些富裕学生的家庭可能在不同城市或区域拥有多个房产,因此可以选择多个区域。此设置充分考虑了复杂的地址情况,提高了数据录入的灵活性。

本文将实现excel文件下拉框基于java程序动态设置,并支持多选下拉框。

动态下拉框的实现步骤如下:

1. 创建支持宏的xlsm模板文件

2. 编写vba代码

3. 基于java代码动态写入下拉框数据

4. 导出动态设置下拉框的excel模板

3.1. 表结构及实体类说明 

1.  PostgreSQL数据库表结构(SQL)

我这里创建了一个比较简单的数据字典表存储下拉框数据,完整版数据字典请移步:基于AOP的数据字典实现:实现前端下拉框的可配置更新_数据字典下拉框怎么写-CSDN博客

CREATE TABLE students (student_id VARCHAR(20) PRIMARY KEY,  -- 学号name VARCHAR(100) NOT NULL,          -- 姓名gender VARCHAR(10),                  -- 性别parent_occupation VARCHAR(100),      -- 父母职业类型home_area TEXT[]                     -- 家庭住址所属区域 (使用数组类型来存储多个区域)
);-- 创建一个独立的字典表,用于存储性别、父母职业类型等下拉框选项
CREATE TABLE gender_options (id SERIAL PRIMARY KEY,gender VARCHAR(10) NOT NULL
);CREATE TABLE parent_occupation_options (id SERIAL PRIMARY KEY,occupation VARCHAR(100) NOT NULL
);-- 插入默认的字典数据
INSERT INTO gender_options (gender) VALUES('男'),('女');INSERT INTO parent_occupation_options (occupation) VALUES('教师'),('医生'),('工程师'),('律师'),('其他');

在student表中插入10条数据:

INSERT INTO students (student_id, name, gender, parent_occupation, home_area)
VALUES
('S10001', '张三', '男', '教师', '{"北京", "上海"}'),
('S10002', '李四', '女', '医生', '{"广州", "深圳"}'),
('S10003', '王五', '男', '工程师', '{"北京"}'),
('S10004', '赵六', '女', '律师', '{"杭州", "南京"}'),
('S10005', '孙七', '男', '商人', '{"上海", "广州"}'),
('S10006', '周八', '女', '公务员', '{"北京", "武汉"}'),
('S10007', '吴九', '男', '教师', '{"成都"}'),
('S10008', '郑十', '女', '护士', '{"重庆", "成都"}'),
('S10009', '冯十一', '男', '程序员', '{"深圳", "上海"}'),
('S10010', '陈十二', '女', '医生', '{"北京", "上海", "广州"}');

2. 实体类

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value="Students对象", description="")
@TableName(value = "students",autoResultMap = true)
public class Students implements Serializable {private static final long serialVersionUID = 1L;@TableId(value = "student_id", type = IdType.ASSIGN_ID)@ExcelProperty(value = "学号",index = 0)private String studentId;@ExcelProperty(value = "姓名",index = 1)private String name;@ExcelProperty(value = "性别",index = 2)@DropDownSetField(source = {"男", "女"})private String gender;@ExcelProperty(value = "父母职业", index = 3)@DropDownSetField(dynamicSource = ParentOccupationOptions.class)private String parentOccupation;@ExcelProperty(value = "所属区域",index = 4,converter = SimpleStringToListConverter.class)@DropDownSetField(source = {"东城区", "西城区", "海淀区", "朝阳区", "丰台区", "石景山区", "门头沟区", "房山区", "通州区", "顺义区", "昌平区", "大兴区", "怀柔区", "平谷区", "密云区", "延庆区"})@TableField(typeHandler = StringArrayTypeHandler.class)private List<String> homeArea;}

3.2. 适配PostgresSQL中text[]类型handler编写

@ConditionalOnClass({BaseTypeHandler.class})
@MappedTypes({List.class})
public class StringArrayTypeHandler extends BaseTypeHandler<List<String>> {@Overridepublic void setNonNullParameter(PreparedStatement ps, int i, List<String> parameter, JdbcType jdbcType)throws SQLException {Connection conn = ps.getConnection();Array array = conn.createArrayOf("text", parameter.toArray(new String[0]));ps.setArray(i, array);}@Overridepublic List<String> getNullableResult(ResultSet rs, String columnName) throws SQLException {Array array = rs.getArray(columnName);return array == null ? null : Arrays.asList((String[]) array.getArray());}@Overridepublic List<String> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {Array array = rs.getArray(columnIndex);return array == null ? null : Arrays.asList((String[]) array.getArray());}@Overridepublic List<String> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {Array array = cs.getArray(columnIndex);return array == null ? null : Arrays.asList((String[]) array.getArray());}
}

这个类是一个 MyBatis 的类型处理器(TypeHandler),主要功能是:

  • 数据转换:实现 PostgreSQL 数据库中的数组类型与 Java 中的 List<String> 类型之间的双向转换
  • Java -> DB: List<String> 转换为 PostgreSQL 的 text[] 数组类型
  • DB -> Java:PostgreSQL  text[] 数组类型转换为 List<String>
  • 应用场景:适用于需要在单个字段中存储多个值的情况,如学生所属区域(可以属于多个区域)的存储和读取
  • 技术特点:
  • 继承自 BaseTypeHandler<List<String>>
  • 使用 @MappedTypes 注解指定处理 List 类型
  • 使用 @ConditionalOnClass 实现条件化配置

3.3. 注解编写

@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DropDownSetField {String[] source() default {};String value() default "";Class<?>[] dynamicSource() default {};
}

3.4. 动态设置下拉框核心工具类编写

@Component
@Slf4j
public class ExchangeSheetUtils {@Autowiredprivate IDropDownDataService dropDownDataService;private static final int MAX_EXCEL_ROWS = 65536;private static final String HIDDEN_SHEET_NAME = "字典sheet";private final char[] alphabet = new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L','M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'};// 使用ThreadLocal来确保线程安全private final ThreadLocal<List<String>> dropDownArrays = ThreadLocal.withInitial(ArrayList::new);private final ThreadLocal<Map<Integer, List<String>>> dropDownMap = ThreadLocal.withInitial(HashMap::new);// 在方法结束时清理ThreadLocalpublic void clearThreadLocals() {dropDownArrays.get().clear();dropDownMap.get().clear();}/*** 根据实体类解析字段,并获取动态或固定的下拉数据。*/public void getEntityField(Class<?> clazz) {try {Field[] fields = clazz.getDeclaredFields();for (Field field : fields) {processDropDownField(field);}} catch (Exception e) {log.error("处理实体类字段失败", e);clearThreadLocals();throw new RuntimeException("处理实体类字段失败", e);}}/*** 处理下拉框字段*/private void processDropDownField(Field field) {ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);if (excelProperty == null || excelProperty.value().length == 0) {log.warn("字段 {} 缺少 ExcelProperty 注解或 value 为空", field.getName());return;}String columnName = excelProperty.value()[0];dropDownArrays.get().add(columnName);DropDownSetField dropDownSetField = field.getAnnotation(DropDownSetField.class);if (dropDownSetField != null) {List<String> dropDownOptions = new ArrayList<>();if (dropDownSetField.dynamicSource().length > 0) {dropDownOptions = getDropDownDataFromDynamicSource(dropDownSetField.dynamicSource());} else if (dropDownSetField.source().length > 0) {dropDownOptions = Arrays.asList(dropDownSetField.source());}if (!dropDownOptions.isEmpty()) {int columnIndex = dropDownArrays.get().size() - 1;dropDownMap.get().put(columnIndex, dropDownOptions);}}}/*** 从动态数据源获取下拉数据*/private List<String> getDropDownDataFromDynamicSource(Class<?>[] dynamicSourceClasses) {List<String> dropDownOptions = new ArrayList<>();for (Class<?> dynamicSourceClass : dynamicSourceClasses) {try {// 调用动态数据源的接口获取下拉数据(例如通过远程接口)List<String> data = dropDownDataService.fetchDynamicDropDownData(dynamicSourceClass);dropDownOptions.addAll(data);} catch (Exception e) {log.error("获取动态下拉框数据失败,错误信息: {}", e.getMessage());}}return dropDownOptions;}/*** 创建并更新隐藏Sheet页,添加下拉框*/public void updateHiddenSheet(Sheet curSheet, Workbook templateWorkbook) {if (dropDownMap.get().isEmpty()) {return; // 如果没有下拉框数据则不进行处理}DataValidationHelper helper = curSheet.getDataValidationHelper();String hiddenSheetName = HIDDEN_SHEET_NAME;Sheet hiddenSheet = templateWorkbook.createSheet(hiddenSheetName);hideOtherSheets(templateWorkbook);clearOldNamedRanges(templateWorkbook);// 填充隐藏Sheet的数据Set<Map.Entry<Integer, List<String>>> entrySet = dropDownMap.get().entrySet();for (Map.Entry<Integer, List<String>> entry : entrySet) {createDropDownList(helper, hiddenSheet, entry);}}/*** 隐藏所有除第一个外的Sheet*/private void hideOtherSheets(Workbook templateWorkbook) {int totalSheets = templateWorkbook.getNumberOfSheets();for (int i = 1; i < totalSheets; i++) {templateWorkbook.setSheetHidden(i, true);}}/*** 清除之前的命名范围*/private void clearOldNamedRanges(Workbook templateWorkbook) {for (int i = 0; i < 26; i++) {Name workbookName = templateWorkbook.getName("dict" + i);if (workbookName != null) {templateWorkbook.removeName(workbookName); // 使用 Name 对象删除}}}/*** 创建并配置下拉框*/private void createDropDownList(DataValidationHelper helper, Sheet hiddenSheet, Map.Entry<Integer, List<String>> entry) {Integer column = entry.getKey();List<String> values = entry.getValue();// 填充数据到隐藏sheetint rowLen = values.size();for (int i = 0; i < rowLen; i++) {Row row = hiddenSheet.getRow(i);if (row == null) {row = hiddenSheet.createRow(i);}Cell cell = row.createCell(column);cell.setCellValue(values.get(i));}String excelColumn = getExcelColumn(column);String refersTo = HIDDEN_SHEET_NAME + "!$" + excelColumn + "$1:$" + excelColumn + "$" + rowLen;// 创建命名范围Name name = hiddenSheet.getWorkbook().createName();name.setNameName("dict" + column);name.setRefersToFormula(refersTo);// 获取第一个sheet(主sheet)Sheet mainSheet = hiddenSheet.getWorkbook().getSheetAt(0);// 创建数据验证DataValidationConstraint constraint = helper.createFormulaListConstraint("dict" + column);CellRangeAddressList addressList = new CellRangeAddressList(1, MAX_EXCEL_ROWS, column, column);DataValidation validation = helper.createValidation(constraint, addressList);// 设置验证属性validation.setSuppressDropDownArrow(true);validation.setShowErrorBox(true);validation.setErrorStyle(DataValidation.ErrorStyle.STOP);validation.createErrorBox("提示", "此值与单元格定义格式不一致!");// 将验证添加到主sheetmainSheet.addValidationData(validation);}/*** 将数字列转化为字母列*/private String getExcelColumn(int num) {int len = alphabet.length;int first = num / len;int second = num % len;if (num < len) {return String.valueOf(alphabet[num]);} else {return String.valueOf(alphabet[first - 1]) + alphabet[second - 1];}}/*** 设置数据Sheet页的初始化*/public void setDataSheet(Sheet sheet, Workbook templateWorkbook) {Row row = sheet.createRow(0);List<String> arrays = dropDownArrays.get();for (int i = 0; i < arrays.size(); i++) {row.createCell(i).setCellValue(arrays.get(i));}}}

ExchangeSheetUtils 核心方法说明

1. getEntityField

public void getEntityField(Class<?> clazz)
  • 功能:解析实体类的字段注解,收集下拉框配置信息
  • 处理流程
    • 获取类的所有字段
    • 通过 processDropDownField 处理每个字段的注解
    • 将下拉框数据存入 ThreadLocal

2. processDropDownField

private void processDropDownField(Field field)
  • 功能:处理单个字段的下拉框配置
  • 处理流程
    • 读取 @ExcelProperty 注解获取列名
    • 读取 @DropDownSetField 注解获取下拉选项
    • 将数据保存到 dropDownArrays 和 dropDownMap

3. updateHiddenSheet

public void updateHiddenSheet(Sheet curSheet, Workbook templateWorkbook)
  • 功能:创建和配置隐藏的数据字典sheet
  • 处理流程
    • 创建隐藏sheet
    • 清理旧的命名范围
    • 通过 createDropDownList 设置下拉框

4. createDropDownList

private void createDropDownList(DataValidationHelper helper, Sheet hiddenSheet, Map.Entry<Integer, List<String>> entry)
  • 功能:创建Excel下拉框
  • 处理流程
    • 在隐藏sheet中填充下拉选项
    • 创建命名范围(Named Range)
    • 设置数据验证规则
    • 配置下拉框和错误提示

这些方法通过 ThreadLocal 实现线程安全,通过 POI 提供的 API 实现 Excel 的各种操作,最终生成一个带有下拉框的 Excel 模板文件。

 3.5. 制作多选下拉框exel模板

1. 新建.xlsx文件

2. 点击顶部【文件】后点击【选项】

3. 在弹出的弹窗中,点击【信任中心】选项页中的【信任中心设置】按钮

4.  开启宏

5. 另存为.xlsm文件 

6. 编写VBA代码

选中Sheet,右键弹出菜单,选择【查看代码】,将下面代码粘进去

Sub Worksheet_Change(ByVal Target As Range)' 让数据有效性选择可以多选,且不可重复Dim rngDV As RangeDim oldVal As StringDim newVal As String' 如果修改的范围超过1个单元格,则退出If Target.Count > 1 Then GoTo exitHandlerOn Error Resume NextSet rngDV = Cells.SpecialCells(xlCellTypeAllValidation)On Error GoTo exitHandlerIf rngDV Is Nothing Then GoTo exitHandlerIf Intersect(Target, rngDV) Is Nothing Then' 如果目标单元格不在数据验证区域,什么都不做ElseApplication.EnableEvents = FalsenewVal = Target.Value' 假设字段映射如下:' 第3列是 "gender" (性别)' 第4列是 "parentOccupation" (父母职业类型)' 第5列是 "homeArea" (家庭住址所属区域)' 如果修改的是 "gender" 或 "parentOccupation",则是单选,直接替换' 如果修改的是 "homeArea",则是多选,去重并追加If Target.Column = 3 Or Target.Column = 4 Then' 对性别(gender)和父母职业类型(parentOccupation)做单选处理Application.UndooldVal = Target.ValueTarget.Value = newVal' 如果原值与新值不同,直接替换If oldVal <> newVal ThenTarget.Value = newValEnd IfElseIf Target.Column = 5 Then' 对家庭住址所属区域(homeArea)做多选处理Application.UndooldVal = Target.ValueTarget.Value = newValIf oldVal = "" Then' 如果原值为空,直接返回ElseIf newVal = "" Then' 如果新值为空,什么都不做Else' 去除重复项If InStr(1, oldVal, newVal) <> 0 Then' 如果新值在旧值中已存在If InStr(1, oldVal, newVal) + Len(newVal) - 1 = Len(oldVal) Then' 如果是最后一个选项重复,则删除Target.Value = Left(oldVal, Len(oldVal) - Len(newVal) - 1)Else' 否则删除逗号后面的重复值Target.Value = Replace(oldVal, newVal & ",", "")End IfElse' 如果是新选项,则追加Target.Value = oldVal & "," & newValEnd IfEnd IfEnd IfEnd IfEnd IfexitHandler:Application.EnableEvents = True
End Sub

这是一个 Excel 工作表的 Worksheet_Change 事件处理程序,主要实现了单元格数据验证的自定义处理逻辑:

  • 功能目标:实现单选和多选下拉框的不同处理逻辑
  • 具体实现:
  • 第3列(性别)和第4列(父母职业)实现单选功能,新值直接替换旧值
  • 第5列(所属区域)实现多选功能:
  • 允许多个选项,用逗号分隔
  • 自动去重(避免重复选择)
  • 支持取消选择(点击已选项可移除)
  • 使用 Application.Undo 和 Application.EnableEvents 确保操作的原子性和避免事件循环

该代码通过 VBA 扩展了 Excel 默认的下拉框功能,使其支持更复杂的业务需求,特别是实现了多选下拉框的去重和动态更新功能。

将上述步骤保存,支持动态下拉框的模板文件(.xlsm)就制作完成了。

3.6. 导出模板方法 

    public void exportTemplate(HttpServletResponse response) {Workbook templateWorkbook = null;FileInputStream fileInputStream = null;try {// 设置响应头response.setContentType("application/vnd.ms-excel.sheet.macroEnabled.12");response.setCharacterEncoding("utf-8");String name = "学生数据模板";response.setHeader("Content-Disposition", "attachment; filename=" +java.net.URLEncoder.encode(name, "UTF-8") + ".xlsm");// 读取模板文件File file = new File("D:/学生数据模板.xlsm");fileInputStream = new FileInputStream(file);templateWorkbook = WorkbookFactory.create(fileInputStream);// 获取数据 - 这三个方法的调用顺序不能变exchangeSheetUtils.getEntityField(Students.class);Sheet outputSheet = templateWorkbook.getSheetAt(0);templateWorkbook.setSheetName(0, name);exchangeSheetUtils.updateHiddenSheet(outputSheet, templateWorkbook);exchangeSheetUtils.setDataSheet(outputSheet, templateWorkbook);// 输出文件templateWorkbook.write(response.getOutputStream());} catch (Exception e) {log.error("导出学生模板失败:"+e.getMessage(), e);throw new RuntimeException("导出模板失败", e);} finally {// 清理 ThreadLocal 资源exchangeSheetUtils.clearThreadLocals();// 关闭其他资源if (fileInputStream != null) {try {fileInputStream.close();} catch (IOException e) {log.error("关闭文件流失败", e);}}if (templateWorkbook != null) {try {templateWorkbook.close();} catch (IOException e) {log.error("关闭工作簿失败", e);}}}}

这是一个用于导出 Excel 模板文件的方法,其核心功能是:

读取预设的 Excel 模板文件(D:/student.xlsm),通过 ExchangeSheetUtils 工具类解析 Students 实体类的注解信息(@ExcelProperty 和 @DropDownSetField),设置下拉框和数据验证,最后将处理好的模板文件(包含表头、下拉框配置和 VBA 代码)以 .xlsm 格式输出到 HTTP 响应流中。整个过程包含了完整的资源管理(使用 try-finally 确保资源正确关闭)和线程安全处理(通过 clearThreadLocals 清理 ThreadLocal 资源)。

关键步骤:

  1. 设置响应头(.xlsm 格式)
  2. 读取模板文件
  3. 处理下拉框配置
  4. 输出文件
  5. 清理资源

3.7 导入数据方法

    @Override@Transactional(rollbackFor = Exception.class)public String importData(MultipartFile file) throws IOException {try {final List<Students> studentsList = new ArrayList<>();// 使用Map方式读取数据EasyExcel.read(file.getInputStream()).sheet(0).headRowNumber(1)  // 将表头行设置为1,因为第0行是表头.registerReadListener(new AnalysisEventListener<Map<Integer, String>>() {@Overridepublic void invoke(Map<Integer, String> data, AnalysisContext context) {log.info("读取到一行数据: {}", JSON.toJSONString(data));// 手动转换为Students对象,使用正确的keyStudents student = new Students();student.setStudentId(data.get(0));      // 学号student.setName(data.get(1));           // 姓名student.setGender(data.get(2));         // 性别student.setParentOccupation(data.get(3));  // 家长职业// 使用与SimpleStringToListConverter相同的逻辑处理homeAreaString areaStr = data.get(4);if (areaStr != null && !areaStr.trim().isEmpty()) {student.setHomeArea(Arrays.asList(areaStr.split(",")));}studentsList.add(student);}@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {log.info("所有数据解析完成!共读取到 {} 条数据", studentsList.size());}}).doRead();if (CollectionUtils.isEmpty(studentsList)) {return "Excel中没有数据";}// 保存数据this.saveBatch(studentsList);return "导入成功,共导入 " + studentsList.size() + " 条数据";} catch (Exception e) {log.error("导入失败:", e);throw e;}}

这个 importData 函数的主要功能是导入Excel文件中的学生数据。具体流程如下:

  • 使用 @Transactional 注解确保数据导入的事务性,如果出现异常会自动回滚
  • 创建一个 studentsList 列表用于存储解析后的数据
  • 使用 EasyExcel 读取上传的 Excel 文件:
  • 读取第一个 sheet(sheet(0))
  • 设置表头行号为1(headRowNumber(1))
  • 使用 Map 方式读取数据,其中 key 是列索引(0-4),value 是单元格内容
  • 在 invoke 方法中处理每一行数据:
  • Map 数据手动转换为 Students 对象
  • 特别处理 homeArea 字段,将字符串用逗号分割转换为 List
  • 最后批量保存数据到数据库(saveBatch
  • 如果过程中出现异常,会记录错误日志并抛出异常触发事务回滚

4. 源码地址

源码里面有导出模板,导入数据和导出数据三个接口,实现了功能闭环,完整代码:

xfc-fdw-cloud: 公共解决方案

5. 结语

本文介绍了如何通过 FastExcel 实现高效的 Excel 数据导入导出,基于实体类动态设置excel下拉框(支持多选),解决了实际开发中的常见需求。如有疑问,欢迎在评论区留言,我看到都会回复。

相关文章:

FastExcel + Java:打造高效灵活的Excel数据导入导出解决方案

作者&#xff1a;后端小肥肠 &#x1f347; 我写过的文章中的相关代码放到了gitee&#xff0c;地址&#xff1a;xfc-fdw-cloud: 公共解决方案 &#x1f34a; 有疑问可私信或评论区联系我。 &#x1f951; 创作不易未经允许严禁转载。 姊妹篇&#xff1a; 基于AOP的数据字典实现…...

fun-transformer学习笔记-Task1——Transformer、Seq2Seq、Encoder-Decoder、Attention之间的关系

Transformer、Seq2Seq、Encoder-Decoder、Attention由这四者之间的关系可以从模型架构的发展脉络来理解&#xff1a; Seq2Seq 与 Encoder–Decoder 模型 “Seq2Seq”&#xff08;sequence‐to‐sequence&#xff09;是一类用于将一个变长序列映射为另一个变长序列的任务&#x…...

使用Hexo部署NexT主体网站

一.使用git提交文件 参考&#xff1a; 从零开始搭建个人博客&#xff08;超详细&#xff09; - 知乎 致谢&#xff01; 第一种&#xff1a;本地没有 git 仓库 直接将远程仓库 clone 到本地&#xff1b;将文件添加并 commit 到本地仓库&#xff1b;将本地仓库的内容push到远程仓…...

图书管理项目(spring boot + Vue)

想要该项目的话&#xff0c;就 jia 我&#xff0c;并在评论区给我说一下&#xff0c;只需要1元&#xff0c;我把整个项目发给你 jia微&#xff1a;18439421203&#xff08;名字叫&#xff1a;Bingo&#xff09; 运行图片&#xff1a;...

python实现常见数学概率分布

常见正态分布 1.贝塔分布1.1 概率密度函数1.2参数对分布形状的影响1.3 应用场景1.4 python实现 2. 帕累托分布&#xff08;80/20法则&#xff09;3. 正态分布&#xff08;高斯分布&#xff09;3.1 正态分布对应性质3.2 正态分布对应图像![在这里插入图片描述](https://i-blog.c…...

解决Blender无法识别Num关闭状态下的笔记本数字键盘中Home键、End键问题

问题描述&#xff1a; 在笔记本电脑上&#xff0c;多少会缺少一些按钮&#xff0c;例如“Home”、“End”、“PgUp”、“PgDn”&#xff0c;它们在笔记本电脑上的作用是&#xff0c;如果关闭Num&#xff0c;则可以从数字键盘访问这些按钮。但问题是在Blender中&#xff0c;不论…...

React 高级教程

使用 React 高级组件&#xff08;HOC&#xff09;实现的完整项目示例&#xff0c;包含权限控制、数据加载状态处理、性能优化等常见高级功能。创建一个简单的博客系统: // 项目结构&#xff1a; src/ |-- components/ | |-- ArticleList.jsx | |-- Article.jsx | |-- He…...

基于Qt 和微信小程序的用户管理系统:WebSocket + SQLite 实现注册与登录

目录 一. 概要 二. 技术栈 三. 系统功能设计 3.1 功能模块 3.2 数据表设计 四. 具体实现 4.1 Qt 服务端 4.1.1 初始化 WebSocket 服务器 4.1.2 用户管理界面 4.2 微信小程序端 4.2.1 注册功能 4.2.2 登录功能 五. 运行效果 六. 源码下载 一. 概要 在物联网和智能设备…...

在CT107D单片机综合训练平台上实现外部中断控制LED闪烁

引言 在单片机开发中&#xff0c;外部中断是一个非常重要的功能&#xff0c;它可以让单片机在检测到外部信号变化时立即做出响应。本文将详细介绍如何在CT107D单片机综合训练平台上使用外部中断来控制LED灯的闪烁。我们将使用两种不同的方式来实现这一功能&#xff1a;一种是在…...

HTML之JavaScript使用JSON

HTML之JavaScript使用JSON JSON(JavaScript Object Notation)是一种轻量级的数据交换格式&#xff0c;易于人阅读和编写&#xff0c;同时也易于机器解析和生成。JSON是JavaScript对象的字符串表示法&#xff0c;它使用文本表示一个js对象的信息&#xff0c;可以将json字符串转换…...

算法很美笔记(Java)——树

性质 树 上面的性质因为两个结点由一条边连成 结点数目越多&#xff0c;算法复杂度越高 二叉树 结构 层次遍历 利用队列&#xff0c;弹一个&#xff0c;加N个&#xff08;队列里弹出一个元素&#xff0c;就把这个元素的所有孩子加进去&#xff09; 具体来说&#xff1a;指…...

SQL面试题4:相互关注问题

引言 在社交媒体和各类社区平台蓬勃发展的当下&#xff0c;用户之间的关系网络成为了平台运营和数据分析的关键部分。相互关注作为一种重要的社交关系&#xff0c;不仅反映了用户之间的紧密程度&#xff0c;还对平台的社交生态、内容传播等方面有着深远影响。本文将聚焦于 SQL…...

ArcGIS基础知识之ArcMap基础设置——ArcMap选项:常规选项卡设置及作用

作为一名 GIS 从业者,ArcMap 是我们日常工作中不可或缺的工具。对于初学者来说,掌握 ArcMap 的基础设置是迈向 GIS 分析与制图的第一步。今天,就让我们一起深入了解 ArcMap 选项中常规选项卡的各个设置,帮助大家更好地使用这款强大的软件。 在 ArcMap 中,常规选项卡是用户…...

jvm 线程监控调试

文章目录 前言一、使用JDK工具转储线程文件(如jstack)1. 找到Java进程的PID:2. 使用jstack生成线程转储文件:3.验证生成的线程转储文件:二、分析文件1.使用在线工具进行分析上传thread-dump文件,等待解析完成2.查看分析结果总结前言 提示:使用jdk自带工具转储线程监控文…...

25、深度学习-自学之路-卷积神经网络基于MNIST数据集的程序展示

import keras #添加Keraskuimport sys,numpy as np from keras.utils import np_utilsimport osfrom keras.datasets import mnist print("licheng&#xff1a;""20"\n) np.random.seed(1)(x_train,y_train),(x_test,y_test) mnist.load_data() #第一次…...

【C++】解锁<list>的正确姿势

> &#x1f343; 本系列为初阶C的内容&#xff0c;如果感兴趣&#xff0c;欢迎订阅&#x1f6a9; > &#x1f38a;个人主页:[小编的个人主页])小编的个人主页 > &#x1f380; &#x1f389;欢迎大家点赞&#x1f44d;收藏⭐文章 > ✌️ &#x1f91e; &#x1…...

Qt中的事件

写一个 可以拖动的按钮 DraggablePushButton.h 头文件 #ifndef DRAGGABLEPUSHBUTTON_H #define DRAGGABLEPUSHBUTTON_H#include <QPushButton> #include <QMouseEvent>class DraggablePushButton : public QPushButton {Q_OBJECTpublic:explicit DraggablePushBu…...

变化检测相关论文可读list

一些用得上的&#xff1a; 遥感变化检测常见数据集https://github.com/rsdler/Remote-Sensing-Change-Detection-Dataset/ 代码解读&#xff1a;代码解读 | 极简代码遥感语义分割&#xff0c;结合GDAL从零实现&#xff0c;以U-Net和建筑物提取为例 NeurIPS2024: https://mp.w…...

Ansible中playbook的变量

变量 playbook的变量有以下几种 在playbook中用户自定义的变量远程主机中由Ansible收集的变量在文件模板中使用的上述两种变量把任务结果作为一个变量使用&#xff0c;叫注册变量用户在执行playbook时&#xff0c;通过命令行传入的变量&#xff0c;叫做额外变量 在playbook中…...

亚信安全正式接入DeepSeek

亚信安全致力于“数据驱动、AI原生”战略&#xff0c;早在2024年5月&#xff0c;推出了“信立方”安全大模型、安全MaaS平台和一系列安全智能体&#xff0c;为网络安全运营、网络安全检测提供AI技术能力。自2024年12月DeepSeek-V3发布以来&#xff0c;亚信安全人工智能实验室利…...

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…...

如何为服务器生成TLS证书

TLS&#xff08;Transport Layer Security&#xff09;证书是确保网络通信安全的重要手段&#xff0c;它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书&#xff0c;可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...

RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程

本文较长&#xff0c;建议点赞收藏&#xff0c;以免遗失。更多AI大模型应用开发学习视频及资料&#xff0c;尽在聚客AI学院。 本文全面剖析RNN核心原理&#xff0c;深入讲解梯度消失/爆炸问题&#xff0c;并通过LSTM/GRU结构实现解决方案&#xff0c;提供时间序列预测和文本生成…...

短视频矩阵系统文案创作功能开发实践,定制化开发

在短视频行业迅猛发展的当下&#xff0c;企业和个人创作者为了扩大影响力、提升传播效果&#xff0c;纷纷采用短视频矩阵运营策略&#xff0c;同时管理多个平台、多个账号的内容发布。然而&#xff0c;频繁的文案创作需求让运营者疲于应对&#xff0c;如何高效产出高质量文案成…...

Netty从入门到进阶(二)

二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架&#xff0c;用于…...

MySQL 部分重点知识篇

一、数据库对象 1. 主键 定义 &#xff1a;主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 &#xff1a;确保数据的完整性&#xff0c;便于数据的查询和管理。 示例 &#xff1a;在学生信息表中&#xff0c;学号可以作为主键&#xff…...

探索Selenium:自动化测试的神奇钥匙

目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...

鸿蒙(HarmonyOS5)实现跳一跳小游戏

下面我将介绍如何使用鸿蒙的ArkUI框架&#xff0c;实现一个简单的跳一跳小游戏。 1. 项目结构 src/main/ets/ ├── MainAbility │ ├── pages │ │ ├── Index.ets // 主页面 │ │ └── GamePage.ets // 游戏页面 │ └── model │ …...

使用SSE解决获取状态不一致问题

使用SSE解决获取状态不一致问题 1. 问题描述2. SSE介绍2.1 SSE 的工作原理2.2 SSE 的事件格式规范2.3 SSE与其他技术对比2.4 SSE 的优缺点 3. 实战代码 1. 问题描述 目前做的一个功能是上传多个文件&#xff0c;这个上传文件是整体功能的一部分&#xff0c;文件在上传的过程中…...

Android写一个捕获全局异常的工具类

项目开发和实际运行过程中难免会遇到异常发生&#xff0c;系统提供了一个可以捕获全局异常的工具Uncaughtexceptionhandler&#xff0c;它是Thread的子类&#xff08;就是package java.lang;里线程的Thread&#xff09;。本文将利用它将设备信息、报错信息以及错误的发生时间都…...