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

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的数据字典实现…...

在Vue中,JavaScript数组常用方法,添加,插入,查找,删除等整理

在Vue中&#xff0c;JavaScript数组常用&#xff0c;添加&#xff0c;插入&#xff0c;查找&#xff0c;删除等整理 1.splice()方法可以直接修改原数组&#xff0c;通过指定要删除元素的索引来删除它。 例&#xff1a; let index // 要删除的元素的索引; this.array.splice(i…...

vue知识点2

1.methods和mounted的区别 methods是定义方法&#xff0c;不涉及到调用 mounted涉及到操作 所以methods后面是&#xff1a;&#xff0c;mounted后面是&#xff08;&#xff09; 2.介绍一下emit的用法 如果子控件要调用父页面的方法&#xff0c;在父页面的子控件引用处&…...

node.js + html调用ChatGPTApi实现Ai网站demo(带源码)

文章目录 前言一、demo演示二、node.js 使用步骤1.引入库2.引入包 前端HTML调用接口和UI所有文件总结 前言 关注博主&#xff0c;学习每天一个小demo 今天是Ai对话网站 又到了每天一个小demo的时候咯&#xff0c;前面我写了多人实时对话demo、和视频转换demo&#xff0c;今天…...

14.Python生成器、迭代器、闭包、装饰器、元类、垃圾回收、内建函数

在 Python 中&#xff0c;生成器、迭代器、闭包、装饰器、元类、垃圾回收和内建函数是一些重要的概念和功能&#xff0c;它们对于编写高效、灵活的代码非常重要。下面我们逐一详细介绍这些概念及其用法。 1. 生成器&#xff08;Generator&#xff09; 生成器是一个函数&#…...

STM32+Proteus+DS18B20数码管仿真实验

1. 实验准备 硬件方面&#xff1a; 了解 STM32 单片机的基本原理和使用方法&#xff0c;本实验可选用常见的 STM32F103 系列。熟悉 DS18B20 温度传感器的工作原理和通信协议&#xff08;单总线协议&#xff09;。数码管可选用共阴极或共阳极数码管&#xff0c;用于显示温度值。…...

Vulhub靶机 ActiveMQ 反序列化漏洞(CVE-2015-5254)(渗透测试详解)

一、开启vulhub环境 docker-compose up -d 启动 docker ps 查看开放的端口 漏洞版本&#xff1a;Apache ActiveMQ 5.x ~ Apache ActiveMQ 5.13.0 二、访问靶机IP 8161端口 默认账户密码 admin/admin&#xff0c;登录 此时qucues事件为空 1、使用jmet-0.1.0-all.jar工具将…...

ConcurrentHashMap扩容

目录 一、tryPreSize方法-初始化数组 二、tryPreSize方法-扩容标识戳 三、transfer方法-构建新数组 四、transfer方法-迁移数据 五、transfer方法-lastRun机制 六、helpTransfer方法-协助扩容 三种触发方式 达到了扩容的阈值 一、tryPreSize方法-初始化数组 // 扩容前…...

2025年二级建造师报名流程图解

2025年二级建造师报名时间&#xff01;附报名流程&#xff01; ⏰️已公布25年二建考试时间的省份如下&#xff1a; ️4月19日、20日考试的城市有&#xff1a;贵州 ️5月10日、11日考试的城市有&#xff1a;湖北、陕西、宁夏、甘肃、福建、浙江、江西、黑龙江、河南、湖南、…...

【自学笔记】人工智能基础知识点总览-持续更新

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 人工智能重点知识点总览一、基础概念与原理1.1 人工智能定义与发展1.2 算法与数据结构1.3 数学基础 二、机器学习2.1 监督学习2.2 无监督学习2.3 强化学习 三、深度…...

hexo 魔改 | 修改卡片透明度

hexo 魔改 | 修改卡片透明度 ** 博客食物用更佳 博客地址 ** 这是笔者自己瞎倒腾的。作为前端菜鸡一枚&#xff0c;大佬们随便看看就好~ 我用的主题是 butterfly 4.12.0 分析 通过开发者工具可以看出来卡片的背景和 --card-bg 变量有关 再在 sources 下的 css 文件夹下的…...

Golang的并发编程案例详解

Golang的并发编程案例详解 一、并发编程概述 并发编程是指程序中有多个独立的执行线索&#xff0c;并且这些线索在时间上是重叠的。在 Golang 中&#xff0c;并发是其核心特性之一&#xff0c;通过 goroutine 和 channel 来支持并发编程&#xff0c;使得程序可以更高效地利用计…...

【升级】阿里云对象存储 HTTPS 根证书升级公告

--时间打败一切...

贪心算法与动态规划的区别

贪心算法&#xff1a;每一步都选择当前最优解&#xff0c;期望通过局部最优达到全局最优。 动态规划&#xff1a;通过分解问题为子问题&#xff0c;存储并重用子问题的解&#xff0c;避免重复计算。 最简单的JS ACM代码举例 贪心算法&#xff1a;找零问题 function greed…...

策略模式-小结

总结一下看到的策略模式&#xff1a; A:一个含有一个方法的接口 B:具体的实行方式行为1,2,3&#xff0c;实现上面的接口。 C:一个环境类&#xff08;或者上下文类&#xff09;&#xff0c;形式可以是&#xff1a;工厂模式&#xff0c;构造器注入模式&#xff0c;枚举模式。 …...

TDengine 性能测试工具 taosBenchmark

简介工具获取运行 无参数模式命令行模式配置文件模式 命令行参数配置文件参数 通用配置参数写入配置参数 数据库相关超级表相关标签列与数据列写入行为相关 查询配置参数 执行指定查询语句查询超级表 订阅配置参数数据类型对照表 配置文件示例 写入 JSON 示例查询 JSON 示例订阅…...

硬件学习笔记--41 电磁兼容试验-5 射频场感应的传导干扰试验介绍

目录 电磁兼容试验-射频场感应的传导干扰试验介绍 1.试验目的 2.试验方法 3.判定依据及意义 电磁兼容试验-射频场感应的传导干扰试验介绍 驻留时间是在规定频率下影响量施加的持续时间。被试设备&#xff08;EUT&#xff09;在经受扫频频带的电磁影响量或电磁干扰的情况下&a…...

泛型 类 接口 方法 通配符

泛型 泛型类 what: 类型参数化 why use&#xff1a; 1. 输出时候是object类型 而不是真正类型转化麻烦 import java.util.ArrayList; import java.util.List;public class ObjectExample {public static void main(String[] args) {List<Object> list new ArrayLi…...

文字转语音(三)FreeTTS实现

项目中有相关的功能&#xff0c;就简单研究了一下。 说明 FreeTTS 是一个基于 Java 的开源文本转语音&#xff08;TTS&#xff09;引擎&#xff0c;旨在将文字内容转换为自然语音输出。 FreeTTS 适合对 英文语音质量要求低、预算有限且需要离线运行 的场景&#xff0c;但若需…...

STM32 RTC 实时时钟说明

目录 背景 RTC(实时时钟)和后备寄存器 32.768HZ 如何产生1S定时 RTC配置程序 第一次上电RTC配置 第1步、启用备用寄存器外设时钟和PWR外设时钟 第2步、使能RTC和备份寄存器访问 第3步、备份寄存器初始化 第4步、开启LSE 第5步、等待LSE启动后稳定状态 第6步、配置LSE为…...

Open-R1 项目代码文件的详细剖析

目录 1. configs.py 功能概述 关键代码与细节 2. evaluate.py 功能概述 关键代码与细节 3. generate.py 功能概述 关键代码与细节 4. grpo.py 功能概述 关键代码与细节 5. rewards.py 功能概述 关键代码与细节 6. sft.py 功能概述 关键代码与细节 安装 训练…...

Android RenderEffect对Bitmap高斯模糊(毛玻璃),Kotlin(1)

Android RenderEffect对Bitmap高斯模糊(毛玻璃)&#xff0c;Kotlin&#xff08;1&#xff09; import android.graphics.Bitmap import android.graphics.BitmapFactory import android.graphics.HardwareRenderer import android.graphics.PixelFormat import android.graphic…...

【DuodooBMS】基于Odoo的开源制造执行系统——以开源之力,驱动智能制造

以用户为中心的开放式智造平台 DuodooMES的设计始终围绕“用户可编程、生态可生长”的核心思想&#xff0c;打破传统工业软件的封闭性&#xff0c;让制造企业真正成为系统的“主人”&#xff1a; 1. 用户可编程&#xff1a;生产流程由你定义 界面可配置&#xff1a;无需代码即…...

机器视觉深度学习,工业缺陷检测中数据标注需要注意那些问题

在工业缺陷检测中,数据标注是构建高质量模型的关键步骤,需注意以下问题: 标注准确性 精确标注缺陷位置:确保标注框或掩码准确覆盖缺陷区域,避免过大或过小。 区分缺陷类型:不同缺陷应有明确分类,避免混淆。标注一致性 统一标注标准:制定并遵循统一的标注规范,确保不同…...

数据结构:图论入门

图论起源于欧拉对哥尼斯堡七桥问题的解决. 他构建的图模型将陆地用点来表示, 桥梁则用线表示, 如此一来, 该问题便转化为在图中能否不重复地遍历每条边的问题. 图论的应用 地图着色 在地图着色问题中, 我们用顶点代表国家, 将相邻国家之间用边相连. 这样, 问题就转化为用最少…...

【R语言】方差分析

一、基本术语 在R语言以及更广泛的统计学领域中&#xff0c;方差分析&#xff08;ANOVA&#xff0c;即Analysis of Variance&#xff09;是一种用于比较两个或更多组数据的均值是否存在显著差异的统计方法。可以使用aov()函数或其他相关函数&#xff08;如anova()&#xff09;…...

区块链+隐私计算:长安链多方计算合约标准协议(CMMPC-1)发布

建设背景 长安链与隐私计算的深度融合是构建分布式数据与价值流通网络的关键基石&#xff0c;可以在有效连接多元参与主体的同时确保数据的分布式、可追溯、可计算&#xff0c;以及隐私性与安全性。在长安链与隐私计算的融合实践中&#xff0c;开源社区提炼并抽象出多方计算场…...

#渗透测试#批量漏洞挖掘#Crocus系统—Download 文件读取

免责声明 本教程仅为合法的教学目的而准备&#xff0c;严禁用于任何形式的违法犯罪活动及其他商业行为&#xff0c;在使用本教程前&#xff0c;您应确保该行为符合当地的法律法规&#xff0c;继续阅读即表示您需自行承担所有操作的后果&#xff0c;如有异议&#xff0c;请立即停…...

HAL库USART中断接收的相关问题

文章目录 一、使用中断的步骤二、相关函数分析1、HAL_UART_IRQHandler2、UART_Receive_IT3、HAL_UART_Receive_IT4、UART_Start_Receive_IT5、总结 三、HAL库使用心得 一、使用中断的步骤 1、配置GPIO 2、配置USART1 3、设置UART1中断优先级&#xff08;不开启手动中断&#x…...

顺序表(C)

1.顺序表的概念 顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构&#xff0c;通常借助数组来实现。它的特点是逻辑上相邻的元素在物理存储位置上也相邻&#xff0c;支持随机访问&#xff0c;可通过下标直接访问任意位置的元素。不过&#xff0c;顺序表在插入和…...