Excel 导入导出工具类文档

Excel 导入导出工具类详解:基于 Spring 和 Apache POI 的通用解决方案

本文分享一套功能强大的 Excel 导入导出工具类,基于 Spring 框架和 Apache POI 实现,支持注解驱动的字段映射、数据类型自动转换、样式定制等高级功能。

工具类核心功能

  • 智能导入:支持 Excel 数据到 Java 对象的自动转换
  • 灵活导出:Java 对象到 Excel 的自动导出
  • 注解驱动:通过注解控制字段映射行为
  • 类型转换:自动处理常见数据类型(日期、数值、布尔值等)
  • 错误处理:导入时跳过错误行,导出时标记错误字段

核心工具类源码

1. Excel 导入工具类 (ExcelImportUtil.java)

java 复制代码
package com.xk.toolkit.util.file.excel;

import com.xk.toolkit.util.file.excel.property.ExcelIgnore;
import com.xk.toolkit.util.file.excel.property.ExcelProperty;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
 * Excel导入工具类
 *
 * <p>提供通用的Excel导入功能,支持基于注解的字段映射和类型转换</p>
 *
 * <p>功能特点:
 * <ul>
 *   <li>基于反射机制实现通用导入</li>
 *   <li>支持字段过滤(@ExcelIgnore)</li>
 *   <li>支持自定义列名和日期格式(@ExcelProperty)</li>
 *   <li>自动类型转换(字符串、数值、日期、布尔值)</li>
 *   <li>公式单元格处理</li>
 *   <li>智能列名匹配(支持大小写不敏感匹配)</li>
 *   <li>容错处理(跳过空行和错误行)</li>
 * </ul>
 */
public class ExcelImportUtil {

    /**
     * 通用Excel导入方法
     *
     * @param <T>   数据类型泛型
     * @param file  上传的Excel文件
     * @param clazz 目标对象类型
     * @return 导入成功的数据对象列表
     * @throws IOException 当文件解析失败时抛出
     *
     * <p>执行流程:
     * <ol>
     *   <li>获取字段映射关系</li>
     *   <li>解析Excel文件</li>
     *   <li>构建列索引到字段的映射</li>
     *   <li>遍历数据行并转换为对象</li>
     *   <li>类型转换和字段赋值</li>
     *   <li>返回成功导入的对象列表</li>
     * </ol>
     */
    public static <T> List<T> importExcel(MultipartFile file, Class<T> clazz) throws IOException {
        List<T> resultList = new ArrayList<>();
        // 获取字段映射(列名->字段对象)
        Map<String, Field> fieldMap = getFieldMap(clazz);

        // 使用try-with-resources确保流关闭
        try (InputStream inputStream = file.getInputStream();
             // 创建XSSFWorkbook解析.xlsx格式
             Workbook workbook = new XSSFWorkbook(inputStream)) {

            // 获取第一个工作表
            Sheet sheet = workbook.getSheetAt(0);
            // 获取表头行(第0行)
            Row headerRow = sheet.getRow(0);
            // 列索引到字段的映射
            Map<Integer, FieldMapping> columnMapping = new HashMap<>();

            // 1. 构建列映射关系 =======================================
            for (int col = 0; col < headerRow.getLastCellNum(); col++) {
                Cell cell = headerRow.getCell(col);
                if (cell != null) {
                    // 获取列名并去除空格
                    String columnName = cell.getStringCellValue().trim();
                    // 根据列名查找对应字段
                    Field field = findFieldByColumnName(fieldMap, columnName);
                    if (field != null) {
                        // 存储映射关系:列索引 -> (字段对象, 日期格式)
                        columnMapping.put(col, new FieldMapping(field, getDateFormat(field)));
                    }
                }
            }

            // 2. 处理数据行 ==========================================
            for (int rowNum = 1; rowNum <= sheet.getLastRowNum(); rowNum++) {
                Row row = sheet.getRow(rowNum);
                // 跳过空行
                if (row == null) continue;

                try {
                    // 通过反射创建对象实例
                    T obj = clazz.getDeclaredConstructor().newInstance();
                    // 标记行是否有有效数据
                    boolean hasData = false;

                    // 遍历所有列
                    for (int col = 0; col < headerRow.getLastCellNum(); col++) {
                        Cell cell = row.getCell(col);
                        // 跳过空单元格
                        if (cell == null) continue;

                        // 获取列映射信息
                        FieldMapping mapping = columnMapping.get(col);
                        if (mapping != null) {
                            // 解析单元格值(根据目标类型和日期格式)
                            Object value = parseCellValue(cell, mapping.field.getType(), mapping.dateFormat);
                            if (value != null) {
                                // 设置字段可访问(私有字段)
                                mapping.field.setAccessible(true);
                                // 给字段赋值
                                mapping.field.set(obj, value);
                                // 标记有有效数据
                                hasData = true;
                            }
                        }
                    }

                    // 如果有有效数据则添加到结果列表
                    if (hasData) {
                        resultList.add(obj);
                    }
                } catch (Exception e) {
                    // 记录错误行信息(实际应用中应使用日志框架)
                    System.err.printf("导入第 %d 行数据出错: %s%n", rowNum + 1, e.getMessage());
                }
            }
        } catch (Exception e) {
            throw new IOException("文件解析失败: " + e.getMessage(), e);
        }

        return resultList;
    }

    /**
     * 获取字段映射关系
     *
     * @param clazz 目标类
     * @return 列名到字段对象的映射Map
     *
     * <p>映射规则:
     * <ul>
     *   <li>忽略带@ExcelIgnore注解的字段</li>
     *   <li>优先使用@ExcelProperty的value作为键</li>
     *   <li>无注解时使用字段名作为键</li>
     * </ul>
     */
    private static Map<String, Field> getFieldMap(Class<?> clazz) {
        Map<String, Field> fieldMap = new HashMap<>();
        // 遍历所有声明字段
        for (Field field : clazz.getDeclaredFields()) {
            // 跳过忽略字段
            if (field.isAnnotationPresent(ExcelIgnore.class)) continue;

            // 获取ExcelProperty注解
            ExcelProperty annotation = field.getAnnotation(ExcelProperty.class);
            // 确定映射键:注解值优先,否则使用字段名
            String key = (annotation != null && !annotation.value().isEmpty()) ?
                    annotation.value() : field.getName();
            fieldMap.put(key, field);
        }
        return fieldMap;
    }

    /**
     * 根据列名查找字段
     *
     * @param fieldMap   字段映射表
     * @param columnName Excel列名
     * @return 匹配的字段对象,未找到返回null
     *
     * <p>匹配策略:
     * <ol>
     *   <li>精确匹配(区分大小写)</li>
     *   <li>忽略大小写匹配</li>
     * </ol>
     */
    private static Field findFieldByColumnName(Map<String, Field> fieldMap, String columnName) {
        // 1. 精确匹配
        if (fieldMap.containsKey(columnName)) {
            return fieldMap.get(columnName);
        }

        // 2. 忽略大小写匹配
        for (String key : fieldMap.keySet()) {
            if (key.equalsIgnoreCase(columnName)) {
                return fieldMap.get(key);
            }
        }

        return null;
    }

    /**
     * 获取字段的日期格式
     *
     * @param field 目标字段
     * @return 日期格式字符串(默认"yyyy-MM-dd HH:mm:ss")
     */
    private static String getDateFormat(Field field) {
        ExcelProperty annotation = field.getAnnotation(ExcelProperty.class);
        // 优先使用注解指定的格式,否则使用默认格式
        return (annotation != null) ? annotation.dateFormat() : "yyyy-MM-dd HH:mm:ss";
    }

    /**
     * 解析单元格值
     *
     * @param cell       单元格对象
     * @param targetType 目标Java类型
     * @param dateFormat 日期格式(仅对日期类型有效)
     * @return 转换后的Java对象
     *
     * <p>处理逻辑:
     * <ul>
     *   <li>字符串类型 -> convertStringValue()</li>
     *   <li>数值类型 -> 判断是否为日期</li>
     *   <li>布尔类型 -> 直接返回</li>
     *   <li>公式类型 -> 解析公式结果</li>
     * </ul>
     */
    private static Object parseCellValue(Cell cell, Class<?> targetType, String dateFormat) {
        // 根据单元格类型分发处理
        switch (cell.getCellType()) {
            case STRING:
                // 字符串类型处理
                return convertStringValue(cell.getStringCellValue().trim(), targetType, dateFormat);
            case NUMERIC:
                // 数值类型:判断是否为日期格式
                if (DateUtil.isCellDateFormatted(cell)) {
                    // 日期类型处理
                    return convertDateValue(cell.getDateCellValue(), targetType);
                } else {
                    // 普通数值处理
                    return convertNumericValue(cell.getNumericCellValue(), targetType);
                }
            case BOOLEAN:
                // 布尔类型直接返回
                return cell.getBooleanCellValue();
            case FORMULA:
                // 公式单元格特殊处理
                return parseFormulaCell(cell, targetType, dateFormat);
            default:
                // 其他类型返回null
                return null;
        }
    }

    /**
     * 解析公式单元格
     *
     * @param cell       公式单元格
     * @param targetType 目标Java类型
     * @param dateFormat 日期格式
     * @return 公式计算结果的转换值
     */
    private static Object parseFormulaCell(Cell cell, Class<?> targetType, String dateFormat) {
        try {
            // 根据公式结果类型处理
            switch (cell.getCachedFormulaResultType()) {
                case STRING:
                    return convertStringValue(cell.getStringCellValue().trim(), targetType, dateFormat);
                case NUMERIC:
                    if (DateUtil.isCellDateFormatted(cell)) {
                        return convertDateValue(cell.getDateCellValue(), targetType);
                    } else {
                        return convertNumericValue(cell.getNumericCellValue(), targetType);
                    }
                case BOOLEAN:
                    return cell.getBooleanCellValue();
                default:
                    return null;
            }
        } catch (Exception e) {
            // 公式解析异常返回null
            return null;
        }
    }

    /**
     * 转换字符串值到目标类型
     *
     * @param value      字符串值
     * @param targetType 目标Java类型
     * @param dateFormat 日期格式
     * @return 转换后的Java对象
     * @throws IllegalArgumentException 当转换失败时抛出
     *
     * <p>支持转换类型:
     * <ul>
     *   <li>String</li>
     *   <li>Integer/int</li>
     *   <li>Long/long</li>
     *   <li>Double/double</li>
     *   <li>Boolean/boolean(支持"是/否"、"YES/NO"、"TRUE/FALSE"、"1/0")</li>
     *   <li>LocalDate(按指定格式)</li>
     *   <li>LocalDateTime(按指定格式)</li>
     * </ul>
     */
    private static Object convertStringValue(String value, Class<?> targetType, String dateFormat) {
        if (value.isEmpty()) return null;

        try {
            // 字符串类型直接返回
            if (targetType == String.class) return value;
            // 整型转换
            if (targetType == Integer.class || targetType == int.class) return Integer.parseInt(value);
            // 长整型转换
            if (targetType == Long.class || targetType == long.class) return Long.parseLong(value);
            // 双精度转换
            if (targetType == Double.class || targetType == double.class) return Double.parseDouble(value);
            // 布尔类型智能转换
            if (targetType == Boolean.class || targetType == boolean.class) {
                return "是".equals(value) || "YES".equalsIgnoreCase(value) ||
                        "TRUE".equalsIgnoreCase(value) || "1".equals(value);
            }
            // 日期转换
            if (targetType == LocalDate.class) {
                return LocalDate.parse(value, DateTimeFormatter.ofPattern(dateFormat));
            }
            // 日期时间转换
            if (targetType == LocalDateTime.class) {
                return LocalDateTime.parse(value, DateTimeFormatter.ofPattern(dateFormat));
            }
        } catch (Exception e) {
            throw new IllegalArgumentException("值转换失败: " + value + " -> " + targetType.getSimpleName());
        }
        // 未匹配类型返回原始字符串
        return value;
    }

    /**
     * 转换数值到目标类型
     *
     * @param value      数值
     * @param targetType 目标Java类型
     * @return 转换后的Java对象
     */
    private static Object convertNumericValue(double value, Class<?> targetType) {
        // 整型转换
        if (targetType == Integer.class || targetType == int.class) return (int) value;
        // 长整型转换
        if (targetType == Long.class || targetType == long.class) return (long) value;
        // 双精度转换
        if (targetType == Double.class || targetType == double.class) return value;
        // 单精度转换
        if (targetType == Float.class || targetType == float.class) return (float) value;
        // 布尔转换(>0为true)
        if (targetType == Boolean.class || targetType == boolean.class) return value > 0;
        // 默认返回原始值
        return value;
    }

    /**
     * 转换Date对象到目标类型
     *
     * @param date       Java Date对象
     * @param targetType 目标Java类型
     * @return 转换后的时间对象
     */
    private static Object convertDateValue(Date date, Class<?> targetType) {
        if (date == null) return null;
        // Date类型直接返回
        if (targetType == Date.class) return date;
        // 转换为LocalDateTime
        if (targetType == LocalDateTime.class) {
            return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
        }
        // 转换为LocalDate
        if (targetType == LocalDate.class) {
            return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
        }
        // 默认返回Date
        return date;
    }

    /**
     * 字段映射辅助类
     *
     * <p>封装字段和对应的日期格式信息</p>
     */
    private static class FieldMapping {
        // 目标字段
        final Field field;
        // 日期格式(来自@ExcelProperty注解)
        final String dateFormat;

        FieldMapping(Field field, String dateFormat) {
            this.field = field;
            this.dateFormat = dateFormat;
        }
    }
}

2. Excel 导出工具类 (ExcelExportUtil.java)

java 复制代码
package com.xk.toolkit.util.file.excel;

import com.mnsn.framework.law.util.property.ExcelIgnore;
import com.mnsn.framework.law.util.property.ExcelProperty;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.util.List;

/**
 * Excel导出工具类
 *
 * <p>提供通用的Excel导出功能,支持基于注解的字段映射和样式定制</p>
 *
 * <p>功能特点:
 * <ul>
 *   <li>基于反射机制实现通用导出</li>
 *   <li>支持字段过滤(@ExcelIgnore)</li>
 *   <li>支持自定义列名(@ExcelProperty)</li>
 *   <li>自动设置表头和数据样式</li>
 *   <li>自动调整列宽适应内容</li>
 *   <li>支持常见数据类型(数值、字符串、布尔值)</li>
 *   <li>响应流直接输出,避免内存溢出</li>
 * </ul>
 */
public class ExcelExportUtil {

    /**
     * 通用Excel导出方法
     *
     * @param <T>        数据类型泛型
     * @param response   Http响应对象,用于输出Excel文件流
     * @param dataList   要导出的数据列表
     * @param fileName   导出文件名(不含扩展名)
     * @param sheetName  工作表名称
     * @param clazz      数据对象类型(用于反射获取字段信息)
     * @throws IOException 当文件导出失败时抛出
     *
     * <p>执行流程:
     * <ol>
     *   <li>创建工作簿和工作表</li>
     *   <li>创建带样式的表头行</li>
     *   <li>遍历数据列表填充数据行</li>
     *   <li>自动调整列宽</li>
     *   <li>设置响应头并输出Excel文件流</li>
     * </ol>
     */
    public static <T> void exportExcel(HttpServletResponse response,
                                       List<T> dataList,
                                       String fileName,
                                       String sheetName,
                                       Class<T> clazz) throws IOException {
        // 使用XSSFWorkbook创建.xlsx格式的Excel工作簿(支持大文件)
        try (Workbook workbook = new XSSFWorkbook()) {
            // 创建工作表
            Sheet sheet = workbook.createSheet(sheetName);

            // 获取所有声明的字段(包括私有字段)
            Field[] fields = clazz.getDeclaredFields();
            // 创建表头行(第0行)
            Row headerRow = sheet.createRow(0);
            // 创建表头样式
            CellStyle headerStyle = createHeaderStyle(workbook);

            // 列索引计数器
            int colNum = 0;
            // 遍历所有字段
            for (Field field : fields) {
                // 设置可访问私有字段
                field.setAccessible(true);
                // 检查是否忽略该字段
                if (!field.isAnnotationPresent(ExcelIgnore.class)) {
                    // 获取列名(优先使用注解值,无注解则使用字段名)
                    String headerName = getHeaderName(field);
                    // 创建表头单元格
                    Cell cell = headerRow.createCell(colNum++);
                    // 设置表头文本
                    cell.setCellValue(headerName);
                    // 应用表头样式
                    cell.setCellStyle(headerStyle);
                }
            }

            // 创建数据单元格样式
            CellStyle dataStyle = createDataStyle(workbook);
            // 行索引计数器(从第1行开始)
            int rowNum = 1;
            // 遍历数据列表
            for (T data : dataList) {
                // 创建数据行
                Row row = sheet.createRow(rowNum++);
                // 重置列索引
                colNum = 0;
                // 遍历所有字段
                for (Field field : fields) {
                    field.setAccessible(true);
                    // 检查是否忽略该字段
                    if (!field.isAnnotationPresent(ExcelIgnore.class)) {
                        Cell cell;
                        try {
                            // 通过反射获取字段值
                            Object value = field.get(data);
                            // 创建数据单元格
                            cell = row.createCell(colNum++);
                            // 设置单元格值(根据类型处理)
                            setCellValue(cell, value);
                            // 应用数据样式
                            cell.setCellStyle(dataStyle);
                        } catch (IllegalAccessException e) {
                            // 字段访问异常处理
                            cell = row.createCell(colNum++);
                            cell.setCellValue("[ERROR]");
                        }
                    }
                }
            }

            // 自动调整列宽(根据内容)
            for (int i = 0; i < colNum; i++) {
                sheet.autoSizeColumn(i);
            }

            // 设置响应内容类型为Excel
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setCharacterEncoding("utf-8");

            // 设置下载文件名(UTF-8编码处理中文)
            response.setHeader("Content-Disposition", "attachment; filename=" +
                    URLEncoder.encode(fileName + ".xlsx", "UTF-8"));

            // 将工作簿写入响应输出流
            workbook.write(response.getOutputStream());
        }
    }

    /**
     * 创建表头样式
     *
     * @param workbook Excel工作簿对象
     * @return 配置好的表头单元格样式
     *
     * <p>样式特点:
     * <ul>
     *   <li>深蓝色背景</li>
     *   <li>白色粗体文字</li>
     *   <li>居中对齐</li>
     * </ul>
     */
    private static CellStyle createHeaderStyle(Workbook workbook) {
        // 创建单元格样式
        CellStyle style = workbook.createCellStyle();
        // 创建字体
        Font font = workbook.createFont();
        // 设置粗体
        font.setBold(true);
        // 设置白色字体
        font.setColor(IndexedColors.WHITE.getIndex());
        // 应用字体
        style.setFont(font);
        // 设置深蓝色背景
        style.setFillForegroundColor(IndexedColors.DARK_BLUE.getIndex());
        // 设置实心填充
        style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        // 设置水平居中
        style.setAlignment(HorizontalAlignment.CENTER);
        return style;
    }

    /**
     * 创建数据行样式
     *
     * @param workbook Excel工作簿对象
     * @return 配置好的数据单元格样式
     *
     * <p>样式特点:
     * <ul>
     *   <li>四周边框</li>
     *   <li>无特殊背景色</li>
     * </ul>
     */
    private static CellStyle createDataStyle(Workbook workbook) {
        CellStyle style = workbook.createCellStyle();
        // 设置上边框(细线)
        style.setBorderTop(BorderStyle.THIN);
        // 设置下边框(细线)
        style.setBorderBottom(BorderStyle.THIN);
        // 设置左边框(细线)
        style.setBorderLeft(BorderStyle.THIN);
        // 设置右边框(细线)
        style.setBorderRight(BorderStyle.THIN);
        return style;
    }

    /**
     * 获取字段的列名
     *
     * @param field 实体类字段
     * @return 列名字符串(优先返回注解值)
     */
    private static String getHeaderName(Field field) {
        // 检查是否有ExcelProperty注解
        if (field.isAnnotationPresent(ExcelProperty.class)) {
            // 返回注解的value值
            return field.getAnnotation(ExcelProperty.class).value();
        }
        // 默认返回字段名
        return field.getName();
    }

    /**
     * 设置单元格值(根据Java类型处理)
     *
     * @param cell  目标单元格
     * @param value 要设置的值
     *
     * <p>处理规则:
     * <ul>
     *   <li>null值 -> 空字符串</li>
     *   <li>数值类型 -> 转换为Double</li>
     *   <li>布尔类型 -> 直接设置</li>
     *   <li>其他类型 -> toString()结果</li>
     * </ul>
     */
    private static void setCellValue(Cell cell, Object value) {
        if (value == null) {
            cell.setCellValue("");  // 空值处理
        } else if (value instanceof Number) {
            // 数值类型转换为Double
            cell.setCellValue(((Number) value).doubleValue());
        } else if (value instanceof Boolean) {
            // 布尔类型直接设置
            cell.setCellValue((Boolean) value);
        } else {
            // 其他类型使用字符串表示
            cell.setCellValue(value.toString());
        }
    }
}

3. 注解系统

Excel 属性注解 (ExcelProperty.java)
java 复制代码
package com.xk.toolkit.util.file.excel.property;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// 列映射注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ExcelProperty {
    // 列名
    String value() default "";
    // 日期格式(仅对时间类型有效)
    String dateFormat() default "yyyy-MM-dd HH:mm:ss";
}
Excel 忽略注解 (ExcelIgnore.java)
java 复制代码
package com.xk.toolkit.util.file.excel.property;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// 忽略字段注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ExcelIgnore {
}
Excel 列宽注解 (ExcelColumnWidth.java)
java 复制代码
package com.xk.toolkit.util.file.excel.property;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// ExcelColumnWidth.java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ExcelColumnWidth {
    int value() default 20; // 列宽(字符数)
}

使用示例

1. 定义实体类

java 复制代码
public class Employee {
    @ExcelProperty("员工ID")
    private Long id;
    
    @ExcelProperty("员工姓名")
    private String name;
    
    @ExcelProperty(value = "入职日期", dateFormat = "yyyy年MM月dd日")
    private LocalDate hireDate;
    
    @ExcelProperty("薪资")
    private Double salary;
    
    @ExcelProperty("是否在职")
    private Boolean active;
    
    @ExcelIgnore
    private String password;
    
    // 省略 getter/setter
}

2. 导入 Excel 数据

java 复制代码
@PostMapping("/import")
public ResponseEntity<String> importEmployees(@RequestParam("file") MultipartFile file) {
    try {
        List<Employee> employees = ExcelImportUtil.importExcel(file, Employee.class);
        employeeService.saveAll(employees);
        return ResponseEntity.ok("成功导入 " + employees.size() + " 条数据");
    } catch (IOException e) {
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                            .body("导入失败: " + e.getMessage());
    }
}

3. 导出 Excel 数据

java 复制代码
@GetMapping("/export")
public void exportEmployees(HttpServletResponse response) throws IOException {
    List<Employee> employees = employeeService.getAllEmployees();
    
    ExcelExportUtil.exportExcel(
        response,
        employees,
        "员工数据报表",
        "员工信息",
        Employee.class
    );
}

技术要点解析

  1. 智能类型转换

    • 字符串自动转换为目标类型(数值、日期、布尔值)
    • 支持多种布尔值表示形式(是/否、YES/NO、TRUE/FALSE、1/0)
    • 日期格式自定义(通过注解)
  2. 容错处理

    • 导入时自动跳过空行
    • 错误行记录到控制台(生产环境应替换为日志框架)
    • 导出时错误字段标记为"[ERROR]"
  3. 性能优化

    • 使用 try-with-resources 确保资源关闭
    • 反射字段缓存提高性能
    • 流式输出避免内存溢出
  4. 扩展性

    • 支持自定义样式(修改 createHeaderStyle/createDataStyle 方法)
    • 支持自定义列宽(通过 ExcelColumnWidth 注解)
    • 支持多种数据类型扩展

依赖配置

xml 复制代码
<!-- Spring Boot Web -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>3.2.0</version>
</dependency>

<!-- Apache POI -->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>5.2.5</version>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>5.2.5</version>
</dependency>

<!-- Jakarta EE (兼容 Spring Boot 3.x) -->
<dependency>
    <groupId>jakarta.servlet</groupId>
    <artifactId>jakarta.servlet-api</artifactId>
    <version>6.0.0</version>
    <scope>provided</scope>
</dependency>

总结

本文分享的 Excel 导入导出工具类具有以下优势:

  1. 开箱即用:简单注解配置即可实现复杂导入导出功能
  2. 高度可定制:支持自定义列名、日期格式、列宽等
  3. 智能类型转换:自动处理常见数据类型转换
  4. 企业级健壮性:完善的错误处理和容错机制
  5. 性能优化:适合处理中小规模数据(<10万行)

在实际项目中,您可以根据业务需求进一步扩展功能,例如:

  • 添加数据校验逻辑
  • 支持多Sheet页导出
  • 添加导出进度提示
  • 集成数据字典转换

希望这套工具类能帮助您提升开发效率,欢迎在评论区交流使用心得!

相关推荐
晨启AI5 分钟前
Trae IDE:打造完美Java开发环境的实战指南
java·环境搭建·trae
C雨后彩虹18 分钟前
行为模式-策略模式
java·设计模式·策略模式
Ashlee_code23 分钟前
美联储降息趋缓叠加能源需求下调,泰国证券交易所新一代交易系统架构方案——高合规、强韧性、本地化的跨境金融基础设施解决方案
java·算法·金融·架构·系统架构·区块链·需求分析
西奥_26 分钟前
【JVM】运行时数据区域
java·jvm
lgx04060511243 分钟前
Maven详细解
java·maven
玩代码1 小时前
模板方法设计模式
java·开发语言·设计模式·模板方法设计模式
都叫我大帅哥1 小时前
Spring Cloud LoadBalancer:微服务世界的“吃货选餐厅”指南 🍜
java·spring cloud
摸鱼仙人~1 小时前
Spring Boot 参数校验:@Valid 与 @Validated
java·spring boot·后端
GeminiGlory1 小时前
从0到1开发网页版五子棋:我的Java实战之旅
java·开发语言
都叫我大帅哥2 小时前
🌈 深入浅出Java Ribbon:微服务负载均衡的艺术与避坑大全
java·spring cloud