springboot中excel处理指南
1. excel处理基础知识
1.1 为什么需要在应用中处理excel文件?
在企业应用开发中,excel文件处理是一个非常常见的需求,主要用于以下场景:
- 数据导入:允许用户通过excel上传批量数据到系统
- 数据导出:将系统数据导出为excel供用户下载分析
- 报表生成:生成复杂的报表并格式化为excel
- 数据交换:作为不同系统间交换数据的媒介
- 批量数据处理:处理大量结构化数据
1.2 java中的excel处理库介绍
java中处理excel文件的主要库有以下几种:
1.2.1 apache poi
apache poi是java中使用最广泛的excel处理库,提供了全面的api来创建、读取和修改office文档。
优点:
- 功能全面,支持excel所有功能
- 支持.xls (hssf - excel 97-2003)和.xlsx (xssf - excel 2007+)格式
- 社区活跃,文档丰富
- 支持公式计算、图表、合并单元格等高级功能
缺点:
- api相对复杂
- 处理大文件时内存消耗大(尤其是xssf)
1.2.2 easyexcel
easyexcel是阿里巴巴开源的excel处理库,基于poi,但做了大量优化。
优点:
- 内存占用低,使用sax模式读取,避免oom
- api简单易用,注解驱动
- 读写速度快
- 适合处理大型excel文件
缺点:
- 功能不如poi全面
- 灵活性相对较低
1.2.3 jexcel
jexcel是另一个处理excel的java库。
优点:
- api较简单
- 速度较快
缺点:
- 仅支持旧版excel (.xls)格式
- 不再积极维护
- 功能有限
1.2.4 apache poi sxssf
sxssf是poi提供的一种流式处理模式,专为处理大型excel文件设计。
优点:
- 大大降低内存占用
- 适合生成大型excel文件
缺点:
- 仅支持写入操作,不支持读取
- 功能比xssf受限
1.3 spring boot中集成excel处理
spring boot本身不提供excel处理功能,但可以轻松集成上述各种excel处理库。本指南将主要介绍:
- 如何在spring boot项目中集成apache poi和easyexcel
- 如何实现excel导入导出的常见功能
- 如何处理常见问题和优化性能
2. 在spring boot中集成excel处理库
2.1 集成apache poi
2.1.1 添加依赖
在pom.xml文件中添加以下依赖:
<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>如果使用gradle,在build.gradle中添加:
implementation 'org.apache.poi:poi:5.2.3' implementation 'org.apache.poi:poi-ooxml:5.2.3'
2.1.2 创建基本配置类
创建一个配置类来处理excel相关的配置:
package com.example.excel.config;
import org.apache.poi.ss.usermodel.workbookfactory;
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;
import org.springframework.web.multipart.multipartresolver;
import org.springframework.web.multipart.commons.commonsmultipartresolver;
@configuration
public class excelconfig {
@bean
public multipartresolver multipartresolver() {
commonsmultipartresolver resolver = new commonsmultipartresolver();
resolver.setmaxuploadsize(10485760); // 设置上传文件最大为10mb
return resolver;
}
}2.2 集成easyexcel
2.2.1 添加依赖
在pom.xml文件中添加以下依赖:
<dependency>
<groupid>com.alibaba</groupid>
<artifactid>easyexcel</artifactid>
<version>3.2.1</version>
</dependency>如果使用gradle,在build.gradle中添加:
implementation 'com.alibaba:easyexcel:3.2.1'
2.2.2 创建配置类
package com.example.excel.config;
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;
import org.springframework.web.multipart.multipartresolver;
import org.springframework.web.multipart.commons.commonsmultipartresolver;
@configuration
public class easyexcelconfig {
@bean
public multipartresolver multipartresolver() {
commonsmultipartresolver resolver = new commonsmultipartresolver();
resolver.setmaxuploadsize(10485760); // 设置上传文件最大为10mb
return resolver;
}
}3. 使用apache poi读取excel文件
3.1 创建数据模型
首先,创建一个模型类来映射excel中的数据:
package com.example.excel.model;
import lombok.data;
@data
public class user {
private long id;
private string name;
private integer age;
private string email;
private string department;
}3.2 创建excel读取服务
创建一个服务类来处理excel文件读取:
package com.example.excel.service;
import com.example.excel.model.user;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.xssfworkbook;
import org.springframework.stereotype.service;
import org.springframework.web.multipart.multipartfile;
import java.io.ioexception;
import java.io.inputstream;
import java.util.arraylist;
import java.util.iterator;
import java.util.list;
@service
public class excelservice {
public list<user> readusersfromexcel(multipartfile file) throws ioexception {
list<user> userlist = new arraylist<>();
// 获取工作簿
try (inputstream inputstream = file.getinputstream()) {
workbook workbook = workbookfactory.create(inputstream);
// 获取第一个工作表
sheet sheet = workbook.getsheetat(0);
// 跳过标题行
iterator<row> rowiterator = sheet.rowiterator();
if (rowiterator.hasnext()) {
rowiterator.next(); // 跳过标题行
}
// 遍历数据行
while (rowiterator.hasnext()) {
row row = rowiterator.next();
user user = new user();
// 读取单元格数据
user.setid((long) row.getcell(0, row.missingcellpolicy.create_null_as_blank).getnumericcellvalue());
user.setname(getcellvalueasstring(row.getcell(1)));
user.setage((int) row.getcell(2, row.missingcellpolicy.create_null_as_blank).getnumericcellvalue());
user.setemail(getcellvalueasstring(row.getcell(3)));
user.setdepartment(getcellvalueasstring(row.getcell(4)));
userlist.add(user);
}
workbook.close();
}
return userlist;
}
// 获取单元格的字符串值
private string getcellvalueasstring(cell cell) {
if (cell == null) {
return "";
}
switch (cell.getcelltype()) {
case string:
return cell.getstringcellvalue();
case numeric:
if (dateutil.iscelldateformatted(cell)) {
return cell.getdatecellvalue().tostring();
} else {
return string.valueof((int) cell.getnumericcellvalue());
}
case boolean:
return string.valueof(cell.getbooleancellvalue());
case formula:
return cell.getcellformula();
default:
return "";
}
}
}3.3 创建controller处理excel上传
创建一个controller来处理excel文件上传:
package com.example.excel.controller;
import com.example.excel.model.user;
import com.example.excel.service.excelservice;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.http.responseentity;
import org.springframework.web.bind.annotation.postmapping;
import org.springframework.web.bind.annotation.requestmapping;
import org.springframework.web.bind.annotation.requestparam;
import org.springframework.web.bind.annotation.restcontroller;
import org.springframework.web.multipart.multipartfile;
import java.io.ioexception;
import java.util.list;
@restcontroller
@requestmapping("/api/excel")
public class excelcontroller {
@autowired
private excelservice excelservice;
@postmapping("/upload")
public responseentity<list<user>> uploadexcel(@requestparam("file") multipartfile file) {
try {
list<user> users = excelservice.readusersfromexcel(file);
return responseentity.ok(users);
} catch (ioexception e) {
e.printstacktrace();
return responseentity.badrequest().build();
}
}
}3.4 创建html上传页面
在src/main/resources/templates目录下创建upload.html:
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<title>excel上传</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="external nofollow" rel="external nofollow" >
</head>
<body>
<div class="container mt-5">
<div class="card">
<div class="card-header">
<h3>上传excel文件</h3>
</div>
<div class="card-body">
<form id="uploadform" enctype="multipart/form-data">
<div class="form-group">
<label for="file">选择excel文件:</label>
<input type="file" class="form-control-file" id="file" name="file" accept=".xls,.xlsx">
</div>
<button type="button" class="btn btn-primary" onclick="uploadexcel()">上传</button>
</form>
<div class="mt-4">
<h4>上传结果:</h4>
<div id="resultcontainer"></div>
</div>
</div>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script>
function uploadexcel() {
var formdata = new formdata(document.getelementbyid('uploadform'));
$.ajax({
url: '/api/excel/upload',
type: 'post',
data: formdata,
processdata: false,
contenttype: false,
success: function(response) {
var resulthtml = '<table class="table table-striped">' +
'<thead><tr><th>id</th><th>姓名</th><th>年龄</th><th>邮箱</th><th>部门</th></tr></thead>' +
'<tbody>';
for (var i = 0; i < response.length; i++) {
var user = response[i];
resulthtml += '<tr>' +
'<td>' + user.id + '</td>' +
'<td>' + user.name + '</td>' +
'<td>' + user.age + '</td>' +
'<td>' + user.email + '</td>' +
'<td>' + user.department + '</td>' +
'</tr>';
}
resulthtml += '</tbody></table>';
$('#resultcontainer').html(resulthtml);
},
error: function(error) {
$('#resultcontainer').html('<div class="alert alert-danger">上传失败: ' + error.responsetext + '</div>');
}
});
}
</script>
</body>
</html>3.5 处理更复杂的excel结构
在实际应用中,excel结构可能更复杂,如多个工作表、合并单元格、公式等。以下是处理这些情况的示例:
public list<department> readcomplexexcel(multipartfile file) throws ioexception {
list<department> departments = new arraylist<>();
try (inputstream inputstream = file.getinputstream()) {
workbook workbook = workbookfactory.create(inputstream);
// 读取部门信息(第一个工作表)
sheet departmentsheet = workbook.getsheetat(0);
for (int i = 1; i <= departmentsheet.getlastrownum(); i++) {
row row = departmentsheet.getrow(i);
if (row == null) continue;
department department = new department();
department.setid((long) row.getcell(0).getnumericcellvalue());
department.setname(row.getcell(1).getstringcellvalue());
department.setmanager(row.getcell(2).getstringcellvalue());
department.setemployees(new arraylist<>());
departments.add(department);
}
// 读取员工信息(第二个工作表)
sheet employeesheet = workbook.getsheetat(1);
for (int i = 1; i <= employeesheet.getlastrownum(); i++) {
row row = employeesheet.getrow(i);
if (row == null) continue;
user employee = new user();
employee.setid((long) row.getcell(0).getnumericcellvalue());
employee.setname(row.getcell(1).getstringcellvalue());
employee.setage((int) row.getcell(2).getnumericcellvalue());
employee.setemail(row.getcell(3).getstringcellvalue());
// 获取部门id并关联到相应部门
long departmentid = (long) row.getcell(4).getnumericcellvalue();
for (department dept : departments) {
if (dept.getid() == departmentid) {
dept.getemployees().add(employee);
break;
}
}
}
workbook.close();
}
return departments;
}4. 使用apache poi创建和导出excel文件
4.1 创建基本excel文件
以下是一个创建简单excel文件的示例:
package com.example.excel.service;
import com.example.excel.model.user;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.xssfworkbook;
import org.springframework.stereotype.service;
import java.io.bytearrayinputstream;
import java.io.bytearrayoutputstream;
import java.io.ioexception;
import java.util.list;
@service
public class excelexportservice {
public bytearrayinputstream exportuserstoexcel(list<user> users) throws ioexception {
try (workbook workbook = new xssfworkbook()) {
// 创建工作表
sheet sheet = workbook.createsheet("用户数据");
// 创建表头样式
font headerfont = workbook.createfont();
headerfont.setbold(true);
headerfont.setcolor(indexedcolors.blue.getindex());
cellstyle headercellstyle = workbook.createcellstyle();
headercellstyle.setfont(headerfont);
headercellstyle.setfillforegroundcolor(indexedcolors.light_yellow.getindex());
headercellstyle.setfillpattern(fillpatterntype.solid_foreground);
headercellstyle.setborderbottom(borderstyle.thin);
headercellstyle.setbordertop(borderstyle.thin);
headercellstyle.setborderright(borderstyle.thin);
headercellstyle.setborderleft(borderstyle.thin);
// 创建表头行
row headerrow = sheet.createrow(0);
// 创建表头单元格
cell cell0 = headerrow.createcell(0);
cell0.setcellvalue("id");
cell0.setcellstyle(headercellstyle);
cell cell1 = headerrow.createcell(1);
cell1.setcellvalue("姓名");
cell1.setcellstyle(headercellstyle);
cell cell2 = headerrow.createcell(2);
cell2.setcellvalue("年龄");
cell2.setcellstyle(headercellstyle);
cell cell3 = headerrow.createcell(3);
cell3.setcellvalue("邮箱");
cell3.setcellstyle(headercellstyle);
cell cell4 = headerrow.createcell(4);
cell4.setcellvalue("部门");
cell4.setcellstyle(headercellstyle);
// 设置数据单元格样式
cellstyle datacellstyle = workbook.createcellstyle();
datacellstyle.setborderbottom(borderstyle.thin);
datacellstyle.setbordertop(borderstyle.thin);
datacellstyle.setborderright(borderstyle.thin);
datacellstyle.setborderleft(borderstyle.thin);
// 创建数据行
int rowidx = 1;
for (user user : users) {
row row = sheet.createrow(rowidx++);
cell idcell = row.createcell(0);
idcell.setcellvalue(user.getid());
idcell.setcellstyle(datacellstyle);
cell namecell = row.createcell(1);
namecell.setcellvalue(user.getname());
namecell.setcellstyle(datacellstyle);
cell agecell = row.createcell(2);
agecell.setcellvalue(user.getage());
agecell.setcellstyle(datacellstyle);
cell emailcell = row.createcell(3);
emailcell.setcellvalue(user.getemail());
emailcell.setcellstyle(datacellstyle);
cell deptcell = row.createcell(4);
deptcell.setcellvalue(user.getdepartment());
deptcell.setcellstyle(datacellstyle);
}
// 自动调整列宽
for (int i = 0; i < 5; i++) {
sheet.autosizecolumn(i);
}
// 写入bytearrayoutputstream
bytearrayoutputstream outputstream = new bytearrayoutputstream();
workbook.write(outputstream);
return new bytearrayinputstream(outputstream.tobytearray());
}
}
}4.2 创建导出控制器
package com.example.excel.controller;
import com.example.excel.model.user;
import com.example.excel.service.excelexportservice;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.core.io.inputstreamresource;
import org.springframework.http.httpheaders;
import org.springframework.http.mediatype;
import org.springframework.http.responseentity;
import org.springframework.web.bind.annotation.getmapping;
import org.springframework.web.bind.annotation.requestmapping;
import org.springframework.web.bind.annotation.restcontroller;
import java.io.bytearrayinputstream;
import java.io.ioexception;
import java.util.arraylist;
import java.util.list;
@restcontroller
@requestmapping("/api/excel")
public class excelexportcontroller {
@autowired
private excelexportservice excelexportservice;
@getmapping("/export")
public responseentity<inputstreamresource> exportusers() throws ioexception {
// 生成示例数据
list<user> users = gettestusers();
// 生成excel文件
bytearrayinputstream in = excelexportservice.exportuserstoexcel(users);
// 设置http头
httpheaders headers = new httpheaders();
headers.add("content-disposition", "attachment; filename=users.xlsx");
// 返回excel文件
return responseentity
.ok()
.headers(headers)
.contenttype(mediatype.parsemediatype("application/vnd.ms-excel"))
.body(new inputstreamresource(in));
}
// 生成测试用户数据
private list<user> gettestusers() {
list<user> users = new arraylist<>();
user user1 = new user();
user1.setid(1l);
user1.setname("张三");
user1.setage(28);
user1.setemail("zhangsan@example.com");
user1.setdepartment("研发部");
users.add(user1);
user user2 = new user();
user2.setid(2l);
user2.setname("李四");
user2.setage(32);
user2.setemail("lisi@example.com");
user2.setdepartment("市场部");
users.add(user2);
user user3 = new user();
user3.setid(3l);
user3.setname("王五");
user3.setage(45);
user3.setemail("wangwu@example.com");
user3.setdepartment("行政部");
users.add(user3);
user user4 = new user();
user4.setid(4l);
user4.setname("赵六");
user4.setage(36);
user4.setemail("zhaoliu@example.com");
user4.setdepartment("财务部");
users.add(user4);
user user5 = new user();
user5.setid(5l);
user5.setname("钱七");
user5.setage(29);
user5.setemail("qianqi@example.com");
user5.setdepartment("人力资源部");
users.add(user5);
return users;
}
}4.3 创建导出页面
在src/main/resources/templates目录下创建export.html:
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<title>excel导出</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="external nofollow" rel="external nofollow" >
</head>
<body>
<div class="container mt-5">
<div class="card">
<div class="card-header">
<h3>导出excel文件</h3>
</div>
<div class="card-body">
<p>点击下面的按钮导出用户数据到excel文件:</p>
<a href="/api/excel/export" rel="external nofollow" class="btn btn-primary">导出用户数据</a>
</div>
</div>
</div>
</body>
</html>4.4 创建复杂的excel文件
以下是一个创建更复杂excel文件的示例,包含多个工作表、合并单元格、公式等:
public bytearrayinputstream exportcomplexexcel(list<department> departments) throws ioexception {
try (workbook workbook = new xssfworkbook()) {
// 创建字体和样式
font headerfont = workbook.createfont();
headerfont.setbold(true);
headerfont.setfontheightinpoints((short) 14);
cellstyle headerstyle = workbook.createcellstyle();
headerstyle.setfont(headerfont);
headerstyle.setfillforegroundcolor(indexedcolors.light_blue.getindex());
headerstyle.setfillpattern(fillpatterntype.solid_foreground);
headerstyle.setalignment(horizontalalignment.center);
cellstyle titlestyle = workbook.createcellstyle();
font titlefont = workbook.createfont();
titlefont.setbold(true);
titlefont.setfontheightinpoints((short) 16);
titlestyle.setfont(titlefont);
titlestyle.setalignment(horizontalalignment.center);
// 创建汇总表
sheet summarysheet = workbook.createsheet("部门汇总");
// 创建标题行
row titlerow = summarysheet.createrow(0);
cell titlecell = titlerow.createcell(0);
titlecell.setcellvalue("公司部门人员统计");
titlecell.setcellstyle(titlestyle);
// 合并标题单元格
summarysheet.addmergedregion(new cellrangeaddress(0, 0, 0, 3));
// 创建表头
row headerrow = summarysheet.createrow(1);
string[] headers = {"部门id", "部门名称", "部门经理", "员工数量"};
for (int i = 0; i < headers.length; i++) {
cell cell = headerrow.createcell(i);
cell.setcellvalue(headers[i]);
cell.setcellstyle(headerstyle);
}
// 填充部门数据
int rowidx = 2;
int totalemployees = 0;
for (department dept : departments) {
row row = summarysheet.createrow(rowidx++);
row.createcell(0).setcellvalue(dept.getid());
row.createcell(1).setcellvalue(dept.getname());
row.createcell(2).setcellvalue(dept.getmanager());
row.createcell(3).setcellvalue(dept.getemployees().size());
totalemployees += dept.getemployees().size();
// 为每个部门创建单独的工作表
sheet deptsheet = workbook.createsheet(dept.getname());
// 创建部门表头
row deptheaderrow = deptsheet.createrow(0);
cell depttitlecell = deptheaderrow.createcell(0);
depttitlecell.setcellvalue(dept.getname() + " - 员工列表");
depttitlecell.setcellstyle(titlestyle);
deptsheet.addmergedregion(new cellrangeaddress(0, 0, 0, 4));
// 员工表头
row empheaderrow = deptsheet.createrow(1);
string[] empheaders = {"员工id", "姓名", "年龄", "邮箱", "入职年限"};
for (int i = 0; i < empheaders.length; i++) {
cell cell = empheaderrow.createcell(i);
cell.setcellvalue(empheaders[i]);
cell.setcellstyle(headerstyle);
}
// 填充员工数据
int emprowidx = 2;
for (user emp : dept.getemployees()) {
row emprow = deptsheet.createrow(emprowidx++);
emprow.createcell(0).setcellvalue(emp.getid());
emprow.createcell(1).setcellvalue(emp.getname());
emprow.createcell(2).setcellvalue(emp.getage());
emprow.createcell(3).setcellvalue(emp.getemail());
// 使用公式计算入职年限(假设年龄减去25)
cell tenurecell = emprow.createcell(4);
tenurecell.setcellformula("c" + emprowidx + "-25");
}
// 自动调整列宽
for (int i = 0; i < 5; i++) {
deptsheet.autosizecolumn(i);
}
}
// 创建总计行
row totalrow = summarysheet.createrow(rowidx);
cell totallabelcell = totalrow.createcell(0);
totallabelcell.setcellvalue("总计");
totallabelcell.setcellstyle(headerstyle);
// 合并总计标签单元格
summarysheet.addmergedregion(new cellrangeaddress(rowidx, rowidx, 0, 2));
cell totalvaluecell = totalrow.createcell(3);
totalvaluecell.setcellvalue(totalemployees);
totalvaluecell.setcellstyle(headerstyle);
// 自动调整列宽
for (int i = 0; i < 4; i++) {
summarysheet.autosizecolumn(i);
}
// 添加图表
xssfsheet chartsheet = (xssfsheet) workbook.createsheet("部门统计图");
// 复制部门数据到图表数据表
row chartheaderrow = chartsheet.createrow(0);
chartheaderrow.createcell(0).setcellvalue("部门");
chartheaderrow.createcell(1).setcellvalue("员工数");
int chartrowidx = 1;
for (department dept : departments) {
row row = chartsheet.createrow(chartrowidx++);
row.createcell(0).setcellvalue(dept.getname());
row.createcell(1).setcellvalue(dept.getemployees().size());
}
// 创建图表和数据序列
xssfdrawing drawing = chartsheet.createdrawingpatriarch();
xssfclientanchor anchor = drawing.createanchor(0, 0, 0, 0, 4, 0, 15, 15);
xssfchart chart = drawing.createchart(anchor);
chart.settitletext("部门人员分布");
chart.settitleoverlay(false);
xddfchartlegend legend = chart.getoraddlegend();
legend.setposition(legendposition.right);
// x轴和y轴
xddfcategoryaxis bottomaxis = chart.createcategoryaxis(axisposition.bottom);
bottomaxis.settitle("部门");
xddfvalueaxis leftaxis = chart.createvalueaxis(axisposition.left);
leftaxis.settitle("员工数");
// 创建数据源
xddfdatasource<string> departments = xddfdatasourcesfactory.fromstringcellrange(
chartsheet, new cellrangeaddress(1, chartrowidx - 1, 0, 0));
xddfnumericaldatasource<double> values = xddfdatasourcesfactory.fromnumericcellrange(
chartsheet, new cellrangeaddress(1, chartrowidx - 1, 1, 1));
// 创建柱状图
xddfbarchartdata barchart = (xddfbarchartdata) chart.createdata(
charttypes.bar, bottomaxis, leftaxis);
barchart.setvarycolors(true);
xddfbarchartdata.series series = (xddfbarchartdata.series) barchart.addseries(departments, values);
series.settitle("员工数", null);
chart.plot(barchart);
// 写入bytearrayoutputstream
bytearrayoutputstream outputstream = new bytearrayoutputstream();
workbook.write(outputstream);
return new bytearrayinputstream(outputstream.tobytearray());
}
}注意:上面的图表代码需要添加以下依赖:
<dependency>
<groupid>org.apache.poi</groupid>
<artifactid>poi-ooxml-full</artifactid>
<version>5.2.3</version>
</dependency>4.5 使用模板导出excel
在某些场景下,我们需要基于预定义的excel模板生成文件,以下是一个示例:
public bytearrayinputstream exportfromtemplate(list<user> users) throws ioexception {
// 加载模板文件
try (inputstream templatestream = getclass().getresourceasstream("/templates/user_template.xlsx");
workbook workbook = workbookfactory.create(templatestream)) {
sheet sheet = workbook.getsheetat(0);
// 从第二行开始填充数据(第一行是表头)
int rowidx = 1;
for (user user : users) {
row row = sheet.createrow(rowidx++);
row.createcell(0).setcellvalue(user.getid());
row.createcell(1).setcellvalue(user.getname());
row.createcell(2).setcellvalue(user.getage());
row.createcell(3).setcellvalue(user.getemail());
row.createcell(4).setcellvalue(user.getdepartment());
}
// 更新模板中的日期单元格(假设在a1位置)
row headerrow = sheet.getrow(0);
if (headerrow.getcell(6) != null) {
cell datecell = headerrow.getcell(6);
datecell.setcellvalue(new date());
}
// 自动调整列宽
for (int i = 0; i < 5; i++) {
sheet.autosizecolumn(i);
}
// 写入bytearrayoutputstream
bytearrayoutputstream outputstream = new bytearrayoutputstream();
workbook.write(outputstream);
return new bytearrayinputstream(outputstream.tobytearray());
}
}5. 使用easyexcel处理excel文件
easyexcel是阿里巴巴开源的基于poi的excel处理工具,相比原生poi,它提供了更简洁的api,并且在处理大文件时有明显的性能优势。
5.1 使用easyexcel读取excel
5.1.1 创建数据模型
使用easyexcel时,通常使用注解来映射excel列:
package com.example.excel.model;
import com.alibaba.excel.annotation.excelproperty;
import com.alibaba.excel.annotation.format.datetimeformat;
import lombok.data;
import java.util.date;
@data
public class employee {
@excelproperty("员工id")
private long id;
@excelproperty("姓名")
private string name;
@excelproperty("年龄")
private integer age;
@excelproperty("邮箱")
private string email;
@excelproperty("部门")
private string department;
@excelproperty("入职日期")
@datetimeformat("yyyy-mm-dd")
private date hiredate;
@excelproperty("薪资")
private double salary;
}5.1.2 创建读取监听器
easyexcel采用事件模式读取excel,需要创建一个监听器来处理读取的数据:
package com.example.excel.listener;
import com.alibaba.excel.context.analysiscontext;
import com.alibaba.excel.event.analysiseventlistener;
import com.example.excel.model.employee;
import lombok.extern.slf4j.slf4j;
import java.util.arraylist;
import java.util.list;
@slf4j
public class employeereadlistener extends analysiseventlistener<employee> {
/**
* 用于暂存读取的数据
*/
private list<employee> employeelist = new arraylist<>();
/**
* 每读取一行数据就会调用一次invoke方法
*/
@override
public void invoke(employee employee, analysiscontext context) {
log.info("读取到一条数据: {}", employee);
employeelist.add(employee);
// 达到batch_count时,需要存储一次数据库,防止数据几万条数据在内存,容易oom
if (employeelist.size() >= 5000) {
savedata();
// 清理内存
employeelist.clear();
}
}
/**
* 所有数据解析完成后调用此方法
*/
@override
public void doafterallanalysed(analysiscontext context) {
// 确保最后一批数据被保存
savedata();
log.info("所有数据解析完成!");
}
/**
* 保存数据,这里只是打印,实际应用中可以将数据存入数据库
*/
private void savedata() {
log.info("{}条数据,开始保存数据库!", employeelist.size());
// 这里可以调用持久层完成数据入库
log.info("存储数据库成功!");
}
/**
* 获取读取到的数据
*/
public list<employee> getemployeelist() {
return employeelist;
}
}5.1.3 创建excel读取服务
package com.example.excel.service;
import com.alibaba.excel.easyexcel;
import com.example.excel.listener.employeereadlistener;
import com.example.excel.model.employee;
import lombok.extern.slf4j.slf4j;
import org.springframework.stereotype.service;
import org.springframework.web.multipart.multipartfile;
import java.io.ioexception;
import java.util.list;
@slf4j
@service
public class easyexcelservice {
public list<employee> reademployeedata(multipartfile file) throws ioexception {
employeereadlistener listener = new employeereadlistener();
easyexcel.read(file.getinputstream(), employee.class, listener).sheet().doread();
return listener.getemployeelist();
}
}5.1.4 创建controller
package com.example.excel.controller;
import com.example.excel.model.employee;
import com.example.excel.service.easyexcelservice;
import lombok.extern.slf4j.slf4j;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.http.responseentity;
import org.springframework.web.bind.annotation.postmapping;
import org.springframework.web.bind.annotation.requestmapping;
import org.springframework.web.bind.annotation.requestparam;
import org.springframework.web.bind.annotation.restcontroller;
import org.springframework.web.multipart.multipartfile;
import java.io.ioexception;
import java.util.list;
@slf4j
@restcontroller
@requestmapping("/api/easyexcel")
public class easyexcelcontroller {
@autowired
private easyexcelservice easyexcelservice;
@postmapping("/upload")
public responseentity<list<employee>> uploadexcel(@requestparam("file") multipartfile file) {
try {
list<employee> employees = easyexcelservice.reademployeedata(file);
return responseentity.ok(employees);
} catch (ioexception e) {
log.error("excel读取失败", e);
return responseentity.badrequest().build();
}
}
}5.2 使用easyexcel导出excel
5.2.1 简单导出示例
package com.example.excel.service;
import com.alibaba.excel.easyexcel;
import com.alibaba.excel.write.style.column.longestmatchcolumnwidthstylestrategy;
import com.example.excel.model.employee;
import org.springframework.stereotype.service;
import java.io.file;
import java.io.ioexception;
import java.io.outputstream;
import java.util.list;
@service
public class easyexcelexportservice {
/**
* 导出员工数据到excel文件
*/
public void exportemployees(list<employee> employees, outputstream outputstream) {
easyexcel.write(outputstream, employee.class)
.registerwritehandler(new longestmatchcolumnwidthstylestrategy()) // 自动调整列宽
.sheet("员工数据")
.dowrite(employees);
}
/**
* 导出员工数据到指定文件
*/
public void exportemployeestofile(list<employee> employees, string filename) throws ioexception {
// 确保目录存在
file file = new file(filename);
if (!file.getparentfile().exists()) {
file.getparentfile().mkdirs();
}
easyexcel.write(filename, employee.class)
.registerwritehandler(new longestmatchcolumnwidthstylestrategy())
.sheet("员工数据")
.dowrite(employees);
}
/**
* 导出多个sheet的excel
*/
public void exportmultiplesheets(list<list<employee>> departmentemployees,
list<string> sheetnames,
outputstream outputstream) {
// 创建excelwriter
try (var excelwriter = easyexcel.write(outputstream, employee.class)
.registerwritehandler(new longestmatchcolumnwidthstylestrategy())
.build()) {
// 同一个对象多个sheet写入
for (int i = 0; i < departmentemployees.size(); i++) {
// 获取sheet名称
string sheetname = i < sheetnames.size() ? sheetnames.get(i) : "sheet" + (i + 1);
// 创建新的sheet
var writesheet = easyexcel.writersheet(i, sheetname).build();
// 写入数据
excelwriter.write(departmentemployees.get(i), writesheet);
}
}
}
}5.2.2 创建controller
package com.example.excel.controller;
import com.example.excel.model.employee;
import com.example.excel.service.easyexcelexportservice;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.web.bind.annotation.getmapping;
import org.springframework.web.bind.annotation.requestmapping;
import org.springframework.web.bind.annotation.restcontroller;
import javax.servlet.http.httpservletresponse;
import java.io.ioexception;
import java.net.urlencoder;
import java.nio.charset.standardcharsets;
import java.util.arraylist;
import java.util.arrays;
import java.util.date;
import java.util.list;
@restcontroller
@requestmapping("/api/easyexcel")
public class easyexcelexportcontroller {
@autowired
private easyexcelexportservice exportservice;
@getmapping("/export")
public void exportemployees(httpservletresponse response) throws ioexception {
// 设置响应内容
response.setcontenttype("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setcharacterencoding("utf-8");
// 设置文件名
string filename = urlencoder.encode("员工数据", standardcharsets.utf_8).replaceall("\\+", "%20");
response.setheader("content-disposition", "attachment;filename*=utf-8''" + filename + ".xlsx");
// 获取测试数据
list<employee> employees = gettestemployees();
// 导出excel
exportservice.exportemployees(employees, response.getoutputstream());
}
@getmapping("/export-multiple-sheets")
public void exportmultiplesheets(httpservletresponse response) throws ioexception {
// 设置响应内容
response.setcontenttype("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setcharacterencoding("utf-8");
// 设置文件名
string filename = urlencoder.encode("部门员工数据", standardcharsets.utf_8).replaceall("\\+", "%20");
response.setheader("content-disposition", "attachment;filename*=utf-8''" + filename + ".xlsx");
// 获取测试数据 - 三个部门的员工
list<list<employee>> departmentemployees = new arraylist<>();
departmentemployees.add(getemployeesbydepartment("研发部"));
departmentemployees.add(getemployeesbydepartment("市场部"));
departmentemployees.add(getemployeesbydepartment("行政部"));
// sheet名称
list<string> sheetnames = arrays.aslist("研发部员工", "市场部员工", "行政部员工");
// 导出excel
exportservice.exportmultiplesheets(departmentemployees, sheetnames, response.getoutputstream());
}
/**
* 生成测试员工数据
*/
private list<employee> gettestemployees() {
list<employee> employees = new arraylist<>();
// 添加测试数据
for (int i = 1; i <= 10; i++) {
employee employee = new employee();
employee.setid((long) i);
employee.setname("员工" + i);
employee.setage(20 + i);
employee.setemail("employee" + i + "@example.com");
employee.setdepartment(i % 3 == 0 ? "研发部" : (i % 3 == 1 ? "市场部" : "行政部"));
employee.sethiredate(new date());
employee.setsalary(5000.0 + i * 1000);
employees.add(employee);
}
return employees;
}
/**
* 根据部门获取员工
*/
private list<employee> getemployeesbydepartment(string department) {
list<employee> allemployees = gettestemployees();
list<employee> departmentemployees = new arraylist<>();
for (employee employee : allemployees) {
if (department.equals(employee.getdepartment())) {
departmentemployees.add(employee);
}
}
return departmentemployees;
}
// ... 5.2.3 使用自定义样式和复杂表头
/**
* 导出自定义样式的excel
*/
public void exportwithcustomstyle(list<employee> employees, outputstream outputstream) {
// 设置自定义拦截器来处理样式
easyexcel.write(outputstream, employee.class)
// 自动调整列宽
.registerwritehandler(new longestmatchcolumnwidthstylestrategy())
// 设置表头样式
.registerwritehandler(new abstractrowheightstylestrategy() {
@override
protected void setheadcolumnheight(row row, int relativerowindex) {
// 设置表头行高
row.setheight((short) 500);
}
@override
protected void setcontentcolumnheight(row row, int relativerowindex) {
// 设置内容行高
row.setheight((short) 400);
}
})
// 设置单元格样式
.registerwritehandler(new cellwritehandler() {
@override
public void aftercellcreate(writesheetholder writesheetholder, writetableholder writetableholder,
cell cell, head head, integer relativerowindex, boolean ishead) {
// 设置表头样式
if (ishead) {
workbook workbook = writesheetholder.getsheet().getworkbook();
cellstyle style = workbook.createcellstyle();
font font = workbook.createfont();
font.setbold(true);
font.setfontheightinpoints((short) 12);
font.setcolor(indexedcolors.white.getindex());
style.setfont(font);
style.setfillforegroundcolor(indexedcolors.royal_blue.getindex());
style.setfillpattern(fillpatterntype.solid_foreground);
style.setalignment(horizontalalignment.center);
style.setverticalalignment(verticalalignment.center);
cell.setcellstyle(style);
}
}
@override
public void aftercelldataconverted(writesheetholder writesheetholder, writetableholder writetableholder,
cell cell, head head, integer relativerowindex, boolean ishead) {
// 在这里可以根据数据内容设置样式
}
@override
public void aftercelldispose(writesheetholder writesheetholder, writetableholder writetableholder,
list<celldata> celldatalist, cell cell, head head, integer relativerowindex, boolean ishead) {
// 内容行的样式
if (!ishead) {
// 偶数行设置背景色
if (relativerowindex % 2 == 0) {
workbook workbook = writesheetholder.getsheet().getworkbook();
cellstyle style = workbook.createcellstyle();
style.setfillforegroundcolor(indexedcolors.pale_blue.getindex());
style.setfillpattern(fillpatterntype.solid_foreground);
style.setalignment(horizontalalignment.center);
style.setverticalalignment(verticalalignment.center);
cell.setcellstyle(style);
}
}
}
})
.sheet("员工数据")
.dowrite(employees);
}
/**
* 导出复杂表头的excel
*/
public void exportwithcomplexhead(list<employee> employees, outputstream outputstream) {
// 构建复杂表头
list<list<string>> head = new arraylist<>();
// 第一列 id
list<string> head1 = new arraylist<>();
head1.add("基本信息");
head1.add("员工id");
head.add(head1);
// 第二列 姓名
list<string> head2 = new arraylist<>();
head2.add("基本信息");
head2.add("姓名");
head.add(head2);
// 第三列 年龄
list<string> head3 = new arraylist<>();
head3.add("基本信息");
head3.add("年龄");
head.add(head3);
// 第四列 邮箱
list<string> head4 = new arraylist<>();
head4.add("联系方式");
head4.add("邮箱");
head.add(head4);
// 第五列 部门
list<string> head5 = new arraylist<>();
head5.add("工作信息");
head5.add("部门");
head.add(head5);
// 第六列 入职日期
list<string> head6 = new arraylist<>();
head6.add("工作信息");
head6.add("入职日期");
head.add(head6);
// 第七列 薪资
list<string> head7 = new arraylist<>();
head7.add("薪资信息");
head7.add("月薪(元)");
head.add(head7);
// 将数据转为list<list<object>>格式
list<list<object>> datalist = new arraylist<>();
for (employee employee : employees) {
list<object> data = new arraylist<>();
data.add(employee.getid());
data.add(employee.getname());
data.add(employee.getage());
data.add(employee.getemail());
data.add(employee.getdepartment());
data.add(employee.gethiredate());
data.add(employee.getsalary());
datalist.add(data);
}
// 写入excel
easyexcel.write(outputstream)
.head(head)
.registerwritehandler(new longestmatchcolumnwidthstylestrategy())
.sheet("员工数据")
.dowrite(datalist);
}
}6. 处理大型excel文件的策略
6.1 使用apache poi sxssf模式
sxssf(streaming xlsx writer)是poi提供的流式写入方式,可以大大减少内存使用:
public void exportlargeexcel(string filename, int rowcount) throws ioexception {
try (sxssfworkbook workbook = new sxssfworkbook(100)) { // 100表示内存中保留的行数
sheet sheet = workbook.createsheet("大数据");
// 创建表头
row headerrow = sheet.createrow(0);
for (int i = 0; i < 10; i++) {
headerrow.createcell(i).setcellvalue("列 " + (i + 1));
}
// 创建数据行
for (int i = 0; i < rowcount; i++) {
row row = sheet.createrow(i + 1);
for (int j = 0; j < 10; j++) {
row.createcell(j).setcellvalue("数据 " + (i + 1) + "-" + (j + 1));
}
// 每生成10000行清理一次临时文件
if (i % 10000 == 0) {
((sxssfsheet)sheet).flushrows();
}
}
// 写入文件
try (fileoutputstream outputstream = new fileoutputstream(filename)) {
workbook.write(outputstream);
}
// 清理临时文件
workbook.dispose();
}
}注意事项:
- 使用完毕后一定要调用
dispose()方法清理临时文件 - sxssf仅支持写入操作,不支持读取
- 不支持某些高级特性(如合并单元格等)
6.2 使用easyexcel处理大文件
easyexcel在设计上就考虑了大文件处理,采用sax方式逐行读取,内存占用小:
// 读取大文件
public void readlargeexcel(string filename) {
// 使用sax方式读取
easyexcel.read(filename, employee.class, new employeereadlistener())
.sheet()
.doread();
}
// 写入大文件
public void writelargeexcel(string filename, int batchsize) {
// 分批获取数据
try (excelwriter excelwriter = easyexcel.write(filename, employee.class)
.registerwritehandler(new longestmatchcolumnwidthstylestrategy())
.build()) {
// 获取writesheet对象
writesheet writesheet = easyexcel.writersheet("员工数据").build();
// 模拟分批获取数据
int totalcount = 100000; // 总数据量
for (int i = 0; i < totalcount; i += batchsize) {
// 获取当前批次数据
list<employee> data = getbatchdata(i, math.min(i + batchsize, totalcount));
// 写入excel
excelwriter.write(data, writesheet);
}
}
}
// 模拟分批获取数据
private list<employee> getbatchdata(int start, int end) {
list<employee> list = new arraylist<>();
for (int i = start; i < end; i++) {
employee employee = new employee();
employee.setid((long) i);
employee.setname("员工" + i);
employee.setage(20 + (i % 20));
employee.setemail("employee" + i + "@example.com");
employee.setdepartment(i % 3 == 0 ? "研发部" : (i % 3 == 1 ? "市场部" : "行政部"));
employee.sethiredate(new date());
employee.setsalary(5000.0 + (i % 10) * 1000);
list.add(employee);
}
return list;
}6.3 使用csv代替excel
对于极大的数据集,考虑使用csv格式替代excel:
public void exporttocsv(list<employee> employees, string filename) throws ioexception {
try (filewriter writer = new filewriter(filename);
csvprinter csvprinter = new csvprinter(writer, csvformat.default
.withheader("id", "姓名", "年龄", "邮箱", "部门", "入职日期", "薪资"))) {
for (employee employee : employees) {
csvprinter.printrecord(
employee.getid(),
employee.getname(),
employee.getage(),
employee.getemail(),
employee.getdepartment(),
employee.gethiredate(),
employee.getsalary()
);
}
csvprinter.flush();
}
}注意:使用csv需要添加依赖:
<dependency>
<groupid>org.apache.commons</groupid>
<artifactid>commons-csv</artifactid>
<version>1.9.0</version>
</dependency>6.4 分页导出大型数据集
对于web应用中需要导出的大型数据集,可以考虑分页导出:
@getmapping("/export/paged")
public responseentity<string> exportpaged() {
// 生成唯一任务id
string taskid = uuid.randomuuid().tostring();
// 启动异步任务
completablefuture.runasync(() -> {
try {
// 导出文件路径
string filepath = "/temp/" + taskid + ".xlsx";
// 分页查询数据并写入excel
int pagesize = 1000;
int totalpages = gettotalpages(pagesize);
try (excelwriter excelwriter = easyexcel.write(filepath, employee.class)
.registerwritehandler(new longestmatchcolumnwidthstylestrategy())
.build()) {
writesheet writesheet = easyexcel.writersheet("员工数据").build();
// 分页导出
for (int pagenum = 0; pagenum < totalpages; pagenum++) {
// 从数据库分页查询数据
list<employee> pagedata = getpagedata(pagenum, pagesize);
// 写入excel
excelwriter.write(pagedata, writesheet);
// 更新进度
updateexportprogress(taskid, (pagenum + 1) * 100 / totalpages);
}
}
// 更新导出状态为完成
updateexportstatus(taskid, "completed", filepath);
} catch (exception e) {
// 更新导出状态为失败
updateexportstatus(taskid, "failed", null);
}
});
// 返回任务id
return responseentity.ok(taskid);
}
@getmapping("/export/status/{taskid}")
public responseentity<map<string, object>> getexportstatus(@pathvariable string taskid) {
// 获取任务状态
map<string, object> status = gettaskstatus(taskid);
return responseentity.ok(status);
}
@getmapping("/export/download/{taskid}")
public responseentity<resource> downloadexportedfile(@pathvariable string taskid) {
// 获取导出文件路径
string filepath = getexportedfilepath(taskid);
if (filepath == null) {
return responseentity.notfound().build();
}
// 创建文件资源
resource resource = new filesystemresource(filepath);
return responseentity.ok()
.header(httpheaders.content_disposition,
"attachment; filename=employee_data.xlsx")
.contenttype(mediatype.parsemediatype("application/vnd.ms-excel"))
.body(resource);
}7. 实际应用场景和最佳实践
7.1 动态列导出
在某些业务场景中,需要根据用户选择动态确定导出的列:
public bytearrayinputstream exportdynamiccolumns(list<employee> employees, list<string> selectedcolumns) throws ioexception {
// 定义所有可能的列
map<string, string> allcolumns = new hashmap<>();
allcolumns.put("id", "员工id");
allcolumns.put("name", "姓名");
allcolumns.put("age", "年龄");
allcolumns.put("email", "邮箱");
allcolumns.put("department", "部门");
allcolumns.put("hiredate", "入职日期");
allcolumns.put("salary", "薪资");
try (workbook workbook = new xssfworkbook()) {
sheet sheet = workbook.createsheet("员工数据");
// 创建表头行
row headerrow = sheet.createrow(0);
// 设置表头样式
cellstyle headerstyle = workbook.createcellstyle();
font headerfont = workbook.createfont();
headerfont.setbold(true);
headerstyle.setfont(headerfont);
// 填充表头
int colidx = 0;
for (string column : selectedcolumns) {
if (allcolumns.containskey(column)) {
cell cell = headerrow.createcell(colidx++);
cell.setcellvalue(allcolumns.get(column));
cell.setcellstyle(headerstyle);
}
}
// 填充数据
int rowidx = 1;
for (employee employee : employees) {
row row = sheet.createrow(rowidx++);
colidx = 0;
for (string column : selectedcolumns) {
cell cell = row.createcell(colidx++);
// 根据列名设置单元格值
switch (column) {
case "id":
cell.setcellvalue(employee.getid());
break;
case "name":
cell.setcellvalue(employee.getname());
break;
case "age":
cell.setcellvalue(employee.getage());
break;
case "email":
cell.setcellvalue(employee.getemail());
break;
case "department":
cell.setcellvalue(employee.getdepartment());
break;
case "hiredate":
if (employee.gethiredate() != null) {
cell.setcellvalue(employee.gethiredate());
// 设置日期格式
cellstyle datestyle = workbook.createcellstyle();
creationhelper createhelper = workbook.getcreationhelper();
datestyle.setdataformat(createhelper.createdataformat().getformat("yyyy-mm-dd"));
cell.setcellstyle(datestyle);
}
break;
case "salary":
cell.setcellvalue(employee.getsalary());
break;
}
}
}
// 自动调整列宽
for (int i = 0; i < selectedcolumns.size(); i++) {
sheet.autosizecolumn(i);
}
// 输出
bytearrayoutputstream outputstream = new bytearrayoutputstream();
workbook.write(outputstream);
return new bytearrayinputstream(outputstream.tobytearray());
}
}7.2 excel模板填充
使用freemarker或其他模板引擎生成excel:
public bytearrayinputstream filltemplate(map<string, object> data) throws exception {
// 加载模板
configuration cfg = new configuration(configuration.version_2_3_30);
cfg.setclassloaderfortemplateloading(getclass().getclassloader(), "templates");
cfg.setdefaultencoding("utf-8");
// 获取模板
template template = cfg.gettemplate("excel_template.ftl");
// 输出目录
file tempdir = new file(system.getproperty("java.io.tmpdir"));
file tempfile = new file(tempdir, "temp_" + system.currenttimemillis() + ".xlsx");
// 填充模板
try (writer out = new filewriter(tempfile)) {
template.process(data, out);
}
// 读取填充后的文件
try (fileinputstream fis = new fileinputstream(tempfile)) {
bytearrayoutputstream baos = new bytearrayoutputstream();
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) > -1) {
baos.write(buffer, 0, len);
}
baos.flush();
// 删除临时文件
tempfile.delete();
return new bytearrayinputstream(baos.tobytearray());
}
}7.3 excel文件校验
在导入excel文件前进行数据校验:
public class excelvalidationlistener extends analysiseventlistener<employee> {
private list<employee> validemployees = new arraylist<>();
private list<map<string, object>> errorrecords = new arraylist<>();
private int rowindex = 1; // 从1开始,0是表头
@override
public void invoke(employee employee, analysiscontext context) {
rowindex++;
// 验证数据
list<string> errors = validateemployee(employee);
if (errors.isempty()) {
// 数据有效
validemployees.add(employee);
} else {
// 记录错误
map<string, object> errorrecord = new hashmap<>();
errorrecord.put("rowindex", rowindex);
errorrecord.put("data", employee);
errorrecord.put("errors", errors);
errorrecords.add(errorrecord);
}
}
@override
public void doafterallanalysed(analysiscontext context) {
// 处理完成
}
// 验证员工数据
private list<string> validateemployee(employee employee) {
list<string> errors = new arraylist<>();
// 验证姓名
if (employee.getname() == null || employee.getname().trim().isempty()) {
errors.add("姓名不能为空");
}
// 验证年龄
if (employee.getage() == null) {
errors.add("年龄不能为空");
} else if (employee.getage() < 18 || employee.getage() > 65) {
errors.add("年龄必须在18-65岁之间");
}
// 验证邮箱
if (employee.getemail() != null && !employee.getemail().isempty()) {
string emailregex = "^[a-za-z0-9_+&*-]+(?:\\.[a-za-z0-9_+&*-]+)*@" +
"(?:[a-za-z0-9-]+\\.)+[a-za-z]{2,7}$";
if (!employee.getemail().matches(emailregex)) {
errors.add("邮箱格式不正确");
}
}
// 验证部门
if (employee.getdepartment() == null || employee.getdepartment().trim().isempty()) {
errors.add("部门不能为空");
}
// 验证薪资
if (employee.getsalary() != null && employee.getsalary() < 0) {
errors.add("薪资不能为负数");
}
return errors;
}
public list<employee> getvalidemployees() {
return validemployees;
}
public list<map<string, object>> geterrorrecords() {
return errorrecords;
}
public boolean haserrors() {
return !errorrecords.isempty();
}
}7.4 统一异常处理
为excel处理添加统一的异常处理:
@controlleradvice
public class excelexceptionhandler {
private static final logger logger = loggerfactory.getlogger(excelexceptionhandler.class);
@exceptionhandler(ioexception.class)
public responseentity<map<string, string>> handleioexception(ioexception e) {
logger.error("文件读写异常", e);
map<string, string> response = new hashmap<>();
response.put("error", "文件读写异常");
response.put("message", e.getmessage());
return responseentity.status(httpstatus.internal_server_error).body(response);
}
@exceptionhandler(illegalargumentexception.class)
public responseentity<map<string, string>> handleillegalargumentexception(illegalargumentexception e) {
logger.error("参数异常", e);
map<string, string> response = new hashmap<>();
response.put("error", "参数异常");
response.put("message", e.getmessage());
return responseentity.status(httpstatus.bad_request).body(response);
}
@exceptionhandler(exception.class)
public responseentity<map<string, string>> handlegenericexception(exception e) {
logger.error("excel处理异常", e);
map<string, string> response = new hashmap<>();
response.put("error", "excel处理异常");
response.put("message", e.getmessage());
return responseentity.status(httpstatus.internal_server_error).body(response);
}
}8. 性能优化和注意事项
8.1 性能优化建议
使用适当的excel库:
- 小文件可使用apache poi
- 大文件请使用easyexcel或poi的sxssf模式
- 极大文件考虑使用csv格式
避免一次性加载整个文件:
- 读取时使用流式解析
- 写入时使用分批写入
合理设置缓冲区大小:
- 在sxssfworkbook中设置合理的内存行数
- 在批处理中选择合适的批次大小
减少样式对象:
- 样式对象重用,而不是为每个单元格创建新样式
- 限制使用的颜色、字体和边框样式数量
使用异步处理:
- 将大文件处理放在后台线程中执行
- 提供进度反馈机制
8.2 注意事项
内存管理:
- 注意监控jvm内存使用情况
- 对于大文件处理,考虑增加jvm堆内存(-xmx参数)
- 使用完毕后及时关闭资源和清理临时文件
安全考虑:
- 限制上传文件大小
- 验证文件类型和内容
- 防止恶意excel文件(包含宏或公式)
编码问题:
- 处理国际字符时,确保使用正确的字符编码
- 文件名包含中文时,确保正确编码
并发控制:
- 大文件处理时注意服务器负载
- 限制并发处理任务数量
临时文件清理:
- 使用sxssf时,必须调用dispose()方法清理临时文件
- 定期清理服务器上的临时文件
总结
spring boot提供了强大而灵活的excel处理能力,通过结合apache poi和easyexcel等工具,可以轻松实现excel文件的读取、创建和导出功能。在实际应用中,应根据具体需求和数据量选择合适的处理策略,既要保证功能完整,又要注重性能和资源使用。
无论是简单的数据导出,还是复杂的报表生成,或是大数据量的文件处理,都可以通过本文介绍的方法灵活实现。重点是要根据实际业务场景,选择合适的技术方案,并注意性能优化和异常处理。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论