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 资源)。
关键步骤:
- 设置响应头(.xlsm 格式)
- 读取模板文件
- 处理下拉框配置
- 输出文件
- 清理资源
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数据导入导出解决方案
作者:后端小肥肠 🍇 我写过的文章中的相关代码放到了gitee,地址:xfc-fdw-cloud: 公共解决方案 🍊 有疑问可私信或评论区联系我。 🥑 创作不易未经允许严禁转载。 姊妹篇: 基于AOP的数据字典实现…...
fun-transformer学习笔记-Task1——Transformer、Seq2Seq、Encoder-Decoder、Attention之间的关系
Transformer、Seq2Seq、Encoder-Decoder、Attention由这四者之间的关系可以从模型架构的发展脉络来理解: Seq2Seq 与 Encoder–Decoder 模型 “Seq2Seq”(sequence‐to‐sequence)是一类用于将一个变长序列映射为另一个变长序列的任务&#x…...
使用Hexo部署NexT主体网站
一.使用git提交文件 参考: 从零开始搭建个人博客(超详细) - 知乎 致谢! 第一种:本地没有 git 仓库 直接将远程仓库 clone 到本地;将文件添加并 commit 到本地仓库;将本地仓库的内容push到远程仓…...
图书管理项目(spring boot + Vue)
想要该项目的话,就 jia 我,并在评论区给我说一下,只需要1元,我把整个项目发给你 jia微:18439421203(名字叫:Bingo) 运行图片:...
python实现常见数学概率分布
常见正态分布 1.贝塔分布1.1 概率密度函数1.2参数对分布形状的影响1.3 应用场景1.4 python实现 2. 帕累托分布(80/20法则)3. 正态分布(高斯分布)3.1 正态分布对应性质3.2 正态分布对应图像实现的完整项目示例,包含权限控制、数据加载状态处理、性能优化等常见高级功能。创建一个简单的博客系统: // 项目结构: 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闪烁
引言 在单片机开发中,外部中断是一个非常重要的功能,它可以让单片机在检测到外部信号变化时立即做出响应。本文将详细介绍如何在CT107D单片机综合训练平台上使用外部中断来控制LED灯的闪烁。我们将使用两种不同的方式来实现这一功能:一种是在…...
HTML之JavaScript使用JSON
HTML之JavaScript使用JSON JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成。JSON是JavaScript对象的字符串表示法,它使用文本表示一个js对象的信息,可以将json字符串转换…...
算法很美笔记(Java)——树
性质 树 上面的性质因为两个结点由一条边连成 结点数目越多,算法复杂度越高 二叉树 结构 层次遍历 利用队列,弹一个,加N个(队列里弹出一个元素,就把这个元素的所有孩子加进去) 具体来说:指…...
SQL面试题4:相互关注问题
引言 在社交媒体和各类社区平台蓬勃发展的当下,用户之间的关系网络成为了平台运营和数据分析的关键部分。相互关注作为一种重要的社交关系,不仅反映了用户之间的紧密程度,还对平台的社交生态、内容传播等方面有着深远影响。本文将聚焦于 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:""20"\n) np.random.seed(1)(x_train,y_train),(x_test,y_test) mnist.load_data() #第一次…...
【C++】解锁<list>的正确姿势
> 🍃 本系列为初阶C的内容,如果感兴趣,欢迎订阅🚩 > 🎊个人主页:[小编的个人主页])小编的个人主页 > 🎀 🎉欢迎大家点赞👍收藏⭐文章 > ✌️ 🤞 …...
Qt中的事件
写一个 可以拖动的按钮 DraggablePushButton.h 头文件 #ifndef DRAGGABLEPUSHBUTTON_H #define DRAGGABLEPUSHBUTTON_H#include <QPushButton> #include <QMouseEvent>class DraggablePushButton : public QPushButton {Q_OBJECTpublic:explicit DraggablePushBu…...
变化检测相关论文可读list
一些用得上的: 遥感变化检测常见数据集https://github.com/rsdler/Remote-Sensing-Change-Detection-Dataset/ 代码解读:代码解读 | 极简代码遥感语义分割,结合GDAL从零实现,以U-Net和建筑物提取为例 NeurIPS2024: https://mp.w…...
Ansible中playbook的变量
变量 playbook的变量有以下几种 在playbook中用户自定义的变量远程主机中由Ansible收集的变量在文件模板中使用的上述两种变量把任务结果作为一个变量使用,叫注册变量用户在执行playbook时,通过命令行传入的变量,叫做额外变量 在playbook中…...
亚信安全正式接入DeepSeek
亚信安全致力于“数据驱动、AI原生”战略,早在2024年5月,推出了“信立方”安全大模型、安全MaaS平台和一系列安全智能体,为网络安全运营、网络安全检测提供AI技术能力。自2024年12月DeepSeek-V3发布以来,亚信安全人工智能实验室利…...
深度学习在微纳光子学中的应用
深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向: 逆向设计 通过神经网络快速预测微纳结构的光学响应,替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…...
【JavaEE】-- HTTP
1. HTTP是什么? HTTP(全称为"超文本传输协议")是一种应用非常广泛的应用层协议,HTTP是基于TCP协议的一种应用层协议。 应用层协议:是计算机网络协议栈中最高层的协议,它定义了运行在不同主机上…...
Docker 运行 Kafka 带 SASL 认证教程
Docker 运行 Kafka 带 SASL 认证教程 Docker 运行 Kafka 带 SASL 认证教程一、说明二、环境准备三、编写 Docker Compose 和 jaas文件docker-compose.yml代码说明:server_jaas.conf 四、启动服务五、验证服务六、连接kafka服务七、总结 Docker 运行 Kafka 带 SASL 认…...
VTK如何让部分单位不可见
最近遇到一个需求,需要让一个vtkDataSet中的部分单元不可见,查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行,是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示,主要是最后一个参数,透明度…...
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南 在数字化营销时代,邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天,我们将深入解析邮件打开率、网站可用性、页面参与时…...
服务器--宝塔命令
一、宝塔面板安装命令 ⚠️ 必须使用 root 用户 或 sudo 权限执行! sudo su - 1. CentOS 系统: yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh2. Ubuntu / Debian 系统…...
Python Ovito统计金刚石结构数量
大家好,我是小马老师。 本文介绍python ovito方法统计金刚石结构的方法。 Ovito Identify diamond structure命令可以识别和统计金刚石结构,但是无法直接输出结构的变化情况。 本文使用python调用ovito包的方法,可以持续统计各步的金刚石结构,具体代码如下: from ovito…...
JS手写代码篇----使用Promise封装AJAX请求
15、使用Promise封装AJAX请求 promise就有reject和resolve了,就不必写成功和失败的回调函数了 const BASEURL ./手写ajax/test.jsonfunction promiseAjax() {return new Promise((resolve, reject) > {const xhr new XMLHttpRequest();xhr.open("get&quo…...
【Nginx】使用 Nginx+Lua 实现基于 IP 的访问频率限制
使用 NginxLua 实现基于 IP 的访问频率限制 在高并发场景下,限制某个 IP 的访问频率是非常重要的,可以有效防止恶意攻击或错误配置导致的服务宕机。以下是一个详细的实现方案,使用 Nginx 和 Lua 脚本结合 Redis 来实现基于 IP 的访问频率限制…...
【学习笔记】erase 删除顺序迭代器后迭代器失效的解决方案
目录 使用 erase 返回值继续迭代使用索引进行遍历 我们知道类似 vector 的顺序迭代器被删除后,迭代器会失效,因为顺序迭代器在内存中是连续存储的,元素删除后,后续元素会前移。 但一些场景中,我们又需要在执行删除操作…...
