本文目录导读:

我来提供一个完整的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;
}
}
性能优化建议
- 使用EasyExcel替代POI:EasyExcel内存占用更低,性能更好
- 合理设置批量大小:根据实际需求调整批量处理条数(建议100-500条)
- 使用多线程处理:大数据量时可以使用线程池并行处理
- 减少事务范围:不要将整个导入过程放在一个事务中
- 使用数据库批量插入:使用MyBatis的foreach或JDBC的batchUpdate
这就是一个完整的Java批量导入Excel案例,包含了EasyExcel和POI两种实现方式,以及异常处理和性能优化建议。