Java批量导入Excel案例

wen java案例 5

本文目录导读:

Java批量导入Excel案例

  1. 项目依赖配置
  2. 使用EasyExcel实现批量导入
  3. 使用Apache POI实现(传统方式)
  4. 异常数据处理示例
  5. 添加错误处理机制
  6. 性能优化建议

我来提供一个完整的Java批量导入Excel的案例,包含多种常用方式。

项目依赖配置

Maven依赖

<!-- Apache POI - 处理xls和xlsx -->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>5.2.3</version>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>5.2.3</version>
</dependency>
<!-- EasyExcel - 阿里开源,性能更好 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.3.2</version>
</dependency>
<!-- Lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.30</version>
</dependency>

使用EasyExcel实现批量导入

1 定义实体类

import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
@Data
public class UserExcelDTO {
    @ExcelProperty("用户名")
    private String username;
    @ExcelProperty("姓名")
    private String name;
    @ExcelProperty("年龄")
    private Integer age;
    @ExcelProperty("邮箱")
    private String email;
    @ExcelProperty("手机号")
    private String phone;
    @ExcelProperty("地址")
    private String address;
    @ExcelProperty("创建时间")
    private Date createTime;
}

2 定义Excel监听器

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.List;
@Slf4j
public class UserExcelListener extends AnalysisEventListener<UserExcelDTO> {
    // 每批处理的条数
    private static final int BATCH_COUNT = 100;
    // 批量处理接口
    private final ExcelDataHandler<UserExcelDTO> dataHandler;
    // 临时存储数据
    private final List<UserExcelDTO> dataList = new ArrayList<>();
    public UserExcelListener(ExcelDataHandler<UserExcelDTO> dataHandler) {
        this.dataHandler = dataHandler;
    }
    /**
     * 每解析一行数据会调用一次
     */
    @Override
    public void invoke(UserExcelDTO data, AnalysisContext context) {
        dataList.add(data);
        // 达到批量处理数量时,处理数据
        if (dataList.size() >= BATCH_COUNT) {
            processData();
            dataList.clear();
        }
    }
    /**
     * 解析完所有数据后调用
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 处理剩余数据
        if (!dataList.isEmpty()) {
            processData();
            dataList.clear();
        }
        log.info("Excel解析完成");
    }
    /**
     * 批量处理数据
     */
    private void processData() {
        try {
            dataHandler.handleData(new ArrayList<>(dataList));
        } catch (Exception e) {
            log.error("批量处理数据失败", e);
            throw new RuntimeException("批量处理数据失败: " + e.getMessage());
        }
    }
}

3 定义数据处理接口

public interface ExcelDataHandler<T> {
    /**
     * 批量处理数据
     */
    void handleData(List<T> dataList);
}

4 实现业务处理逻辑

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
public class UserImportService implements ExcelDataHandler<UserExcelDTO> {
    @Autowired
    private UserMapper userMapper;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void handleData(List<UserExcelDTO> dataList) {
        log.info("开始批量导入用户数据, 数量: {}", dataList.size());
        // 校验数据
        for (UserExcelDTO user : dataList) {
            // 数据校验
            validateUser(user);
        }
        // 批量插入
        int result = userMapper.batchInsert(dataList);
        log.info("批量导入完成, 影响行数: {}", result);
    }
    private void validateUser(UserExcelDTO user) {
        if (StringUtils.isBlank(user.getUsername())) {
            throw new RuntimeException("用户名不能为空");
        }
        // 检查用户名是否已存在
        User existingUser = userMapper.findByUsername(user.getUsername());
        if (existingUser != null) {
            throw new RuntimeException("用户名 " + user.getUsername() + " 已存在");
        }
        // 手机号格式校验
        if (StringUtils.isNotBlank(user.getPhone())) {
            if (!user.getPhone().matches("^1[3-9]\\d{9}$")) {
                throw new RuntimeException("手机号格式不正确: " + user.getPhone());
            }
        }
    }
}

5 Controller层实现

import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import com.alibaba.excel.EasyExcel;
@RestController
@RequestMapping("/api/user")
public class UserController {
    @Autowired
    private UserImportService userImportService;
    /**
     * 批量导入用户
     */
    @PostMapping("/import")
    public Result<?> importUsers(@RequestParam("file") MultipartFile file) {
        if (file.isEmpty()) {
            return Result.error("上传文件不能为空");
        }
        // 检查文件类型
        String filename = file.getOriginalFilename();
        if (!filename.endsWith(".xlsx") && !filename.endsWith(".xls")) {
            return Result.error("文件格式不正确,请上传Excel文件");
        }
        try {
            // 定义监听器
            UserExcelListener listener = new UserExcelListener(userImportService);
            // 读取Excel文件
            EasyExcel.read(file.getInputStream())
                    .head(UserExcelDTO.class)
                    .sheet()
                    .doRead();
            return Result.success("导入成功");
        } catch (Exception e) {
            log.error("导入Excel失败", e);
            return Result.error("导入失败: " + e.getMessage());
        }
    }
}

6 MyBatis批量插入

<insert id="batchInsert" parameterType="list">
    INSERT INTO user (username, name, age, email, phone, address, create_time)
    VALUES 
    <foreach collection="list" item="user" separator=",">
        (#{user.username}, #{user.name}, #{user.age}, #{user.email}, 
         #{user.phone}, #{user.address}, NOW())
    </foreach>
</insert>

使用Apache POI实现(传统方式)

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.web.multipart.MultipartFile;
@Service
public class ExcelImportService {
    /**
     * 使用POI解析Excel
     */
    public List<UserExcelDTO> parseExcel(MultipartFile file) throws Exception {
        List<UserExcelDTO> userList = new ArrayList<>();
        try (Workbook workbook = WorkbookFactory.create(file.getInputStream())) {
            Sheet sheet = workbook.getSheetAt(0); // 第一个sheet
            // 跳过表头(第一行)
            for (int rowNum = 1; rowNum <= sheet.getLastRowNum(); rowNum++) {
                Row row = sheet.getRow(rowNum);
                if (row == null) {
                    continue;
                }
                // 跳过空行
                if (isRowEmpty(row)) {
                    continue;
                }
                UserExcelDTO user = new UserExcelDTO();
                user.setUsername(getCellValue(row.getCell(0)));
                user.setName(getCellValue(row.getCell(1)));
                user.setAge(Integer.parseInt(getCellValue(row.getCell(2))));
                user.setEmail(getCellValue(row.getCell(3)));
                user.setPhone(getCellValue(row.getCell(4)));
                user.setAddress(getCellValue(row.getCell(5)));
                userList.add(user);
                // 每1000条处理一次
                if (userList.size() == 1000) {
                    batchImport(userList);
                    userList.clear();
                }
            }
            // 处理剩余数据
            if (!userList.isEmpty()) {
                batchImport(userList);
            }
        }
        return userList;
    }
    /**
     * 获取单元格值
     */
    private String getCellValue(Cell cell) {
        if (cell == null) {
            return null;
        }
        switch (cell.getCellType()) {
            case STRING:
                return cell.getStringCellValue();
            case NUMERIC:
                if (DateUtil.isCellDateFormatted(cell)) {
                    return cell.getDateCellValue().toString();
                }
                return String.valueOf(cell.getNumericCellValue());
            case BOOLEAN:
                return String.valueOf(cell.getBooleanCellValue());
            default:
                return null;
        }
    }
    /**
     * 判断行是否为空
     */
    private boolean isRowEmpty(Row row) {
        for (int i = 0; i < row.getLastCellNum(); i++) {
            Cell cell = row.getCell(i);
            if (cell != null && cell.getCellType() != CellType.BLANK) {
                return false;
            }
        }
        return true;
    }
    /**
     * 批量导入
     */
    @Transactional(rollbackFor = Exception.class)
    public void batchImport(List<UserExcelDTO> userList) {
        // 批量插入数据库
        // ...
    }
}

异常数据处理示例

import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import com.alibaba.excel.converters.Converter;
/**
 * 自定义转换器示例
 */
public class CustomDateConverter implements Converter<Date> {
    @Override
    public Date convertToJavaData(ReadCellData<?> cellData, 
                                  ExcelContentProperty contentProperty,
                                  GlobalConfiguration globalConfiguration) {
        String value = cellData.getStringValue();
        try {
            return new SimpleDateFormat("yyyy-MM-dd").parse(value);
        } catch (Exception e) {
            throw new RuntimeException("日期格式错误: " + value);
        }
    }
}

添加错误处理机制

public class ImportException extends RuntimeException {
    private int rowNum;
    private String message;
    public ImportException(int rowNum, String message) {
        super("第" + rowNum + "行: " + message);
        this.rowNum = rowNum;
        this.message = message;
    }
}

性能优化建议

  1. 使用EasyExcel替代POI:EasyExcel内存占用更低,性能更好
  2. 合理设置批量大小:根据实际需求调整批量处理条数(建议100-500条)
  3. 使用多线程处理:大数据量时可以使用线程池并行处理
  4. 减少事务范围:不要将整个导入过程放在一个事务中
  5. 使用数据库批量插入:使用MyBatis的foreach或JDBC的batchUpdate

这就是一个完整的Java批量导入Excel案例,包含了EasyExcel和POI两种实现方式,以及异常处理和性能优化建议。

抱歉,评论功能暂时关闭!