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

news2025/4/21 14:55:49

作者:后端小肥肠

🍇 我写过的文章中的相关代码放到了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>> {

    @Override
    public 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);
    }

    @Override
    public List<String> getNullableResult(ResultSet rs, String columnName) throws SQLException {
        Array array = rs.getArray(columnName);
        return array == null ? null : Arrays.asList((String[]) array.getArray());
    }

    @Override
    public List<String> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        Array array = rs.getArray(columnIndex);
        return array == null ? null : Arrays.asList((String[]) array.getArray());
    }

    @Override
    public 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 {
    @Autowired
    private 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);

    // 在方法结束时清理ThreadLocal
    public 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();

        // 填充数据到隐藏sheet
        int 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("提示", "此值与单元格定义格式不一致!");

        // 将验证添加到主sheet
        mainSheet.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 Range
    Dim oldVal As String
    Dim newVal As String

    ' 如果修改的范围超过1个单元格,则退出
    If Target.Count > 1 Then GoTo exitHandler

    On Error Resume Next
    Set rngDV = Cells.SpecialCells(xlCellTypeAllValidation)
    On Error GoTo exitHandler

    If rngDV Is Nothing Then GoTo exitHandler

    If Intersect(Target, rngDV) Is Nothing Then
        ' 如果目标单元格不在数据验证区域,什么都不做
    Else
        Application.EnableEvents = False
        newVal = Target.Value

        ' 假设字段映射如下:
        ' 第3列是 "gender" (性别)
        ' 第4列是 "parentOccupation" (父母职业类型)
        ' 第5列是 "homeArea" (家庭住址所属区域)
        ' 如果修改的是 "gender" 或 "parentOccupation",则是单选,直接替换
        ' 如果修改的是 "homeArea",则是多选,去重并追加

        If Target.Column = 3 Or Target.Column = 4 Then
            ' 对性别(gender)和父母职业类型(parentOccupation)做单选处理
            Application.Undo
            oldVal = Target.Value
            Target.Value = newVal

            ' 如果原值与新值不同,直接替换
            If oldVal <> newVal Then
                Target.Value = newVal
            End If
        ElseIf Target.Column = 5 Then
            ' 对家庭住址所属区域(homeArea)做多选处理
            Application.Undo
            oldVal = Target.Value
            Target.Value = newVal

            If oldVal = "" Then
                ' 如果原值为空,直接返回
            Else
                If 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 If
                    Else
                        ' 如果是新选项,则追加
                        Target.Value = oldVal & "," & newVal
                    End If
                End If
            End If
        End If
    End If

exitHandler:
    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>>() {
                        @Override
                        public void invoke(Map<Integer, String> data, AnalysisContext context) {
                            log.info("读取到一行数据: {}", JSON.toJSONString(data));
                            // 手动转换为Students对象,使用正确的key
                            Students student = new Students();
                            student.setStudentId(data.get(0));      // 学号
                            student.setName(data.get(1));           // 姓名
                            student.setGender(data.get(2));         // 性别
                            student.setParentOccupation(data.get(3));  // 家长职业
                            
                            // 使用与SimpleStringToListConverter相同的逻辑处理homeArea
                            String areaStr = data.get(4);
                            if (areaStr != null && !areaStr.trim().isEmpty()) {
                                student.setHomeArea(Arrays.asList(areaStr.split(",")));
                            }
                            
                            studentsList.add(student);
                        }

                        @Override
                        public 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下拉框(支持多选),解决了实际开发中的常见需求。如有疑问,欢迎在评论区留言,我看到都会回复。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2297894.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

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

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

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工具将…

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

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

hexo 魔改 | 修改卡片透明度

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

Golang的并发编程案例详解

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

策略模式-小结

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

硬件学习笔记--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…

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

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

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

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

LabVIEW用户界面设计原则

在LabVIEW开发中&#xff0c;用户界面&#xff08;UI&#xff09;设计不仅仅是为了美观&#xff0c;它直接关系到用户的操作效率和体验。一个直观、简洁、易于使用的界面能够大大提升软件的可用性&#xff0c;尤其是在复杂的实验或工业应用中。设计良好的UI能够减少操作错误&am…

MySQL8.0 innodb Cluster 高可用集群部署(MySQL、MySQL Shell、MySQL Router安装)

简介 MySQL InnoDB集群&#xff08;Cluster&#xff09;提供了一个集成的&#xff0c;本地的&#xff0c;HA解决方案。Mysq Innodb Cluster是利用组复制的 pxos 协议&#xff0c;保障数据一致性&#xff0c;组复制支持单主模式和多主模式。 InnoDB Cluster组件&#xff1a; …

Effective Objective-C 2.0 读书笔记——内存管理(上)

Effective Objective-C 2.0 读书笔记——内存管理&#xff08;上&#xff09; 文章目录 Effective Objective-C 2.0 读书笔记——内存管理&#xff08;上&#xff09;引用计数属性存取方法中的内存管理autorelease保留环 ARCARC必须遵循的方法命名原则ARC 的自动优化&#xff1…

软件测试覆盖率详解

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 一、覆盖率概念 覆盖率是用来度量测试完整性的一个手段&#xff0c;是测试技术有效性的一个度量。分为&#xff1a;白盒覆盖、灰盒覆盖和黑盒覆盖&#xff1b;测…