源码下载地址:
https://github.com/pagehelper/Mybatis-PageHelper/tree/25e019186e025aab8b56cd568aea98f583b15fc8
版本:3.7.5
前言:
本文中,类名称和方法名称部分与源码中不同,例:源码:PageHelper(本文:PageUtil);查看代码时,请以源码为准。
内容如有错误或补充,请及时联系、交流。
邮箱地址:[email protected]
①实现org.apache.ibatis.plugin.Interceptor接口,MyBatis初始化时调用setProperties方法,封装sql工具类SqlUtil,同时根据Dialect属性封装具体数据库的Parser类,以及处理MyBatis中MappedStatement的工具类MSUtils(MSUtil)。(Dialect、Parser和MSUtils的封装顺序如下)
PageHelper(PageUtil) :
@Override
public void setProperties(Properties properties) {
// 获取数据库方言,默认是MySql
String dialect = properties.getProperty("dialect") == null ? "mysql" : properties.getProperty("dialect");
sqlUtil = new SqlUtil(dialect);
sqlUtil.setProperties(properties);
}
SqlUtil :
// 处理org.apache.ibatis.mapping.MappedStatement工具类
private MSUtil msUtil;
// 数据库方言枚举类
private Dialect dialect;
// 具体数据库的Parser
private Parser parser;
/**
* SqlUtil构造方法
* @param dialect 数据库方言
*/
public SqlUtil(String strDialect) {
if (strDialect == null || "".equals(strDialect))
throw new IllegalArgumentException("Mybatis分页插件无法获取dialect参数!");
// 根据配置文件中dialect属性,配置数据库方言
dialect = Dialect.of(strDialect);
parser = AbstractParser.newParser(dialect);
msUtil = new MSUtil(parser);
}
AbstractParser :
public abstract class AbstractParser implements Parser, Constant {
// 处理SQL
public static final SqlParser sqlParser = new SqlParser();
public static Parser newParser(Dialect dialect) {
Parser parser = null;
switch (dialect) {
case mysql:
case mariadb:
case sqlite:
parser = new MysqlParser();
break;
case oracle:
parser = new OracleParser();
break;
// 此处省略其他数据库实例对象,详见PageHelper源码com.github.pagehelper.parser.impl.AbstractParser
default:
break;
}
return parser;
}
}
MSUtils(MSUtil) :
public MSUtil(Parser parser) {
this.parser = parser;
}
②封装分页属性
在需要分页的查询之前调用PageHelper(PageUtil)的静态方法startPage(int pageNum, int pageSize)方法;封装一个Page(PageBean)对象并保存到当前线程中(ThreadLocal)。
PageHelper(PageUtil) :
/**
* 开始分页
* @param pageNum 页码
* @param pageSize 每页显示数量
* @param count 是否进行count查询
* @param reasonable 分页合理化,null时用默认配置
* @param pageSizeZero true且pageSize=0时返回全部结果,false时分页,null时用默认配置
* @return
*/
public static PageBean startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {
PageBean pageBean = new PageBean(pageNum, pageSize, count);
pageBean.setReasonable(reasonable);
pageBean.setPageSizeZero(pageSizeZero);
// 将PageBean保存至当前线程
SqlUtil.setLocalPage(pageBean);
return pageBean;
}
③执行查询方法;调用PageHelper(PageUtil)的实现的plugin(Object target)方法生成代理对象。
PageHelper(PageUtil) :
/**
* 只拦截Executor
* @param target
* @return
*/
@Override
public Object plugin(Object target) {
if (target instanceof Executor) {
return Plugin.wrap(target, this);
} else {
return target;
}
}
④调用PageHelper(PageUtil)的实现的intercept(Invocation invocation)开始执行分页。
PageHelper(PageUtil) :
/**
* MyBatis拦截器方法
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
return sqlUtil.processPage(invocation);
}
①调用SqlUtil类的 _processPage(Invocation invocation)执行分页。
SqlUtil :
/**
* MyBatis拦截器调用方法
* @param invocation 拦截器入参
* @return
* @throws Throwable
*/
public Object processPage(Invocation invocation) throws Throwable {
try {
Object result = _processPage(invocation);
// 返回查询结果
return result;
} finally {
// 清除当前线程的PageBean对象
clearLocalPageBean();
}
}
②org.apache.ibatis.plugin.Invocation对象内容。
public class Invocation {
// org.apache.ibatis.executor.CachingExecutor
private Object target;
private Method method;
private Object[] args;
}
args[0] : org.apache.ibatis.mapping.MappedStatement(描述Mapper配置文件中的一个select/update/insert/delete节点)
args[1] : MapperMethod(存储查询条件值的Map容器)
args[2] : org.apache.ibatis.session.RowBounds(RowBounds用于MyBatis自带的分页功能)
以MySql为例:
获得总记录数count(*) from (sql)
实现分页 sql limit ?, ?
1、禁用MyBatis自带的分页方法,即将RowBounds对象清空。
2、生成新的MappedStatement对象,将SQL语句(MappedStatement中SqlSource封装了预加载的SQL语句)进行替换,分别增加count(*)和limit ?, ?;并在SqlSource中的List添加分页属性的ParameterMapping对象。
3、在实现分页时,将分页属性封装到args[1]中。
③执行分页方法:
SqlUtil :
/**
* MyBatis拦截器调用方法
* @param invocation
* @return
* @throws Throwable
*/
private Object _processPage(Invocation invocation) throws Throwable {
final Object[] args = invocation.getArgs();
// RowBounds用于MyBatis自带分页功能
RowBounds rowBounds = (RowBounds)args[2];
// DEFAULT = new RowBounds()
if (SqlUtil.getLocalPagebean() == null && rowBounds == RowBounds.DEFAULT) {
// 没有采用分页
return invocation.proceed();
} else {
// 开始分页
// 忽略RowBounds,否则会进行MyBatis自带的内存分页
args[2] = RowBounds.DEFAULT;
// 分页信息
PageBean pageBean = getPage(rowBounds);
if ((pageBean.getPageSizeZero() != null && pageBean.getPageSizeZero()) && pageBean.getPageSize() == 0) {
// 不进行分页
Object result = invocation.proceed();
// 得到处理结果
pageBean.addAll((List)result);
pageBean.setPageNum(1);
pageBean.setPageSize(pageBean.size());
pageBean.setTotal(pageBean.size());
return pageBean;
}
// 进行分页
// 获取原始的MappedStatement
MappedStatement ms = (MappedStatement)args[0];
// 获得SQL对象
SqlSource sqlSource = ms.getSqlSource();
// 通过total值判断是否进行count查询
if (pageBean.isCount()) {
// 将参数中的MappedStatement替换为新的qs(count(*)总记录数)
msUtil.processCountMappedStatement(ms, sqlSource, args);
// 查询总数
Object sqlCountResult = invocation.proceed();
// 设置总数
pageBean.setTotal((Integer)((List)sqlCountResult).get(0));
if (pageBean.getTotal() == 0)
return pageBean;
}
// pageSize > 0时执行分页查询,pageSize <= 0时不执行相当于只返回了一个count
if (pageBean.getPageSize() > 0 &&
((rowBounds == RowBounds.DEFAULT && pageBean.getPageNum() > 0)
|| rowBounds != RowBounds.DEFAULT)) {
// 将参数中的MappedStatement替换为新的qs(limit分页)
msUtil.processPageMappedStatement(ms, sqlSource, pageBean, args);
// 执行分页查询
Object sqlResult = invocation.proceed();
// 得到处理结果
pageBean.addAll((List)sqlResult);
}
return pageBean;
}
}
SqlUtil获得总记录数和执行分页查询时,分别调用了MSUtils(MSUtil)的processCountMappedStatement(MappedStatement ms, SqlSource sqlSource, Object[] args)和processPageMappedStatement(MappedStatement mappedStatement, SqlSource sqlSource, PageBean pageBean, Object[] args)。
①计算总记录数processCountMappedStatement方法:
MSUtils(MSUtil) :
/**
* 处理count查询的MappedStatement
* @param ms
* @param sqlsource
* @param args
*/
public void processCountMappedStatement(MappedStatement ms, SqlSource sqlSource, Object[] args) {
args[0] = getMappedStatement(ms, sqlSource, args[1], SUFFIX_COUNT);
}
1、根据ID + 后缀(MappedStatement的id属性 + ‘_PageHelper_Count’)查询缓存,是否有MappedStatement对象。
2、若获取缓存失败,则需要创建新的MappedStatement对象。取出具体SqlSource的实现类(MSUtils的getSqlSource(MappedStatement mappedStatement, SqlSource sqlSource, Object parameterObject, boolean count)方法)。统计总记录数时属于StaticSqlSource类。
3、修改SQL语句,调用Parser实现类的getCountSql(String sql_count)方法(实际com.github.pagehelper.parser.SqlParser类的getSmartCountSql(String sql)方法);得到count(*) from (sql)的语句,封装至StaticSqlSource对象并返回。
MSUtils(MSUtil) :
/**
* 获取静态count的SqlSource
* @param configuration
* @param sqlSource
* @param parameterObject
* @return
*/
public SqlSource getStaticCountSqlSource(Configuration configuration, SqlSource sqlSource, Object parameterObject) {
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
return new StaticSqlSource(configuration, parser.getCountSql(boundSql.getSql()), boundSql.getParameterMappings());
}
4、根据新生成的StaticSqlSource对象,创建一个新的MappedStatement对象(MSUtils的newMappedStatement(MappedStatement ms, SqlSource sqlSource, String suffix)方法);并保存到缓存。
5、将新的MappedStatement替换org.apache.ibatis.plugin.Invocation的args[0]。
6、执行查询。
②分页查询processPageMappedStatement方法:
MSUtils(MSUtil) :
/**
* 处理分页查询的MappedStatement
* @param ms
* @param sqlSource
* @param pageBean
* @param args
*/
public void processPageMappedStatement(MappedStatement mappedStatement, SqlSource sqlSource, PageBean pageBean, Object[] args) {
args[0] = getMappedStatement(mappedStatement, sqlSource, args[1], SUFFIX_PAGE);
// 处理入参
args[1] = setPageParameter((MappedStatement) args[0], args[1], pageBean);
}
1、根据ID + 后缀(MappedStatement的id属性 + ‘_PageHelper’)查询缓存,是否有MappedStatement对象。
2、若获取缓存失败,则需要创建新的MappedStatement对象。取出具体SqlSource的实现类(MSUtils的getSqlSource(MappedStatement mappedStatement, SqlSource sqlSource, Object parameterObject, boolean count)方法)(动态SQL:PageUtilDynamicSqlSource;注解式SQL:PageProviderSqlSource或静态SQL:StaticPageSqlSource)。
以静态SQL对象为例:
MSUtils(MSUtil) :
/**
* 获取静态的分页SqlSource
* ParameterMappings需要增加分页参数的映射
* @param configuration
* @param sqlSource
* @param parameterObject
* @return
*/
public SqlSource getStaticPageSqlSource(Configuration configuration, SqlSource sqlSource, Object parameterObject) {
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
return new StaticSqlSource(configuration, parser.getPageSql(boundSql.getSql()), parser.getPageParameterMapping(configuration, boundSql));
}
3、修改SQL语句,调用Parser实现类的getPageSql(String sql)方法(根据Dialect属性实现的子类,比如针对MySql方言的com.github.pagehelper.parser.impl.MysqlParser类);在原查询语句的末尾添加’limit ?,?’。
以MySql方言为例:
MySqlParser :
@Override
public String getPageSql(String sql) {
StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14);
sqlBuilder.append(sql);
sqlBuilder.append(" limit ?,?");
return sqlBuilder.toString();
}
4、由于增加条件参数,对比统计总记录数的方法,需要讲SqlSource中的List进行替换,并添加分页属性的ParameterMapping对象。
Contant(用于存储常量) :
// 分页的ID后缀
String SUFFIX_PAGE = "_PageHelper";
// 第一个分页参数
String PAGEPARAMETER_FIRST = "First" + SUFFIX_PAGE;
// 第二个分页参数
String PAGEPARAMETER_SECOND = "Second" + SUFFIX_PAGE;
AbstractParser :
public List<ParameterMapping> getPageParameterMapping(Configuration configuration, BoundSql boundSql) {
List<ParameterMapping> newParameterMappings = new ArrayList<ParameterMapping>();
if (boundSql.getParameterMappings() != null) {
newParameterMappings.addAll(boundSql.getParameterMappings());
}
newParameterMappings.add(new ParameterMapping.Builder(configuration, PAGEPARAMETER_FIRST, Integer.class).build());
newParameterMappings.add(new ParameterMapping.Builder(configuration, PAGEPARAMETER_SECOND, Integer.class).build());
return newParameterMappings;
}
5、根据新生成的SqlSource对象,创建一个新的MappedStatement对象(MSUtils的newMappedStatement(MappedStatement ms, SqlSource sqlSource, String suffix)方法);并保存到缓存。
6、将新的MappedStatement替换org.apache.ibatis.plugin.Invocation的args[0]。
7、处理入参(MSUtils的setPageParameter(MappedStatement mappedStatement, Object parameterObject, PageBean pageBean)方法)。
MSUtils(MSUtil) :
/**
* 设置分页参数 limit ? ?
* @param mappedStatement
* @param object
* @param pageBean
* @return
*/
private Map setPageParameter(MappedStatement mappedStatement, Object parameterObject, PageBean pageBean) {
BoundSql boundSql = mappedStatement.getBoundSql(parameterObject);
return parser.setPageParameter(mappedStatement, parameterObject, boundSql, pageBean);
}
以MySql方言为例:
MySqlParser :
@Override
public Map setPageParameter(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql, PageBean pageBean) {
// MySqlParser 继承 AbstractParser
Map paramMap = super.setPageParameter(mappedStatement, parameterObject, boundSql, pageBean);
paramMap.put(PAGEPARAMETER_FIRST, pageBean.getStartRow());
paramMap.put(PAGEPARAMETER_SECOND, pageBean.getPageSize());
return paramMap;
}
AbstractParser的setPageParameter(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql, PageBean pageBean)方法详见源码com.github.pagehelper.parser.impl.AbstractParser.setPageParameter(MappedStatement ms, Object parameterObject, BoundSql boundSql, Page page)。
8、将新的Map替换org.apache.ibatis.plugin.Invocation的args[1]。
9、执行查询。