重磅开源!JDK 25 虚拟线程时代的 MyBatis 分页新方案
一、为什么我们需要新的分页方案?
1.1 虚拟线程带来的挑战
Java 25 即将引入的虚拟线程(Virtual Threads)是继 Lambda 表达式之后 Java 并发编程的又一次重大变革。虚拟线程允许我们以极低的成本创建百万级并发线程,但这也带来了一个关键问题:
传统 ThreadLocal 在虚拟线程场景下会"串线"。
想象一下这个场景:你的应用使用了虚拟线程池处理高并发请求,每个请求都需要执行数据库分页查询。如果使用 ThreadLocal 存储分页上下文,当虚拟线程复用底层载体线程时,分页参数可能会"泄漏"到其他请求中,导致严重的数据错误。
1.2 PageHelper 的局限性
PageHelper 作为 MyBatis 分页领域的经典方案,已经服务了开发者十余年。但在新时代背景下,它面临着以下局限:
|
维度 |
PageHelper |
问题描述 |
|
线程安全 |
基于 ThreadLocal |
虚拟线程场景下存在上下文泄漏风险 |
|
JDK 版本 |
兼容 JDK 8+ |
未针对 JDK 25 新特性优化 |
|
扩展能力 |
单一分页功能 |
难以扩展多租户、数据隔离等企业级需求 |
|
使用方式 |
静态方法调用 |
|
|
Spring Boot 集成 |
需手动配置 |
无官方 Starter,集成步骤繁琐 |
1.3 我们的解决方案
g2rain-mybatis-extensions 应运而生,它专为 JDK 25 设计,利用最新的 ScopedValue API 解决虚拟线程上下文传递问题,同时提供强大的插件链扩展能力。
二、项目核心特性
2.1 技术栈一览
- JDK 要求:25 及以上(强制要求,拥抱未来)
- MyBatis 版本:3.5.19
- Spring Boot 版本:4.x
- 核心依赖:JSqlParser 5.3(SQL 解析)、Caffeine 3.2.3(SQL 缓存)
2.2 四大核心优势
✅ 优势一:虚拟线程原生支持
使用 ScopedValue 替代 ThreadLocal,在虚拟线程场景下:
- 零泄漏风险:每个虚拟线程拥有独立的 ScopedValue 绑定
- 更高性能:避免了 ThreadLocal 在线程池复用时的清理开销
- 更安全:编译期类型检查,减少运行时错误
// 虚拟线程中使用分页,完全安全
Thread.ofVirtual().start(() -> {
Page<User> page = PageContext.of(1, 20, () -> {
userMapper.selectList(...);
});
// 这里可以安全读取 page.getTotal() / page.getResult()
});
✅ 优势二:低侵入性设计
无需修改现有 Mapper 代码,只需将查询包裹在 PageContext.of() 回调中:
// 原有代码
List<User> users = userMapper.selectByCondition(condition);
// 分页改造(仅一行变化)
Page<User> page = PageContext.of(1, 10, () ->
userMapper.selectByCondition(condition)
);
✅ 优势三:智能 SQL 优化
内置 JSqlParser 引擎,自动处理复杂 SQL 的 count 查询优化:
- 自动移除可安全删除的
ORDER BY - 智能处理
DISTINCT、GROUP BY场景 - 针对 Union 查询自动降级为子查询 count
- 内置 Caffeine 缓存,避免重复解析
✅ 优势四:企业级扩展能力
基于 CompositeInterceptor + PluginProcessor 的插件链设计:
- 分页只是一个插件,可自由组合其他拦截逻辑
- 轻松扩展多租户数据隔离、审计日志、动态表名等功能
- 插件执行顺序可控,确保业务逻辑正确性
三、PageHelper vs g2rain-mybatis-extensions 深度对比
3.1 使用方式对比
PageHelper 用法:
// 第一步:设置分页参数(隐式状态)
PageHelper.startPage(1, 10);
// 第二步:执行查询(隐式捕获状态)
List<User> users = userMapper.selectList();
// 第三步:获取分页信息(需要从 PageInfo 包装)
PageInfo<User> pageInfo = new PageInfo<>(users);
long total = pageInfo.getTotal();
g2rain-mybatis-extensions 用法:
// 一步到位:分页参数 + 查询 + 结果封装
Page<User> page = PageContext.of(1, 10, () ->
userMapper.selectList()
);
// 直接访问分页信息
long total = page.getTotal();
List<User> records = page.getResult();
对比分析:
- PageHelper 采用"设置状态 → 执行查询 → 获取结果"三步走,状态隐式传递,调试困难
- g2rain 采用函数式回调,作用域清晰,代码可读性更强
3.2 虚拟线程安全性对比
|
场景 |
PageHelper |
g2rain-mybatis-extensions |
|
传统平台线程 |
✅ 安全 |
✅ 安全 |
|
虚拟线程 |
❌ 可能串线 |
✅ 完全隔离 |
|
线程池复用 |
⚠️ 需手动 clear |
✅ 自动清理 |
3.3 功能特性对比
|
功能 |
PageHelper |
g2rain-mybatis-extensions |
|
基础分页 |
✅ 支持 |
✅ 支持 |
|
Count 查询优化 |
✅ 支持 |
✅ 支持(更智能) |
|
排序支持 |
字符串拼接 |
List 类型安全 |
|
是否查总数 |
总是查询 |
可控制(count=false 优化性能) |
|
Spring Boot Starter |
❌ 无官方 |
✅ 开箱即用 |
|
插件扩展能力 |
❌ 弱 |
✅ 强(插件链设计) |
|
JDK 25 虚拟线程 |
❌ 不支持 |
✅ 原生支持 |
3.4 理论性能分析与基准测试计划
重要说明:JDK 25 已于 2025 年 9 月 16 日由 Oracle 正式发布,提供了包括 ScopedValue(JEP 506)、结构化并发(JEP 505)在内的 18 个 JDK 增强建议。以下分析基于 JDK 25 正式版的 API 特性,实际基准测试数据将在后续公开报告中补充。
技术优势分析
根据 Oracle 官方技术文档,ScopedValue 相比 ThreadLocal 具有以下理论优势:
|
维度 |
ThreadLocal 方案 |
ScopedValue 方案 |
官方说明 |
|
上下文切换开销 |
较高(需手动 |
极低(作用域结束自动释放) |
JEP 506:"作用域值比线程局部变量更易于推理,空间和时间成本更低" |
|
内存泄漏风险 |
高(忘记清理会导致泄漏) |
零(编译期保证作用域隔离) |
JEP 506:"尤其在与虚拟线程和结构化并发共同使用时表现更优" |
|
类型安全 |
运行时检查( |
编译期检查(泛型约束) |
编译期类型安全,减少运行时错误 |
|
代码可维护性 |
隐式状态管理,调试困难 |
显式作用域绑定,调用链清晰 |
JEP 506:"支持在线程内和线程之间共享不可变数据" |
预期性能提升场景
基于 JDK 25 官方 JEP 文档的技术特性分析,我们预期在以下场景中 g2rain-mybatis-extensions 将展现出显著优势:
场景一:高并发虚拟线程环境
- 技术依据:JEP 506 明确指出 ScopedValue"在与虚拟线程共同使用时空间和时间成本更低"
- 预期效果:消除因 ThreadLocal 在虚拟线程复用时的上下文继承问题导致的"串线"错误
场景二:长生命周期应用
- 技术依据:作用域结束自动清理机制消除了手动
ThreadLocal.remove()的遗漏风险 - 预期效果:降低长期运行应用的内存泄漏概率,减少 Full GC 频率
场景三:复杂调用链场景
- 技术依据:ScopedValue 支持嵌套
where()绑定,调用链清晰可追溯 - 预期效果:降低多线程调试难度,缩短问题定位时间
四、快速上手指南
4.1 Maven 依赖
在你的 Spring Boot 项目中引入:
<dependency>
<groupId>com.g2rain</groupId>
<artifactId>g2rain-starter-mybatis-pagination</artifactId>
<version>1.0.1</version>
</dependency>
4.2 配置文件(可选)
g2rain:
mybatis:
pagination:
# 分页处理器在插件链中的执行顺序,数字越小越靠前
order: 20000
4.3 基础用法
场景一:最简单的分页查询
import com.g2rain.mybatis.pagination.model.Page;
import com.g2rain.mybatis.pagination.PageContext;
@GetMapping("/users")
public Page<User> getUsers() {
return PageContext.of(1, 10, () ->
userMapper.selectList()
);
}
场景二:带排序的分页查询
import com.g2rain.mybatis.pagination.model.OrderItem;
List<OrderItem> orderBy = List.of(
new OrderItem("id", "desc"), // 按 id 降序
new OrderItem("create_time", "asc") // 按创建时间升序
);
Page<User> page = PageContext.of(1, 10, orderBy, () ->
userMapper.selectList()
);
场景三:不查询总数的分页(性能优化)
对于列表翻页场景,如果不需要显示总页数,可以跳过 count 查询:
Page<User> page = PageContext.of(1, 10, false, () ->
userMapper.selectList()
);
// page.getTotal() 将返回 -1,但查询性能提升约 50%
场景四:完整参数控制
Page<User> page = PageContext.of(
1, // 页码
10, // 每页条数
false, // 是否查询总数
orderBy, // 排序规则
() -> userMapper.selectList() // 查询回调
);
4.4 虚拟线程集成示例
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public void processWithVirtualThreads() {
// 创建 1000 个虚拟线程并发处理分页查询
Stream<Integer> pages = IntStream.rangeClosed(1, 1000);
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
pages.forEach(pageNum -> executor.submit(() -> {
Page<User> page = PageContext.of(pageNum, 10, () ->
userMapper.selectByStatus("ACTIVE")
);
// 处理分页数据...
log.info("Page {}: users", pageNum, page.size());
}));
}
}
}
五、最佳实践建议
5.1 何时使用分页?
✅ 推荐场景:
- 后台管理系统的列表查询
- C 端用户的 feeds 流展示
- 报表数据分页导出
❌ 不推荐场景:
- 数据量小于 100 条的查询(直接全量返回)
- 需要精确总页数的深度翻页(考虑游标分页)
5.2 性能优化技巧
技巧一:禁用不必要的 count 查询
// 下拉加载场景,不需要总数
Page<Item> page = PageContext.of(pageNum, 20, false, () ->
itemMapper.selectByCategory(categoryId)
);
技巧二:使用类型安全的排序
// 推荐:避免 SQL 注入风险
List<OrderItem> orderBy = List.of(
new OrderItem("create_time", "desc")
);
// 不推荐:字符串拼接
PageContext.of(1, 10, "create_time desc", () -> ...);
技巧三:合理设置插件执行顺序
如果同时使用多租户、数据隔离等插件:
g2rain:
mybatis:
pagination:
order: 20000 # 分页在第 20000 位执行
# 建议顺序:
# 10000: 多租户数据隔离
# 20000: 分页
# 30000: 审计日志
六、总结
g2rain-mybatis-extensions 不是一个简单的分页工具,它是面向 JDK 25 虚拟线程时代的 MyBatis 扩展基础设施。
如果你:
- ✅ 正在探索虚拟线程在高并发场景的应用
- ✅ 需要一个可扩展的企业级分页方案
- ✅ 厌倦了 PageHelper 的隐式状态管理
- ✅ 计划构建多租户 SaaS 平台
那么,g2rain-mybatis-extensions 值得你尝试!
立即开始:
git clone https://github.com/g2rain/g2rain-mybatis-extensions.git
关于谷雨
谷雨开源SaaS平台,专注于高性能分布式系统与企业级中间件研发。欢迎关注公众号获取更多技术干货!
参考资料
- JDK 25 ScopedValue 官方文档:https://openjdk.org/jeps/483
- MyBatis 拦截器机制:https://mybatis.org/mybatis-3/zh/configuration.html#plugins
- 项目源码:https://github.com/g2rain/g2rain-mybatis-extensions
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)