Spring Boot 4.0 企业级性能调优:连接池、序列化与分页
前言
接口变慢时直接增加线程数,往往只会更快地把数据库连接和内存耗尽。性能问题需要先判断时间花在等待连接、SQL 扫描、对象序列化还是网络传输。
本章围绕订单列表做三个可验证改造:合理配置连接池、只序列化页面需要的字段、使用游标分页替代深偏移。
读者需要完成前面的数据库与分页章节。完整代码位于 dvsusan/spring-boot4-demo。
最终结果
新增接口:
GET /api/v1/chapter7/performance/orders?limit=3
GET /api/v1/chapter7/performance/orders?cursor=...&limit=3第一页返回 nextCursor,第二页从游标位置继续,不使用越来越大的 OFFSET。

图 1:先用指标定位瓶颈,再选择连接池、SQL、分页或序列化优化。
第 1 步:配置有界连接池
application-chapter7.yml:
spring:
datasource:
hikari:
# 上限不是越大越好,要结合数据库连接上限和服务实例数。
maximum-pool-size: 10
minimum-idle: 2
# Hikari 的时间参数按毫秒绑定。
connection-timeout: 3000
validation-timeout: 1000
idle-timeout: 600000
max-lifetime: 1800000如果 4 个应用实例都配置 50 个连接,数据库可能面对 200 个连接。连接池总预算应按数据库容量统一分配,而不是每个服务独立追求更大的数字。
第 2 步:只返回页面需要的 DTO
订单实体包含明细、审计字段和关联对象,但列表页只需要五个字段:
public record PerformanceOrderView(
String orderNo,
String customerId,
BigDecimal amount,
String status,
Instant createdAt
) {
}直接序列化 JPA 实体容易出现懒加载异常、循环引用和字段泄露。DTO 同时控制响应大小和对外契约。
第 3 步:用稳定游标替代深偏移
游标由“创建时间 + 订单号”组成:
private boolean isAfter(
PerformanceOrderView order,
Cursor cursor
) {
if (order.createdAt().isAfter(cursor.createdAt())) {
return true;
}
return order.createdAt().equals(cursor.createdAt())
&& order.orderNo().compareTo(cursor.orderNo()) > 0;
}加入订单号作为第二排序键,避免同一时间的多行在翻页时重复或遗漏。
编码与解码:
private String encode(PerformanceOrderView order) {
String value = order.createdAt().toEpochMilli()
+ "|"
+ order.orderNo();
return Base64.getUrlEncoder()
.withoutPadding()
.encodeToString(value.getBytes(StandardCharsets.UTF_8));
}生产 SQL 应转换为:
SELECT order_no, customer_id, amount, status, created_at
FROM purchase_order
WHERE tenant_id = ?
AND (created_at, order_no) > (?, ?)
ORDER BY created_at, order_no
LIMIT ?;索引需要覆盖 (tenant_id, created_at, order_no),并包含查询返回字段。具体索引仍要通过执行计划验证。
第 4 步:限制单页数量
int size = Math.max(1, Math.min(requestedSize, MAX_PAGE_SIZE));客户端可以请求 limit=100000,服务端必须设置硬上限。否则一个请求就能占用大量内存、数据库带宽和 JSON 序列化 CPU。
第 5 步:启动并验证两页数据
java -jar demo-web/target/demo-web-1.0.0-SNAPSHOT.jar \
--spring.profiles.active=chapter7 \
--server.port=18107
curl -sS \
"http://localhost:18107/api/v1/chapter7/performance/orders?limit=3"响应结构:
{
"data": {
"items": [
{"orderNo": "SO0000", "customerId": "C0", "amount": 100}
],
"nextCursor": "<base64-cursor>",
"hasMore": true,
"pageSize": 3
}
}把 nextCursor 放回请求:
curl -sS \
"http://localhost:18107/api/v1/chapter7/performance/orders?cursor=<nextCursor>&limit=3"自动化测试会解析两次响应,确认两页订单号没有交叉。
第 6 步:运行验证
mvn -pl demo-web -am \
-Dtest=Chapter7AdvancedIntegrationTest \
-Dsurefire.failIfNoSpecifiedTests=false \
test游标分页用例实际通过:第一页 3 条、hasMore=true,第二页 3 条且与第一页不重复。
优化必须用指标闭环
至少观察以下指标:
| 指标 | 能回答的问题 |
|---|---|
| Hikari 活跃连接、等待时间 | 是否在等数据库连接 |
| SQL P95、扫描行数、执行计划 | 是否在做全表或深偏移扫描 |
| 响应字节数、序列化耗时 | 是否返回了过多字段 |
| GC、堆使用、线程数 | 是否构造了过大的对象图 |
| 接口 P95、错误率 | 用户体验是否真正改善 |
没有前后指标对比,就没有性能优化,只有配置变化。
常见问题排查
| 现象 | 原因 | 处理方式 |
|---|---|---|
| 连接池等待时间长 | 池太小或连接泄漏 | 查看活跃连接和占用时长 |
| 调大池后数据库更慢 | 总连接超过数据库承载 | 按实例数控制总连接预算 |
| 翻页重复或遗漏 | 排序列不稳定 | 加入唯一字段作为第二排序键 |
| 游标无法解码 | Base64 URL 字符被修改 | 前端原样透传,不手工拼接 |
| 响应仍然很大 | DTO 包含关联集合 | 分离列表和详情接口 |
练习题
为游标增加签名,防止客户端伪造任意位置;再加入按状态过滤,并验证新增条件后的索引和执行计划。验收标准是分页顺序稳定、总页数未知时仍能继续翻页、非法游标返回明确 400。
总结
连接池控制并发资源,DTO 控制响应边界,游标分页控制大数据量访问成本。真正的调优顺序永远是测量、定位、修改、复测。
下一篇在这些工程基础上接入 Spring AI 2.0,搭建带引用来源的企业知识库客服。
上一节:Resilience4j 容错
下一节:Spring AI 2.0 知识库客服
相关阅读:MyBatis 多表查询与分页
返回课程目录
13 个企业级实战项目 · 源码 + 教程 + 答疑
简历包装指导AI Agent 实战高并发·微服务Offer 导向持续更新3000+ 同行交流1V1 答疑3 天包退
覆盖高并发、微服务、分布式与 AI Agent:100万QPS短链、企业智能知识库、商城微服务、SaaS点餐、秒杀、刷题小程序、代码生成器、AI Agent 等。券后 ¥129(原价 ¥159),不满意 3 天包退。
