详解Mybatis极其(最)简(好)单(用)的一个分页插件
发布时间 - 2026-01-10 22:08:30 点击率:次注意:这篇博客已经和当前的分页插件完全不一样了,所以建议大家通过上面项目地址查看最新的源码和文档来了解。

以前为Mybatis分页查询发愁过,而且在网上搜过很多相关的文章,最后一个都没采用。在分页的地方完全都是手写分页SQL和count的sql,总之很麻烦。
后来有一段时间想从Mybatis内部写一个分页的实现,我对LanguageDriver写过一个实现,自动分页是没问题了,但是查询总数(count)仍然没法一次性解决,最后不了了之。
最近又要用到分页,为了方便必须地写个通用的分页类,因此又再次参考网上大多数的Mybatis分页代码。
实际上在很早之前,有人在github上开源过一个实现,支持MySQL,Oracle,sqlserver的,和上面这个参考的比较类似,考虑的更全面。但是我觉得太多类太麻烦了,所以自己实现了一个只有一个拦截器的类,实际上可以分为两个类,其中一个类被我写成静态类放在了拦截器中,你也可以将Page类提取出来,方便使用Page。
先说实现方法,该插件只有一个类:PageHelper.Java
拦截器签名为:
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class}),
@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})})
这里的签名对整个实现和思想至关重要,首先我拦截prepare方法来改分页SQL,来做count查询。然后我拦截handleResultSets方法来获取最后的处理结果,将结果放到Page对象中。
下面是修改分页的代码,是针对Oracle数据进行的修改,如果有用其他数据库的,自己修改这里的代码就可以。
/**
* 修改原SQL为分页SQL
* @param sql
* @param page
* @return
*/
private String buildPageSql(String sql, Page page) {
StringBuilder pageSql = new StringBuilder(200);
pageSql.append("select * from ( select temp.*, rownum row_id from ( ");
pageSql.append(sql);
pageSql.append(" ) temp where rownum <= ").append(page.getEndRow());
pageSql.append(") where row_id > ").append(page.getStartRow());
return pageSql.toString();
}
之后在下面的setPageParameter方法中一个selelct count语句,这里也需要根据数据库类型进行修改:
// 记录总记录数
String countSql = "select count(0) from (" + sql + ")";
为什么我不提供对各种数据库的支持呢,我觉得没必要,还有些数据库不支持分页,而且这个插件越简单对使用的开发人员来说越容易理解,越容易修改。修改成自己需要的分页查询肯定不是问题。
最后上完整代码(继续看下去,下面还有使用方法):(点击下载)
package com.mybatis.util;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.log4j.Logger;
import java.sql.*;
import java.util.List;
import java.util.Properties;
/**
* Mybatis - 通用分页拦截器
* @author liuzh/abel533/isea
* Created by liuzh on 14-4-15.
*/
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class}),
@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})})
public class PageHelper implements Interceptor {
private static final Logger logger = Logger.getLogger(PageHelper.class);
public static final ThreadLocal<Page> localPage = new ThreadLocal<Page>();
/**
* 开始分页
* @param pageNum
* @param pageSize
*/
public static void startPage(int pageNum, int pageSize) {
localPage.set(new Page(pageNum, pageSize));
}
/**
* 结束分页并返回结果,该方法必须被调用,否则localPage会一直保存下去,直到下一次startPage
* @return
*/
public static Page endPage() {
Page page = localPage.get();
localPage.remove();
return page;
}
@Override
public Object intercept(Invocation invocation) throws Throwable {
if (localPage.get() == null) {
return invocation.proceed();
}
if (invocation.getTarget() instanceof StatementHandler) {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler);
// 分离代理对象链(由于目标类可能被多个拦截器拦截,从而形成多次代理,通过下面的两次循环
// 可以分离出最原始的的目标类)
while (metaStatementHandler.hasGetter("h")) {
Object object = metaStatementHandler.getValue("h");
metaStatementHandler = SystemMetaObject.forObject(object);
}
// 分离最后一个代理对象的目标类
while (metaStatementHandler.hasGetter("target")) {
Object object = metaStatementHandler.getValue("target");
metaStatementHandler = SystemMetaObject.forObject(object);
}
MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");
//分页信息if (localPage.get() != null) {
Page page = localPage.get();
BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
// 分页参数作为参数对象parameterObject的一个属性
String sql = boundSql.getSql();
// 重写sql
String pageSql = buildPageSql(sql, page);
//重写分页sql
metaStatementHandler.setValue("delegate.boundSql.sql", pageSql);
Connection connection = (Connection) invocation.getArgs()[0];
// 重设分页参数里的总页数等
setPageParameter(sql, connection, mappedStatement, boundSql, page);
// 将执行权交给下一个拦截器
return invocation.proceed();
} else if (invocation.getTarget() instanceof ResultSetHandler) {
Object result = invocation.proceed();
Page page = localPage.get();
page.setResult((List) result);
return result;
}
return null;
}
/**
* 只拦截这两种类型的
* StatementHandler
* ResultSetHandler
* @param target
* @return
*/
@Override
public Object plugin(Object target) {
if (target instanceof StatementHandler || target instanceof ResultSetHandler) {
return Plugin.wrap(target, this);
} else {
return target;
}
}
@Override
public void setProperties(Properties properties) {
}
/**
* 修改原SQL为分页SQL
* @param sql
* @param page
* @return
*/
private String buildPageSql(String sql, Page page) {
StringBuilder pageSql = new StringBuilder(200);
pageSql.append("select * from ( select temp.*, rownum row_id from ( ");
pageSql.append(sql);
pageSql.append(" ) temp where rownum <= ").append(page.getEndRow());
pageSql.append(") where row_id > ").append(page.getStartRow());
return pageSql.toString();
}
/**
* 获取总记录数
* @param sql
* @param connection
* @param mappedStatement
* @param boundSql
* @param page
*/
private void setPageParameter(String sql, Connection connection, MappedStatement mappedStatement,
BoundSql boundSql, Page page) {
// 记录总记录数
String countSql = "select count(0) from (" + sql + ")";
PreparedStatement countStmt = null;
ResultSet rs = null;
try {
countStmt = connection.prepareStatement(countSql);
BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql,
boundSql.getParameterMappings(), boundSql.getParameterObject());
setParameters(countStmt, mappedStatement, countBS, boundSql.getParameterObject());
rs = countStmt.executeQuery();
int totalCount = 0;
if (rs.next()) {
totalCount = rs.getInt(1);
}
page.setTotal(totalCount);
int totalPage = totalCount / page.getPageSize() + ((totalCount % page.getPageSize() == 0) ? 0 : 1);
page.setPages(totalPage);
} catch (SQLException e) {
logger.error("Ignore this exception", e);
} finally {
try {
rs.close();
} catch (SQLException e) {
logger.error("Ignore this exception", e);
}
try {
countStmt.close();
} catch (SQLException e) {
logger.error("Ignore this exception", e);
}
}
}
/**
* 代入参数值
* @param ps
* @param mappedStatement
* @param boundSql
* @param parameterObject
* @throws SQLException
*/
private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql,
Object parameterObject) throws SQLException {
ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
parameterHandler.setParameters(ps);
}
/**
* Description: 分页
* Author: liuzh
* Update: liuzh(2014-04-16 10:56)
*/
public static class Page<E> {
private int pageNum;
private int pageSize;
private int startRow;
private int endRow;
private long total;
private int pages;
private List<E> result;
public Page(int pageNum, int pageSize) {
this.pageNum = pageNum;
this.pageSize = pageSize;
this.startRow = pageNum > 0 ? (pageNum - 1) * pageSize : 0;
this.endRow = pageNum * pageSize;
}
public List<E> getResult() {
return result;
}
public void setResult(List<E> result) {
this.result = result;
}
public int getPages() {
return pages;
}
public void setPages(int pages) {
this.pages = pages;
}
public int getEndRow() {
return endRow;
}
public void setEndRow(int endRow) {
this.endRow = endRow;
}
public int getPageNum() {
return pageNum;
}
public void setPageNum(int pageNum) {
this.pageNum = pageNum;
}
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
public int getStartRow() {
return startRow;
}
public void setStartRow(int startRow) {
this.startRow = startRow;
}
public long getTotal() {
return total;
}
public void setTotal(long total) {
this.total = total;
}
@Override
public String toString() {
return "Page{" +
"pageNum=" + pageNum +
", pageSize=" + pageSize +
", startRow=" + startRow +
", endRow=" + endRow +
", total=" + total +
", pages=" + pages +
'}';
}
}
}
使用该拦截器首先需要在Mybatis配置中配置该拦截器:
<plugins> <plugin interceptor="com.mybatis.util.PageHelper"></plugin> </plugins>
配置拦截器的时候需要注意plugins的位置,plugins位置顺序如下:
properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, plugins?, environments?, databaseIdProvider?, mappers?
最后是调用该方法的例子代码(Service层):
@Override
public PageHelper.Page<SysLoginLog> findSysLoginLog(String loginIp,
String username,
String loginDate,
String exitDate,
String logerr,
int pageNumber,
int pageSize) throws BusinessException {
PageHelper.startPage(pageNumber,pageSize);
sysLoginLogMapper.findSysLoginLog(loginIp, username, loginDate, exitDate, logerr);
return PageHelper.endPage();
}
从上面可以看到使用该插件使用起来是很简单的,只需要在查询前后使用PageHelper的startPage和endPage方法即可,中间代码的调用结果已经存在于Page的result中,如果你在一个返回一个结果的地方调用PageHelper,返回的结果仍然是一个List,取第一个值即可(我想没人会在这种地方这么用,当然这样也不出错)。
另外在startPage和endPage中间的所有mybatis代码都会被分页,而且PageHelper只会保留最后一次的结果,因而使用时需要保证每次只在其中执行一个mybatis查询,如果有多个分页,请多次使用startPage和endPage。
由于这里只提供了Oracle的实现,所以我希望参考该分页插件实现的其他数据库的读者也能将相应的代码开源。
项目地址:http://xiazai./201612/yuanma/Mybatis_PageHelper_jb51.zip
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
# mybatis
# 分页插件
# mybatis分页插件使用
# mybatis的分页插件
# Mybatis分页插件PageHelper的使用详解
# Java简单实现SpringMVC+MyBatis分页插件
# mybatis分页插件pageHelper详解及简单实例
# 自己动手写的mybatis分页插件(极其简单好用)
# Mybatis常用分页插件实现快速分页处理技巧
# spring boot和mybatis集成分页插件
# SSM使用mybatis分页插件pagehepler实现分页示例
# SpringBoot集成MyBatis的分页插件PageHelper实例代码
# Mybatis分页插件PageHelper的配置和简单使用方法(推荐)
# Mybatis分页插件使用方法详解
# 分页
# 拦截器
# 我觉得
# 多个
# 只有一个
# 重写
# 方法来
# 开源
# 都是
# 是一个
# 我想
# 也不
# 我不
# 放在
# 太多
# 第一个
# 都没
# 你在
# 我对
# 只需
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
网站设计制作书签怎么做,怎样将网页添加到书签/主页书签/桌面?
简历没回改:利用AI润色让你的文字更专业
javascript中数组(Array)对象和字符串(String)对象的常用方法总结
网站制作价目表怎么做,珍爱网婚介费用多少?
如何在 React 中条件性地遍历数组并渲染元素
如何在建站宝盒中设置产品搜索功能?
如何用IIS7快速搭建并优化网站站点?
Laravel的路由模型绑定怎么用_Laravel Route Model Binding简化控制器逻辑
Laravel如何发送系统通知?(Notification渠道示例)
百度输入法ai面板怎么关 百度输入法ai面板隐藏技巧
济南网站建设制作公司,室内设计网站一般都有哪些功能?
Python正则表达式进阶教程_复杂匹配与分组替换解析
Laravel的Blade指令怎么自定义_创建你自己的Laravel Blade Directives
手机怎么制作网站教程步骤,手机怎么做自己的网页链接?
Laravel如何使用Service Provider服务提供者_Laravel依赖注入与容器绑定【深度】
如何获取免费开源的自助建站系统源码?
百度浏览器网页无法复制文字怎么办 百度浏览器复制修复
潮流网站制作头像软件下载,适合母子的网名有哪些?
Laravel Artisan命令怎么自定义_创建自己的Laravel命令行工具完全指南
Android实现代码画虚线边框背景效果
Python企业级消息系统教程_KafkaRabbitMQ高并发应用
在centOS 7安装mysql 5.7的详细教程
郑州企业网站制作公司,郑州招聘网站有哪些?
动图在线制作网站有哪些,滑动动图图集怎么做?
文字头像制作网站推荐软件,醒图能自动配文字吗?
悟空识字如何进行跟读录音_悟空识字开启麦克风权限与录音
网站制作软件免费下载安装,有哪些免费下载的软件网站?
Laravel模型关联查询教程_Laravel Eloquent一对多关联写法
如何快速搭建高效WAP手机网站吸引移动用户?
详解jQuery停止动画——stop()方法的使用
装修招标网站设计制作流程,装修招标流程?
如何在云主机上快速搭建多站点网站?
轻松掌握MySQL函数中的last_insert_id()
Laravel项目如何进行性能优化_Laravel应用性能分析与优化技巧大全
5种Android数据存储方式汇总
如何构建满足综合性能需求的优质建站方案?
胶州企业网站制作公司,青岛石头网络科技有限公司怎么样?
Laravel如何实现一对一模型关联?(Eloquent示例)
如何快速搭建自助建站会员专属系统?
Laravel如何实现数据导出到CSV文件_Laravel原生流式输出大数据量CSV【方案】
Laravel怎么返回JSON格式数据_Laravel API资源Response响应格式化【技巧】
高性价比服务器租赁——企业级配置与24小时运维服务
邀请函制作网站有哪些,有没有做年会邀请函的网站啊?在线制作,模板很多的那种?
矢量图网站制作软件,用千图网的一张矢量图做公司app首页,该网站并未说明版权等问题,这样做算不算侵权?应该如何解决?
Laravel如何使用Facades(门面)及其工作原理_Laravel门面模式与底层机制
如何制作一个表白网站视频,关于勇敢表白的小标题?
Win11怎么设置默认图片查看器_Windows11照片应用关联设置
HTML透明颜色代码怎么让图片透明_给img元素加透明色的技巧【方法】
Laravel怎么在Controller之外的地方验证数据
Win11怎么开启自动HDR画质_Windows11显示设置HDR选项

