当前位置: 首页 > 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的数据字典实现…...

【数据结构入门】一、数组

一、数组的概念 数组&#xff1a;一种由相同类型的数据元素组成的基本数据类型&#xff0c;为引用类型 二、数据的顺序 这里很奇怪&#xff0c;讲了一个寻址函数&#xff0c;就是怎样用坐标求该元素的内存地址。说实话我不知道求这个能干什么&#xff0c;但是感觉还挺好玩的…...

编码格式大全解释以及相关编码特性

目录 说明: 1. Base64 Base64编码的字符集通常包括&#xff1a; Base64的工作原理&#xff1a; Base64编码在安全渗透中的应用场景 常见的Base64编码绕过场景 如何防范Base64绕过攻击 2. URL编码&#xff08;Percent Encoding&#xff09; URL编码与安全渗透的关系 示…...

正则表达式(Regular expresssion)

正则表达式 匹配单次 . &#xff1a;匹配任意一个字符 [ ] &#xff1a;匹配[ ]里举例的任意一个字符 /d &#xff1a;匹配数字0-9 /D &#xff1a;匹配非数字 /s &#xff1a;匹配空白或tab建 /S &#xff1a;匹配非空白 /w &#xff1a;…...

JavaScript 发起网络请求 axios、fetch、async / await

目录 fetch 发送 GET 请求&#xff08;fetch&#xff09; 发送 POST 请求&#xff08;fetch&#xff09; 处理后台异常响应 async / await async await 发送 GET 请求&#xff08;async / await fetch&#xff09; 发送 POST 请求&#xff08;async / await fetch&…...

安装OpenJDK21(linux、macos)

文章目录 安装OpenJDK21java21linux下安装配置mac下安装 安装OpenJDK21 java21 封神&#xff01;Java 21正式发布了&#xff0c;迎来了史诗级新特性&#xff0c;堪称版本最强&#xff01;&#xff01;&#xff01; 视频链接&#xff1a;https://www.bilibili.com/video/BV1E8…...

Java面试宝典:说下Spring Bean的生命周期?

Java面试宝典专栏范围&#xff1a;JAVA基础&#xff0c;面向对象编程&#xff08;OOP&#xff09;&#xff0c;异常处理&#xff0c;集合框架&#xff0c;Java I/O&#xff0c;多线程编程&#xff0c;设计模式&#xff0c;网络编程&#xff0c;框架和工具等全方位面试题详解 每…...

k8s向容器内传文件与下载文件

1、下载&#xff1a; kubectl cp <namespace>/<pod-name>:<container-path> <local-path>示例&#xff1a; kubectl cp mynamespace/mypod:/tmp/testfile.txt ./testfile.txt如果 Pod 中有多个容器&#xff0c;可以通过 -c 标志指定容器: kubectl c…...

A4988一款带转换器和过流保护的 DMOS 微步驱动器的使用方式

A4988是一款带转换器和过流保护的 DMOS 微步驱动器&#xff0c;用于驱动双极步进电动机。它支持全、半、1/4、1/8 及 1/16 步进模式&#xff0c;输出驱动性能可达 35 V 及 2 A。其特点包括简单的步进和方向控制接口、可调电位器调节最大电流输出、自动电流衰减模式检测/选择以及…...

Elasticsearch+Logstash+Kibana可视化集群部署

文章目录 1.组件介绍简述2.集群规划3.Es组件部署4.Logstash组件部署5.Kibana组件部署6.Kibana的基础使用 1.组件介绍简述 Elasticsearch&#xff1a;开源实时分布式搜索和分析引擎&#xff0c;支持大规模数据存储和高吞吐量&#xff0c;提供丰富的搜索功能和可扩展性。 Logsta…...

数据结构之二叉树

数据结构之二叉树 数据结构之二叉树1. 什么是二叉树&#xff1f;2. 基本概念3. 二叉树的基本形态4. 二叉树的性质5. 特殊二叉树6. 二叉树的存储7. 二叉树的遍历7.1 前序遍历7.2 中序遍历7.3 后序遍历7.4 层次遍历 8. 应用场景9. 实例代码9.1 示例树结构9.2 输出结果 数据结构之…...

华为云+硅基流动使用Chatbox接入DeepSeek-R1满血版671B

华为云硅基流动使用Chatbox接入DeepSeek-R1满血版671B 硅基流动 1.1 注册登录 1.2 实名认证 1.3 创建API密钥 1.4 客户端工具 OllamaChatboxCherry StudioAnythingLLM 资源包下载&#xff1a; AI聊天本地客户端 接入Chatbox客户端 点击设置 选择SiliconFloW API 粘贴1.3创…...

《图解设计模式》笔记(十)用类来表现

二十二、Command模式:命令也是类 一个类调用某方法,虽然调用结果会反映在对象的状态中,但不会留下工作的历史记录。 若有一个类表示“请进行这项工作”的“命令”,每一项想做的工作就不再是“方法的调用”这种动态处理了,而是一个表示命令的类的实例,即可以用“物”来表…...

Scrapy:任务队列底层设计详解

Scrapy 中队列设计详解 1. 概述 Scrapy 的队列系统是其调度器&#xff08;Scheduler&#xff09;的核心组件之一&#xff0c;负责存储和管理待抓取的请求。Scrapy 实现了两种类型的队列&#xff1a; 内存队列&#xff1a;请求存储在内存中&#xff0c;重启后数据丢失磁盘队列…...

zola + github page,用 workflows 部署

之前的Zola都是本地build之后&#xff0c;再push到github上&#xff0c;这种方式很明显的弊端就是只能在本地编辑&#xff0c;而不能通过github编辑&#xff0c;再pull到本地&#xff0c;缺乏了灵活性。因此将zola用workflows来部署。 repo地址&#xff1a;https://github.com/…...

pytest测试专题 - 1.2 如何获得美观的测试报告

<< 返回目录 1 pytest测试专题 - 1.2 如何获得美观的测试报告 1.1 背景 虽然pytest命令的报文很详细&#xff0c;用例在执行调试时还算比较方便阅读和提取失败信息&#xff0c; 但对于大量测试用例运行时&#xff0c;可能会存在以下不足 报文被冲掉测试日志没法归档 …...

关闭浏览器安全dns解决访问速度慢的问题

谷歌浏览器加载速度突然变慢了&#xff1f;检查安全DNS功能(DoH)是否被默认开启。 谷歌浏览器在去年已经推出安全DNS功能(即DoH) , 启用此功能后可以通过加密的DNS增强网络连接安全性。例如查询请求被加密后网络运营商将无法嗅探用户访问的地址&#xff0c;因此对于增强用户的…...

今日AI和商界事件(2025-02-14)

今日AI大事件主要包括以下几个方面&#xff1a; 一、苹果新品预告 事件概述&#xff1a;苹果CEO蒂姆库克在社交媒体发布7秒视频&#xff0c;配文“准备好迎接家庭的新成员”&#xff0c;并宣布2月19日将有新品发布。知名科技记者马克古尔曼称&#xff0c;新款低端iPhone SE将…...

【黑马点评优化】1-使用JWT登录认证+redis实现自动续期

1-使用JWT登录认证redis实现自动续期 0 前言1 原先的redis实现登录鉴权2 JWT登录认证Redis自动续期2.1 认证&#xff08;identification&#xff09;授权 &#xff08;authorization&#xff09;和鉴权&#xff08;Authorization&#xff09;2.1.1 认证&#xff08;identificat…...

一个让Stable Diffusion更稳定、更易用的Github开源项目

2023除了ChatGPT大火&#xff0c;Stable Diffusion同样也是非常火热&#xff0c;Stable Diffusion是一个Github开源项目&#xff0c;很多爱好者都会本地安装&#xff0c;但面对一些初学者来说&#xff0c;在安装、配置和使用过程中还是会经常出现很多问题&#xff0c;特别不了解…...

Mac之JDK安装

Mac之JDK安装 一.安装 jdk 打开终端输入命令:java -version 查看是否已安装 JDK Oracle 官方下载地址 根据自己Mac 系统安装 查看 Mac 系统&#xff0c;打开中断命令&#xff0c;输入: uname -a Compressed Archive 是压缩文档&#xff0c;下载的是一个 .tar.gz 压缩包 D…...

深入Flask:如何优雅地处理HTTP请求与响应

哈喽,大家好,我是木头左! 本文将带你深入了解如何在Flask中优雅地处理HTTP请求和响应,让你的应用更加高效、安全和用户友好。 创建一个简单的Flask应用 让从创建一个最简单的Flask应用开始: from flask import Flaskapp = Flask(__name__)@app.route(/) def...

kron积计算mask类别矩阵

文章目录 1. 生成类别矩阵如下2. pytorch 代码3. 循环移动矩阵 1. 生成类别矩阵如下 2. pytorch 代码 import torch import torch.nn as nn import torch.nn.functional as Ftorch.set_printoptions(precision3, sci_modeFalse)if __name__ "__main__":run_code 0…...

Redis实现消息队列

什么是消息列队。 消息队列是一种应用间的异步协作机制&#xff0c;同时消息队列中间件是分布式系统中重要的组件&#xff0c;主要解决应用耦合&#xff0c;异步消息&#xff0c;流量削峰等问题。实现高性能&#xff0c;高可用&#xff0c;可伸缩和最终一致性架构。市面上的 M…...

vue+springboot+webtrc+websocket实现双人音视频通话会议

前言 最近一些时间我有研究&#xff0c;如何实现一个视频会议功能&#xff0c;但是找了好多资料都不太理想&#xff0c;最终参考了一个文章 WebRTC实现双端音视频聊天&#xff08;Vue3 SpringBoot&#xff09; 只不过&#xff0c;它的实现效果里面只会播放本地的mp4视频文件&…...

【免费送书活动】《MySQL 9从入门到性能优化(视频教学版)》

本博主免费赠送读者3本书&#xff0c;书名为《MySQL 9从入门到性能优化&#xff08;视频教学版&#xff09;》。 《MySQL 9从入门到性能优化&#xff08;视频教学版&#xff09;&#xff08;数据库技术丛书&#xff09;》(王英英)【摘要 书评 试读】- 京东图书 这本书已经公开…...

【设计模式】【行为型模式】命令模式(Command)

&#x1f44b;hi&#xff0c;我不是一名外包公司的员工&#xff0c;也不会偷吃茶水间的零食&#xff0c;我的梦想是能写高端CRUD &#x1f525; 2025本人正在沉淀中… 博客更新速度 &#x1f44d; 欢迎点赞、收藏、关注&#xff0c;跟上我的更新节奏 &#x1f3b5; 当你的天空突…...

1.14学习总结

日常刷题单 刷了题目后&#xff0c;对于排序方法更加熟练&#xff0c;手搓代码的速度也得到了提高。 感觉字符串还不熟练&#xff0c;高精度更是云里雾里&#xff0c;上升空间极大。 同时看见今晚有个入门难度的测试&#xff0c;去练了练手&#xff0c;想看看自己是什么成分&…...

QxOrm生成json

下载Qxorm-1.5版本 使用vs打开项目&#xff0c;直接生成即可&#xff1a; lib目录中会生成dll和lib文件 新建Qt项目使用Qxorm: 将QxOrm中上面三个目录拷贝到新建的Qt项目中 pro文件添加使用QxOrm第三方库 INCLUDEPATH $$PWD/include/ LIBS -L"$$PWD/lib" LIBS…...

伯克利 CS61A 课堂笔记 09 —— Data Abstraction

本系列为加州伯克利大学著名 Python 基础课程 CS61A 的课堂笔记整理&#xff0c;全英文内容&#xff0c;文末附词汇解释。 目录 01 Data Abstraction 数据抽象 Ⅰ Rational Numbers Ⅱ Rational Number Arithmetic 02 Pairs 对 Ⅰ Representing Pairs Using Lists Ⅱ Re…...

高频 SQL 50 题(基础版)

高频 SQL 50 题&#xff08;基础版&#xff09; 查询连接聚合函数排序和分组高级查询和连接子查询高级字符串函数 / 正则表达式 / 子句 查询 链接: link 链接: link 链接: link 链接: link 链接: link 连接 链接: 高频 SQL 50 题基础版_1378. 使用唯一标识码替换员工ID 链接…...

HtmlRAG:RAG系统中,HTML比纯文本效果更好

HtmlRAG 方法通过使用 HTML 而不是纯文本来增强 RAG 系统中的知识表示能力。通过 HTML 清洗和两步块树修剪方法&#xff0c;在保持关键信息的同时缩短了 HTML 文档的长度。这种方法优于现有基于纯文本的RAG的性能。 方法 其实主要看下围绕html提纯思路&#xff0c;将提纯后的…...

python学opencv|读取图像(六十二)使用cv2.morphologyEx()形态学函数实现图像梯度处理

【1】引言 前序已经学习了腐蚀和膨胀的单独作用函数&#xff0c;还研究了按照不同顺序调用腐蚀和膨胀函数调整图像效果&#xff0c;相关文章包括且不限于&#xff1a; python学opencv|读取图像&#xff08;六十一&#xff09;先后使用cv2.dilate()函数和cv2.erode()函数实现图…...

10G EPON光模块

一、10G EPON对称光模块 工作模式&#xff1a;上行突发接收、下行连续发射。 工作原理&#xff1a;当需要发送信号时&#xff0c;系统信号通过光模块的电接口把信号传送到驱动芯片&#xff0c;芯片处理后&#xff0c;驱动激光器发出调制光信号&#xff0c;经光纤发到远端&…...

RocketMQ与kafka如何解决消息丢失问题?

0 前言 消息丢失基本是分布式MQ中需要解决问题&#xff0c;消息丢失时保证数据可靠性的范畴。如何保证消息不丢失程序员面试中几乎不可避免的问题。本文主要说明RocketMQ和Kafka在解决消息丢失问题时&#xff0c;在生产者、Broker和消费者之间如何解决消息丢失问题。 1.Rocket…...

每日Attention学习23——KAN-Block

模块出处 [SPL 25] [link] [code] KAN See In the Dark 模块名称 Kolmogorov-Arnold Network Block (KAN-Block) 模块作用 用于vision的KAN结构 模块结构 模块代码 import torch import torch.nn as nn import torch.nn.functional as F import mathclass Swish(nn.Module)…...

【前端】ES6新特性汇总

本文作者&#xff1a; slience_me ES6新特性汇总 1. let声明变量 1&#xff09;let作用域 // var 声明的变量往往会越域 // let 声明的变量有严格的局部作用域 {var a 1;let b 2; } console.log(a); // 1 console.log(b); // 报错 b is not defined2&#xff09;声明次数 …...

2024 CyberHost 语音+图像-视频

项目&#xff1a;CyberHost: Taming Audio-driven Avatar Diffusion Model with Region Codebook Attention 音频驱动的身体动画面临两个主要挑战&#xff1a;&#xff08;1&#xff09;关键人体部位&#xff0c;如面部和手部&#xff0c;在视频帧中所占比例较小&#x…...

Git命令摘录

使用 Git 升级软件通常是指通过 Git 仓库获取软件的最新版本或更新代码。以下是详细的步骤和方法&#xff1a; 1. 克隆软件仓库 如果这是你第一次获取软件代码&#xff0c;可以使用 git clone 命令将远程仓库克隆到本地。 git clone <仓库地址> 例如&#xff1a; git cl…...

DeepSeek24小时写作机器人,持续创作高质量文案

内容创作已成为企业、自媒体和创作者的核心竞争力。面对海量的内容需求&#xff0c;人工创作效率低、成本高、质量参差不齐等问题日益凸显。如何在有限时间内产出高质量内容&#xff1f;DeepSeek写作机器人&#xff0c;一款24小时持续创作的智能工具&#xff0c;为企业和个人提…...

Python 面向对象的三大特征

前言&#xff1a;本篇讲解面向对象的三大特征&#xff08;封装&#xff0c;继承&#xff0c;多态&#xff09;&#xff0c;还有比较细致的&#xff08;类属性类方法&#xff0c;静态方法&#xff09;&#xff0c;分步骤讲解&#xff0c;比较适合理清楚三大特征的思路 面向对象的…...

在mac中安装Colima使用docker(替代Docker Desktop)

目录 推荐方案&#xff1a;Colima Docker CLI&#xff08;原生 ARM 支持&#xff09; 步骤 1: 安装必需工具 步骤 2: 启动 Colima (优化 ARM 虚拟机) 步骤 3: 绑定 Docker CLI 到 Colima 步骤 4: 验证 Docker 运行 方案对比与注意事项 常见陷阱 卸载残留配置&#xff…...

YOLO11网络结构以及改进1

YOLO11 1.YOLO11网络结构图在哪里&#xff1f;2.对应的网络结构图3.每一个模块详解3.1 Conv模块3.2关于卷积模块3.3 关于给各个模块指定参数的细节 4.加入CBAM 1.YOLO11网络结构图在哪里&#xff1f; 2.对应的网络结构图 3.每一个模块详解 3.1 Conv模块 位置&#xff1a;ultr…...

EtherNetIP转ModbusTCP网关,给风电注入“超级赛亚人”能量

EtherNetIP转ModbusTCP网关&#xff0c;给风电注入“超级赛亚人”能量 在工业通信领域&#xff0c;常常需要将不同网络协议的设备和系统连接起来&#xff0c;以实现更高效的数据交互和系统集成。比如&#xff0c;把EtherNet/IP设备及其网络连接到ModbusTCP网络系统&#xff0c…...

30天开发操作系统 第 20 天 -- API

前言 大家早上好&#xff0c;今天我们继续努力哦。 昨天我们已经实现了应用程序的运行, 今天我们来实现由应用程序对操作系统功能的调用(即API, 也叫系统调用)。 为什么这样的功能称为“系统调用”(system call)呢&#xff1f;因为它是由应用程序来调用(操作)系统中的功能来完…...

DeepSeek处理自有业务的案例:让AI给你写一份小众编辑器(EverEdit)的语法着色文件

1 DeepSeek处理自有业务的案例&#xff1a;让AI给你写一份小众编辑器(EverEdit)的语法着色文件 1.1 背景 AI能力再强&#xff0c;如果不能在企业的自有业务上产生助益&#xff0c;那基本也是一无是处。将企业的自有业务上传到线上训练&#xff0c;那是脑子进水的做法&#xff…...

在香橙派5 NPU上使用Yolov5

【香橙派】使用NPU部署Yolov5的完整解决方案 香橙派使用NPU部署Yolov5的完整解决方案 Orangepi 5 Pro(香橙派5pro)部署yolov5 RK3588实战&#xff1a;调用npu加速&#xff0c;yolov5识别图像、ffmpeg发送到rtmp服务器 香橙派5 RK3588 yolov5模型转换rknn及部署踩坑全记录 orang…...

常用排序算法

1. 基础排序算法 1.1 冒泡排序&#xff08;Bubble Sort&#xff09; 原理&#xff1a; 依次比较相邻元素&#xff0c;将较大的元素逐步"冒泡"到右侧。 def bubble_sort(arr):n len(arr)for i in range(n):swapped Falsefor j in range(0, n-i-1):if arr[j] >…...

MySQL判空函数--IFNULL函数的使用

文章目录 IFNULL函数介绍IFNULL函数的语法举例相关扩展 IFNULL函数介绍 在MySQL中&#xff0c;IFNULL函数用于判断给定的表达式是否为NULL。如果表达式为NULL&#xff0c;则IFNULL函数返回指定的替代值&#xff1b;如果表达式不为NULL&#xff0c;则返回表达式本身的值。 IFN…...

Git 设置代理

设置 HTTP 和 HTTPS 代理 运行以下命令来配置 Git 的 HTTP 和 HTTPS 代理&#xff1a; git config --global http.proxy http://127.0.0.1:7890 git config --global https.proxy https://127.0.0.1:7890 验证代理设置 你可以通过以下命令检查代理是否设置成功&#xff1a; g…...