休闲

mybatis如何实现分页

时间:2010-12-5 17:23:32  作者:热点   来源:百科  查看:  评论:0
内容摘要:MyBatis的分页,说穿了就两条路:内存分页和物理分页。前者是假把式,把数据全查出来再在Java里截取;后者才是真功夫,直接拼SQL让数据库只返回需要的行。绝大多数人用MyBatis,冲的就是那个P

MyBatis的实现分页,说穿了就两条路:内存分页和物理分页。分页前者是实现假把式,把数据全查出来再在Java里截取;后者才是分页真功夫,直接拼SQL让数据库只返回需要的实现行。绝大多数人用MyBatis,分页冲的实现就是那个PageHelper插件,但很多人没搞明白它到底在背后干了什么。分页

先泼盆冷水,实现别指望MyBatis原生支持物理分页。分页它是实现个SQL映射框架,不是分页ORM全桶。你写select * from user,实现它就把这条SQL原封不动扔给JDBC。分页想分页?实现要么你自己在XML里手动拼LIMIT #{ offset}, #{ pageSize},要么引入插件拦截器。手动拼有个致命伤——不同数据库方言不一样。MySQL用LIMIT,Oracle用ROWNUM,SQL Server用OFFSET FETCH。你每写一个分页查询就得记着换语法,代码里全是if判断数据库类型,那画面太美我不敢看。

PageHelper的做法是拦截器模式。它实现了一个MyBatis的Interceptor接口,在Executor执行SQL之前,用Page对象里的页码和每页条数,动态改写原SQL。比如你传第2页、每页10条,它就把select * from user改成select * from user LIMIT 10, 10。同时它还会自动执行一条COUNT查询,算出总记录数塞进PageInfo里。这个设计有个隐藏前提——你的SQL必须能被安全地包一层count。要是你的查询里带GROUP BY或者DISTINCT,它生成的count语句常常算错数,这就是为什么有些人用PageHelper时统计结果莫名其妙翻倍。

还有个坑很多人踩过。PageHelper的Page对象是线程绑定的,用ThreadLocal存当前分页参数。你必须在查询前调用PageHelper.startPage(pageNum, pageSize),紧接着执行Mapper方法。要是中间隔了别的数据库操作,或者查询被包在另一个方法里没直接调用,分页参数就会串到别的SQL上,导致数据错乱。我见过生产环境出过这种事故——一个定时任务里先分页查了订单,紧接着又查用户列表,结果用户列表也被套上了订单的分页条件,返回了空集。排查半天,最后发现是同事在service层调了两次Mapper,中间没重置ThreadLocal。

再说说分页的边界问题。PageHelper默认页码从1开始,你要是传0或者负数,它不会报错,而是当成第1页处理。这不算bug,但容易让新人心存侥幸。真正要命的是超大页码。你查第100万页,每页10条,LIMIT的偏移量就是1000万。MySQL这时候会扫完前1000万行再丢掉,性能直线下降。优化手段无非两种:要么限制最大页码,要么改用游标分页——用WHERE id > lastMaxId ORDER BY id LIMIT 10,但这只适合排序字段唯一且有序的场景,比如按主键排序。

如果你不用插件,自己写分页也有讲究。MyBatis的RowBounds类能实现内存分页,但它会把所有数据load进内存再截取,数据量一大直接OOM。我见过有人图省事用这个,结果一张百万行的表差点把应用搞挂。所以正经做法还是XML里写动态SQL,用<if test="offset != null">LIMIT #{ offset}, #{ pageSize}</if>控制。这样虽然每个查询都得自己带两个参数,但胜在可控,还能针对特定SQL做索引优化。比如你知道某个查询过滤条件能命中联合索引,就手动指定LIMIT的偏移量,而不是依赖插件自动生成的count。

最后提醒一句,分页别只想着SQL那一亩三分地。接口返回给前端的时候,PageInfo里除了list,还有pageNum、pageSize、total、pages这些字段。很多人直接把这个对象整个序列化扔给前端,导致多余的字段泄露内部实现细节。更合理的做法是自己定义个DTO,只传需要的字段。别偷懒,分页是门面活,前端拿到乱七八糟的键名,回头骂的还是后端。

最近更新
热门排行
copyright © 2026 powered by 力不从心网   sitemap