网站首页 > 文章精选 正文
一、为什么需要分页插件?
在数据库操作中,分页查询是最常见的需求之一。原生MyBatis并不提供内置的分页功能,开发者通常需要:
- 编写带有LIMIT和OFFSET的SQL语句(MySQL)
- 使用RowBounds进行内存分页(性能差)
- 为每个分页查询重复编写相似代码
这些方式要么不够优雅,要么性能不佳。今天,我将带你从MyBatis插件原理出发,手把手实现一个高性能的分页插件!
二、MyBatis插件核心原理
1. 插件拦截机制
MyBatis采用责任链模式实现插件功能,允许我们在四大核心对象的方法调用前后插入自定义逻辑:
- Executor:执行器,负责SQL执行和缓存管理
- StatementHandler:处理SQL语句
- ParameterHandler:处理参数
- ResultSetHandler:处理结果集
2. 拦截点(Interceptor)
通过实现Interceptor接口并指定拦截点注解,我们可以拦截目标方法:
@Intercepts({
@Signature(type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class PageInterceptor implements Interceptor {
// 实现逻辑
}
3. 插件执行流程
- 创建目标对象(如Executor)
- 通过Plugin.wrap()生成代理对象
- 调用代理对象方法时,触发插件的intercept()方法
三、分页插件实现详解
1. 定义分页参数类
public class PageParam {
private int pageNum; // 当前页码
private int pageSize; // 每页数量
private long total; // 总记录数
private List<?> data; // 分页数据
// getter/setter省略
}
2. 实现分页拦截器
@Intercepts({
@Signature(type = StatementHandler.class,
method = "prepare",
args = {Connection.class, Integer.class})
})
public class PageInterceptor implements Interceptor {
private static final ThreadLocal<PageParam> PAGE_PARAM_THREAD_LOCAL = new ThreadLocal<>();
public static void startPage(int pageNum, int pageSize) {
PAGE_PARAM_THREAD_LOCAL.set(new PageParam(pageNum, pageSize));
}
public static void clearPage() {
PAGE_PARAM_THREAD_LOCAL.remove();
}
@Override
public Object intercept(Invocation invocation) throws Throwable {
PageParam pageParam = PAGE_PARAM_THREAD_LOCAL.get();
if (pageParam == null) {
return invocation.proceed();
}
StatementHandler handler = (StatementHandler) invocation.getTarget();
BoundSql boundSql = handler.getBoundSql();
String originalSql = boundSql.getSql();
// 修改SQL添加分页
String pageSql = getPageSql(originalSql, pageParam);
MetaObject metaObject = SystemMetaObject.forObject(boundSql);
metaObject.setValue("sql", pageSql);
// 执行查询
Object result = invocation.proceed();
// 设置分页结果
pageParam.setData((List<?>) result);
return result;
}
private String getPageSql(String sql, PageParam pageParam) {
StringBuilder pageSql = new StringBuilder();
pageSql.append(sql);
pageSql.append(" LIMIT ");
pageSql.append((pageParam.getPageNum() - 1) * pageParam.getPageSize());
pageSql.append(",");
pageSql.append(pageParam.getPageSize());
return pageSql.toString();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
// 可接收配置参数
}
}
3. 注册插件
在MyBatis配置文件中添加:
<plugins>
<plugin interceptor="com.yourpackage.PageInterceptor">
<!-- 可配置参数 -->
</plugin>
</plugins>
4. 使用示例
// 开始分页
PageInterceptor.startPage(1, 10);
// 执行查询
List<User> users = userMapper.selectAll();
// 获取分页结果
PageParam page = PageInterceptor.getPageParam();
System.out.println("总记录数:" + page.getTotal());
System.out.println("当前页数据:" + page.getData());
// 清除分页参数
PageInterceptor.clearPage();
四、高级优化:支持多种数据库
上面的实现只支持MySQL,我们可以扩展支持多种数据库:
private String getPageSql(String sql, PageParam pageParam, String dialect) {
switch (dialect.toLowerCase()) {
case "mysql":
return mysqlPageSql(sql, pageParam);
case "oracle":
return oraclePageSql(sql, pageParam);
case "postgresql":
return postgresqlPageSql(sql, pageParam);
default:
throw new RuntimeException("不支持的数据库类型");
}
}
private String mysqlPageSql(String sql, PageParam pageParam) {
return String.format("%s LIMIT %d, %d",
sql,
(pageParam.getPageNum() - 1) * pageParam.getPageSize(),
pageParam.getPageSize());
}
private String oraclePageSql(String sql, PageParam pageParam) {
// Oracle分页实现
// ...
}
五、性能优化:获取总记录数
完整的分页需要知道总记录数,我们可以通过拦截count查询实现:
// 在intercept方法中添加
if (isCountQuery(boundSql)) {
// 执行count查询
int total = executeCount(handler, connection);
pageParam.setTotal(total);
return total;
}
private boolean isCountQuery(BoundSql boundSql) {
String sql = boundSql.getSql().toLowerCase();
return sql.trim().startsWith("select count(");
}
六、插件开发注意事项
- 线程安全:使用ThreadLocal存储分页参数
- SQL注入防护:不要直接拼接SQL参数
- 性能考虑:避免在插件中执行耗时操作
- 兼容性:考虑不同MyBatis版本的差异
- 可配置化:通过properties支持灵活配置
七、总结
通过本文,我们深入理解了MyBatis插件机制,并实现了一个完整的分页插件。这个插件具有以下优点:
- 无侵入性:不改动原有Mapper接口和SQL映射
- 使用简单:通过静态方法控制分页
- 高性能:数据库层面分页,非内存分页
- 可扩展:支持多种数据库方言
完整代码已上传GitHub(示例地址),欢迎Star和提出改进建议!
思考题:如何实现基于注解的分页,让代码更加优雅?欢迎在评论区分享你的想法!
这篇文章结合了理论讲解和实战编码,突出了MyBatis插件开发的核心要点,同时提供了可直接使用的分页插件实现。通过清晰的代码示例和分步讲解,读者可以快速掌握MyBatis插件开发技巧。文章结构紧凑,避免了冗余内容,每个部分都直击要点,符合技术爆款文章的要求。
猜你喜欢
- 2025-06-09 PageHelper - 最方便的 MyBatis 分页插件
- 2025-06-09 50个Java编程技巧,免费送给大家(java编程基础知识入门)
- 2025-06-09 SpringBoot 各种分页查询方式详解(全网最全)
- 2025-06-09 SpringBatch - R&W, 我与富婆的这一年
- 2025-06-09 面试官:说说MyBatis分页插件(PageHelper)工作原理和配置过程?
- 2025-06-09 面试二:pagehelper是怎么实现分页的,
- 2025-06-09 SpringBoot集成Mybatis-Plus分页插件
- 2025-06-09 【开发技术】Mybatis中进行多表关联查询?性能是不是会变好呢?
- 2025-06-09 每天从外包系统同步百万数据,用什么方案?Java实战讲解
- 2025-06-09 Mybatis 是如何进行分页的?分页插件的原理是什么
- 最近发表
-
- 面试中常被问到的Hash表,你了解吗
- JAVA面试考点:一文搞懂一致性Hash的原理和实现
- 一次性搞清楚equals和hashCode(hashcode() 与equals()区别,简单说明)
- HashMap.Key的故事:Key为什么出现Hash碰撞及冲突呢?
- hash冲突的几种解决方案对比(hash冲突的解决方式)
- 游戏王LN 无头骑士(无头骑士cv)
- Linux ln、unlink命令用法(linux link命令详解)
- n和l分不清矫正发音方法,这三步就够了
- golang引用私有gitlab项目代码(golang引入当前包下的文件)
- Instamic:录音领域中的 GoPro,让你想录就录,随心所欲
- 标签列表
-
- newcoder (56)
- 字符串的长度是指 (45)
- drawcontours()参数说明 (60)
- unsignedshortint (59)
- postman并发请求 (47)
- python列表删除 (50)
- 左程云什么水平 (56)
- 计算机网络的拓扑结构是指() (45)
- 编程题 (64)
- postgresql默认端口 (66)
- 数据库的概念模型独立于 (48)
- 产生系统死锁的原因可能是由于 (51)
- 数据库中只存放视图的 (62)
- 在vi中退出不保存的命令是 (53)
- 哪个命令可以将普通用户转换成超级用户 (49)
- noscript标签的作用 (48)
- 联合利华网申 (49)
- swagger和postman (46)
- 结构化程序设计主要强调 (53)
- 172.1 (57)
- apipostwebsocket (47)
- 唯品会后台 (61)
- 简历助手 (56)
- offshow (61)
- mysql数据库面试题 (57)