先说一下pageHepler的简单使用:
//设置当前页和每页大小
PageHelper.startPage(Integer.parseInt(jobListParam.getCurrentPage()), Integer.parseInt(jobListParam.getPageSize()));
//在startPage后的第一个查询语句将会被分页
List jobs=new ArrayList<>();
if (CommonUtil.isNotNullList(jobNums)) {
jobs = iJobDao.selectByJobNumList(jobNums);
logger.info("jobs instanceof Page:{}", jobs instanceof Page);
logger.info("查询结果:{}", JSON.toJSONString(jobs));
} //PageInfo返回的就是分页后的数据 PageInfo page=new PageInfo<>(jobs)
xml配置:
pageHepler的使用相当的简单,我们接下来来看一下原理:
pageHepler的实现是基于Mybatis对插件的支持(实际是拦截器Interceptor)
在 Mybaits 中允许用插件来拦截的方法包括:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)--封装了统一的增删改查,对外暴露增删改查
- StatementHandler (prepare, parameterize, batch, update, query)--封装了sql的具体执行步骤,例如prepare(准备语句)、parameterize(参数化)、query(查询)等
- ParameterHandler (getParameterObject, setParameters)--在StatementHandler的参数化阶段给PreparedStatement设置参数的
- ResultSetHandler (handleResultSets, handleOutputParameters)--在StatementHandler执行阶段(例如query)处理结果
而pageHepler是拦截的Executor阶段,通过修改
先说PageHelper.startPage()
/**
* 开始分页
*
* @param pageNum 页码
* @param pageSize 每页显示数量
* @param count 是否进行count查询
* @param reasonable 分页合理化,null时用默认配置
* @param pageSizeZero true且pageSize=0时返回全部结果,false时分页,null时用默认配置
*/
public static Page startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {
//这里创建了一个Page,而Page继承了ArrayList,实际上Page就是一个集合
Page page = new Page(pageNum, pageSize, count);
page.setReasonable(reasonable);
page.setPageSizeZero(pageSizeZero);
//当已经执行过orderBy的时候
Page oldPage = SqlUtil.getLocalPage();
if (oldPage != null && oldPage.isOrderByOnly()) {
page.setOrderBy(oldPage.getOrderBy());
}
//这里是将page放到一个ThreadLocal中,以便使用的时候直接get
//private static final ThreadLocal LOCAL_PAGE = new ThreadLocal();
SqlUtil.setLocalPage(page);
return page;
}
接下来看PageHelper如何执行
首先PageHelper实现了Mybatis的Interceptor接口,我们来看一下Mybatis的Interceptor接口在什么时候执行(这里只研究Executor)
当时用
sqlSessionFactory.openSession()的时候,会创建执行器:final Executor executor = configuration.newExecutor(tx, execType);
在创建执行器的过程中会查询所有的插件,于是PageHelper开始执行
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
//这句再做一下保护,囧,防止粗心大意的人将defaultExecutorType设成null?
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
//然后就是简单的3个分支,产生3种执行器BatchExecutor/ReuseExecutor/SimpleExecutor
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
//如果要求缓存,生成另一种CachingExecutor(默认就是有缓存),装饰者模式,所以默认都是返回CachingExecutor
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
//此处调用插件,通过插件可以改变Executor行为
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
//这里可以看到是调用了plugin方法,然后返回了一个object,并在上面的代码中强制转化为一个Executor,通过这里我们猜测一下,这个object极有可能是一个代理类
//我们接下来代码中看PageHelper的plugin方法
public Object pluginAll(Object target) {
//循环调用每个Interceptor.plugin方法
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
@Intercepts(@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}))
public class PageHelper implements Interceptor {
//sql工具类
private SqlUtil sqlUtil;
//属性参数信息
private Properties properties;
//配置对象方式
private SqlUtilConfig sqlUtilConfig;
//自动获取dialect,如果没有setProperties或setSqlUtilConfig,也可以正常进行
private boolean autoDialect = true;
//运行时自动获取dialect
private boolean autoRuntimeDialect;
//多数据源时,获取jdbcurl后是否关闭数据源
private boolean closeConn = true;
//缓存
private Map urlSqlUtilMap = new ConcurrentHashMap();
/**
* 获取任意查询方法的count总数
*
* @param select
* @return
*/
public static long count(ISelect select) {
Page> page = startPage(1, -1, true);
select.doSelect();
return page.getTotal();
}
/**
* Mybatis拦截器方法
*
* @param invocation 拦截器入参
* @return 返回执行结果
* @throws Throwable 抛出异常
*/
//我们这里知道是Plugin的invoke方法执行然后调用到了这里
//Invocation类,参数是target=CachingExecutor,method=Executor中指定的query方法方法,args={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}。因为只有这个参数才会进到这个方法中
public Object intercept(Invocation invocation) throws Throwable {
//默认是false
if (autoRuntimeDialect) {
//获取对应的分页解析器
//具体下面有解释
SqlUtil sqlUtil = getSqlUtil(invocation);
return sqlUtil.processPage(invocation);
} else {
//这里默认是true
if (autoDialect) {
initSqlUtil(invocation);
}
return sqlUtil.processPage(invocation);
}
}
/**
* 初始化sqlUtil
*
* @param invocation
*/
public synchronized void initSqlUtil(Invocation invocation) {
if (this.sqlUtil == null) {
this.sqlUtil = getSqlUtil(invocation);
//默认是false
if (!autoRuntimeDialect) {
//属性参数信息
properties = null;
//配置对象方式
sqlUtilConfig = null;
}
autoDialect = false;
}
}
/**
* 获取url
*
* @param dataSource
* @return
*/
public String getUrl(DataSource dataSource){
Connection conn = null;
try {
conn = dataSource.getConnection();
return conn.getMetaData().getURL();
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
if(conn != null){
try {
if(closeConn){
conn.close();
}
} catch (SQLException e) {
//ignore
}
}
}
}
/**
* 根据daatsource创建对应的sqlUtil
*
* @param invocation
*/
//参数是target=CachingExecutor,method=Executor中指定的query方法方法,args={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
public SqlUtil getSqlUtil(Invocation invocation) {
//不要问这里为啥就是MappedStatement,上面已经说过了,指定的
MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
//改为对dataSource做缓存,这里是获取数据源
DataSource dataSource = ms.getConfiguration().getEnvironment().getDataSource();
//获取链接,没啥意思,这里不用看了
String url = getUrl(dataSource);
//默认是空的,首次肯定进不来
if (urlSqlUtilMap.containsKey(url)) {
return urlSqlUtilMap.get(url);
}
//防止并发
ReentrantLock lock = new ReentrantLock();
try {
lock.lock();
if (urlSqlUtilMap.containsKey(url)) {
return urlSqlUtilMap.get(url);
}
if (StringUtil.isEmpty(url)) {
throw new RuntimeException("无法自动获取jdbcUrl,请在分页插件中配置dialect参数!");
}
//获取对应的数据库类型枚举
String dialect = Dialect.fromJdbcUrl(url);
if (dialect == null) {
throw new RuntimeException("无法自动获取数据库类型,请通过dialect参数指定!");
}
//创建SqlUtil,设置了mysql的分页解析器(其实就是加了 limit ?,?)
SqlUtil sqlUtil = new SqlUtil(dialect);
if (this.properties != null) {
sqlUtil.setProperties(properties);
} else if (this.sqlUtilConfig != null) {
sqlUtil.setSqlUtilConfig(this.sqlUtilConfig);
}
//设置数据库链接(因为是唯一的),对应的工具类
urlSqlUtilMap.put(url, sqlUtil);
return sqlUtil;
} finally {
lock.unlock();
}
}
/**
* 只拦截Executor,这里是mybatis调用plugin方法的地方,由上面猜测,传入的参数是CachingExecutor,这里返回的应该是一个代理类
* 我们接下来来看Plugin.wrap()方法,再下一个代码块里
*
* @param target
* @return
*/
public Object plugin(Object target) {
if (target instanceof Executor) {
//target=CachingExecutor
//this=PageHelper
return Plugin.wrap(target, this);
} else {
return target;
}
}
}
//这里是Plugin,可以看到它实现了InvocationHandler接口,重写了invoke方法
public class Plugin implements InvocationHandler {
//这里就是CachingExecutor
private final Object target;
//这里的就是PageHelper
private final Interceptor interceptor;
//
private final Map<Class>, Set> signatureMap;
private Plugin(Object target, Interceptor interceptor, Map<Class>, Set> signatureMap) {
this.target = target;
this.interceptor = interceptor;
this.signatureMap = signatureMap;
}
//上面代码块中的plugin方法中调用的就是这里
//target=CachingExecutor,interceptor=PageHelper
public static Object wrap(Object target, Interceptor interceptor) {
//这个方法下面有解释,最终返回的是
//
Map<Class>, Set> signatureMap = getSignatureMap(interceptor);
//获取CachingExecutor.class
Class> type = target.getClass();
//type=CachingExecutor.class,signatureMap=
//这里返回的是Executor.class接口
Class>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
//通过这里我们可以看到创建了代理对象并返回,然后我们在mybatis中拿到的Executor就是这里的代理对象,而Executor执行任何方法时都会到我们的invoke方法中方法中(动态代理原理)
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
//Executor执行任何方法时都会到这里
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//这里的method可能是Executor中的任何方法,method.getDeclaringClass()是Executor.class
Set methods = signatureMap.get(method.getDeclaringClass());
//上面的代码中我们知道methods中只有Executor中指定的query方法,因此只有PageHelper指定的那个方法才会进入到这个判断中
if (methods != null && methods.contains(method)) {
//这里开始调用PageHelper的intercept方法
//并创建了一个Invocation类,参数是target=CachingExecutor,method=Executor中指定的query方法方法,args={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}。因为只有这个参数才会进到这个方法中
//@Intercepts注解中指定了,Invocation下面的代码块中有解释
//然后我们再回到PageHelper的intercept方法
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
//这里是通过反射获取PageHelper上的注解
//@Intercepts(@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}))
private static Map<Class>, Set> getSignatureMap(Interceptor interceptor) {
//拿到@Intercepts注解
Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
// issue #251
if (interceptsAnnotation == null) {
throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
}
//获取注解中的值:@Signature[]
Signature[] sigs = interceptsAnnotation.value();
//这里存放的是key是Executor.class,methods存放的是Executor中指定的query方法
Map<Class>, Set> signatureMap = new HashMap<Class>, Set>();
for (Signature sig : sigs) {
Set methods = signatureMap.get(sig.type());
if (methods == null) {
methods = new HashSet();
//这的key是Executor.class,methods目前是空
signatureMap.put(sig.type(), methods);
}
try {
//sig.method()=query
//sig.args()={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
//这里是获取Executor中的的query方法,并且参数是固定的
//通过这个方法可以拿到Executor接口中的:
// List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
//不知道的去看一下Executor接口中的方法
Method method = sig.type().getMethod(sig.method(), sig.args());
methods.add(method);
} catch (NoSuchMethodException e) {
throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
}
}
return signatureMap;
}
//这里入参是:type=CachingExecutor.class,signatureMap=
private static Class>[] getAllInterfaces(Class> type, Map<Class>, Set> signatureMap) {
Set<Class>> interfaces = new HashSet<Class>>();
while (type != null) {
//获取CachingExecutor.class接口Executor.class
for (Class> c : type.getInterfaces()) {
//判断是否含有Executor.class,我们知道是有的
if (signatureMap.containsKey(c)) {
//将Executor.class添加到set中
interfaces.add(c);
}
}
//将Executor.class从新指给type
type = type.getSuperclass();
}
return interfaces.toArray(new Class>[interfaces.size()]);
}
}
public class Invocation {
//target=CachingExecutor
private final Object target;
//method=Executor中指定的query方法方法
private final Method method;
//args={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
private final Object[] args;
public Invocation(Object target, Method method, Object[] args) {
this.target = target;
this.method = method;
this.args = args;
}
public Object getTarget() {
return target;
}
public Method getMethod() {
return method;
}
public Object[] getArgs() {
return args;
}
public Object proceed() throws InvocationTargetException, IllegalAccessException {
//通过反射执行Executor中指定的query方法方法中的执行指定参数
return method.invoke(target, args);
}
public class SqlUtil implements Constant {
private static final ThreadLocal LOCAL_PAGE = new ThreadLocal();
//params参数映射
private static Map PARAMS = new HashMap(5);
//request获取方法
private static Boolean hasRequest;
private static Class> requestClass;
private static Method getParameterMap;
//缓存count查询的ms
private static final Map msCountMap = new ConcurrentHashMap();
//RowBounds参数offset作为PageNum使用 - 默认不使用
private boolean offsetAsPageNum = false;
//RowBounds是否进行count查询 - 默认不查询
private boolean rowBoundsWithCount = false;
//当设置为true的时候,如果pagesize设置为0(或RowBounds的limit=0),就不执行分页,返回全部结果
private boolean pageSizeZero = false;
//分页合理化
private boolean reasonable = false;
//具体针对数据库的parser
//MysqlParser
private Parser parser;
//是否支持接口参数来传递分页参数,默认false
private boolean supportMethodsArguments = false;
/**
* 构造方法
*
* @param strDialect
*/
public SqlUtil(String strDialect) {
Exception exception = null;
try {
//通过名字获取对应的数据库枚举
Dialect dialect = Dialect.of(strDialect);
//获取的就是MysqlParser,因为我是用的就是mysql
parser = AbstractParser.newParser(dialect);
} catch (Exception e) {
exception = e;
//异常的时候尝试反射,允许自己写实现类传递进来
}
}
/**
* 获取Page参数
*
* @return
*/
public static Page getLocalPage() {
return LOCAL_PAGE.get();
}
public static void setLocalPage(Page page) {
LOCAL_PAGE.set(page);
}
/**
* 移除本地变量
*/
public static void clearLocalPage() {
LOCAL_PAGE.remove();
}
/**
* 是否已经处理过
*
* @param ms
* @return
*/
public boolean isPageSqlSource(MappedStatement ms) {
if (ms.getSqlSource() instanceof PageSqlSource) {
return true;
}
return false;
}
/**
* 修改SqlSource
*
* @param ms
* @throws Throwable
*/
//这里修改后才会在对应的sql中加上limit参数
public void processMappedStatement(MappedStatement ms) throws Throwable {
SqlSource sqlSource = ms.getSqlSource();
MetaObject msObject = SystemMetaObject.forObject(ms);
SqlSource pageSqlSource;
if (sqlSource instanceof StaticSqlSource) {
pageSqlSource = new PageStaticSqlSource((StaticSqlSource) sqlSource);
} else if (sqlSource instanceof RawSqlSource) {
//我们来看这个
pageSqlSource = new PageRawSqlSource((RawSqlSource) sqlSource);
} else if (sqlSource instanceof ProviderSqlSource) {
pageSqlSource = new PageProviderSqlSource((ProviderSqlSource) sqlSource);
} else if (sqlSource instanceof DynamicSqlSource) {
pageSqlSource = new PageDynamicSqlSource((DynamicSqlSource) sqlSource);
} else {
throw new RuntimeException("无法处理该类型[" + sqlSource.getClass() + "]的SqlSource");
}
//这里将新的SqlSource放到MappedStatement中,这样当真正的Excutor执行时,从MappedStatement中getBoundSql时,实际是从sqlSource中getBoundSql获取,而我们已经将我们的sqlSource放进去了,所以会执行对应的sql
msObject.setValue("sqlSource", pageSqlSource);
//由于count查询需要修改返回值,因此这里要创建一个Count查询的MS
msCountMap.put(ms.getId(), MSUtils.newCountMappedStatement(ms));
}
/**
* 获取分页参数
*
* @param args
* @return 返回Page对象
*/
public Page getPage(Object[] args) {
//就是从ThreadLocal中拿到,我们最开始放进去的了
Page page = getLocalPage();
//因为我设置了,所以不会走这里
if (page == null || page.isOrderByOnly()) {
Page oldPage = page;
//这种情况下,page.isOrderByOnly()必然为true,所以不用写到条件中
if ((args[2] == null || args[2] == RowBounds.DEFAULT) && page != null) {
return oldPage;
}
if (args[2] instanceof RowBounds && args[2] != RowBounds.DEFAULT) {
RowBounds rowBounds = (RowBounds) args[2];
if (offsetAsPageNum) {
page = new Page(rowBounds.getOffset(), rowBounds.getLimit(), rowBoundsWithCount);
} else {
page = new Page(new int[]{rowBounds.getOffset(), rowBounds.getLimit()}, rowBoundsWithCount);
//offsetAsPageNum=false的时候,由于PageNum问题,不能使用reasonable,这里会强制为false
page.setReasonable(false);
}
} else {
try {
page = getPageFromObject(args[1]);
} catch (Exception e) {
return null;
}
}
if (oldsPage != null) {
page.setOrderBy(oldPage.getOrderBy());
}
setLocalPage(page);
}
//分页合理化
if (page.getReasonable() == null) {
//设置为false
page.setReasonable(reasonable);
}
//当设置为true的时候,如果pagesize设置为0(或RowBounds的limit=0),就不执行分页,返回全部结果
if (page.getPageSizeZero() == null) {
//设置为false
//当设置为true的时候,如果pagesize设置为0(或RowBounds的limit=0),就不执行分页,返回全部结果
page.setPageSizeZero(pageSizeZero);
}
return page;
}
/**
* Mybatis拦截器方法,这一步嵌套为了在出现异常时也可以清空Threadlocal
*
* @param invocation 拦截器入参
* @return 返回执行结果
* @throws Throwable 抛出异常
*/
public Object processPage(Invocation invocation) throws Throwable {
try {
Object result = _processPage(invocation);
return result;
} finally {
clearLocalPage();
}
}
/**
* Mybatis拦截器方法
*
* @param invocation 拦截器入参
* @return 返回执行结果
* @throws Throwable 抛出异常
*/
//从Pagehepler中的intercept到这里
//参数是target=CachingExecutor,method=Executor中指定的query方法方法,args={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
private Object _processPage(Invocation invocation) throws Throwable {
//{MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
final Object[] args = invocation.getArgs();
Page page = null;
//支持方法参数时,会先尝试获取Page
//默认是false
if (supportMethodsArguments) {
page = getPage(args);
}
//分页信息
RowBounds rowBounds = (RowBounds) args[2];
//支持方法参数时,如果page == null就说明没有分页条件,不需要分页查询
if ((supportMethodsArguments && page == null)
//当不支持分页参数时,判断LocalPage和RowBounds判断是否需要分页
|| (!supportMethodsArguments && SqlUtil.getLocalPage() == null && rowBounds == RowBounds.DEFAULT)) {
return invocation.proceed();
} else {
//不支持分页参数时,page==null,这里需要获取
//实际会进入到这里
if (!supportMethodsArguments && page == null) {
//从
page = getPage(args);
}
//开始真正执行分页
return doProcessPage(invocation, page, args);
}
}
/**
* Mybatis拦截器方法
*
* @param invocation 拦截器入参
* @return 返回执行结果
* @throws Throwable 抛出异常
*/
//参数是target=CachingExecutor,method=Executor中指定的query方法方法,args={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
//args=MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class
private Page doProcessPage(Invocation invocation, Page page, Object[] args) throws Throwable {
//保存RowBounds状态
RowBounds rowBounds = (RowBounds) args[2];
//获取原始的ms
MappedStatement ms = (MappedStatement) args[0];
//判断并处理为PageSqlSource
if (!isPageSqlSource(ms)) {
//这里是设置对应的SqlSource,方便分页,里面有原始时期sql
processMappedStatement(ms);
}
//设置当前的parser,后面每次使用前都会set,ThreadLocal的值不会产生不良影响
((PageSqlSource)ms.getSqlSource()).setParser(parser);
try {
//忽略RowBounds-否则会进行Mybatis自带的内存分页
args[2] = RowBounds.DEFAULT;
//如果只进行排序 或 pageSizeZero的判断
if (isQueryOnly(page)) {
return doQueryOnly(page, invocation);
}
//简单的通过total的值来判断是否进行count查询
if (page.isCount()) {
page.setCountSignal(Boolean.TRUE);
//替换MS
args[0] = msCountMap.get(ms.getId());
//查询总数
Object result = invocation.proceed();
//还原ms
args[0] = ms;
//设置总数
page.setTotal((Integer) ((List) result).get(0));
if (page.getTotal() == 0) {
return page;
}
} else {
page.setTotal(-1l);
}
//pageSize>0的时候执行分页查询,pageSize<=0的时候不执行相当于可能只返回了一个count if page.getpagesize> 0 &&
((rowBounds == RowBounds.DEFAULT && page.getPageNum() > 0)
|| rowBounds != RowBounds.DEFAULT)) {
//将参数中的MappedStatement替换为新的qs
page.setCountSignal(null);
//这里的args[1]就是query方法
//实际调用了PageStaticSqlSource中的getPageBoundSql对原始sql进行拼接,
//并将对应的当前页和每页大小设置到了ParameterMapping中,key分别是First_PageHelper、Second_PageHelper,value对应的是当前页和每页大小
//参看PageStaticSqlSource代码块
BoundSql boundSql = ms.getBoundSql(args[1]);
//这里是将startRow、pageSize放进去,其实就是limit后的两个参数,并重新赋值给原始参数,这里相当于修改了原始参数
args[1] = parser.setPageParameter(ms, args[1], boundSql, page);
page.setCountSignal(Boolean.FALSE);
//执行分页查询
//开始执行分页
//开始执行真正的query方法,在进行参数化时,调用DefaultParameterHandler的setParameters设置原始值和分页参数值,真正是由TypeHandler(BaseTypeHandler)的setParameters进行设置原始值和分页参数值
//然后调用PreparedStatement的execute方法从而进行分页
Object result = invocation.proceed();
//得到处理结果,最后返回的是一个page,但是page继承list
page.addAll((List) result);
}
} finally {
((PageSqlSource)ms.getSqlSource()).removeParser();
}
//返回结果
return page;
}
}
public class PageStaticSqlSource extends PageSqlSource {
private String sql;
private List parameterMappings;
private Configuration configuration;
private SqlSource original;
@SuppressWarnings("unchecked")
public PageStaticSqlSource(StaticSqlSource sqlSource) {
MetaObject metaObject = SystemMetaObject.forObject(sqlSource);
//原始sql
this.sql = (String) metaObject.getValue("sql");
//所有的参数
this.parameterMappings = (List) metaObject.getValue("parameterMappings");
this.configuration = (Configuration) metaObject.getValue("configuration");
this.original = sqlSource;
}
@Override
protected BoundSql getPageBoundSql(Object parameterObject) {
String tempSql = sql;
String orderBy = PageHelper.getOrderBy();
if (orderBy != null) {
tempSql = OrderByParser.converToOrderBySql(sql, orderBy);
}
//调用MysqlParser的getPageSql方法,拼接了 limit ?,?
tempSql = localParser.get().getPageSql(tempSql);
//并将对应的当前页和每页大小设置到了ParameterMapping中,key分别是First_PageHelper、Second_PageHelper,value对应的是当前页和每页大小
//这里的BoundSql设置好分页sql和后面的参数后,在mybatis中会进行set
return new BoundSql(configuration, tempSql, localParser.get().getPageParameterMapping(configuration, original.getBoundSql(parameterObject)), parameterObject);
}
}
public class MysqlParser extends AbstractParser {
@Override
public String getPageSql(String sql) {
StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14);
sqlBuilder.append(sql);
sqlBuilder.append(" limit ?,?");
return sqlBuilder.toString();
}
@Override
public Map setPageParameter(MappedStatement ms, Object parameterObject, BoundSql boundSql, Page> page) {
Map paramMap = super.setPageParameter(ms, parameterObject, boundSql, page);
paramMap.put(PAGEPARAMETER_FIRST, page.getStartRow());
paramMap.put(PAGEPARAMETER_SECOND, page.getPageSize());
return paramMap;
}
}
简单理解就是:
通过PageHelper的startPage方法,将当前页和每页大小等字段放到ThreadLocal中。
PageHelper 实现了 Mybatis的Interceptor接口,在创建CachingExecutor时调用Interceptor的plugin方法生成CachingExecutor的代理对象。
当用户执行查询的时候PageHelper的intercept会执行(实际就是invoke方法),然后创建sqlUtil并执行doProcessPage方法开始处理分页数据。
在doProcessPage方法中,通过创建新的PageSqlSource取代掉MappedStatement中原来的SqlSource,新的PageSqlSource中的BoundSql和parameterMappings已经是PageHelper处理后的,加上了limit ?,?和对应的分页参数(通过对应数据库的解析器,例如MysqlParser修改sql和分页参数)。
同时在doProcessPage方法中,在执行真正的CachingExecutor的query方法前修改了用户的参数传递,在原来基础上增加了key=First_PageHelper,value=startRow、key=Second_PageHelper,value=pageSize
当真正的CachingExecutor的query方法执行时,通过MappedStatement拿到的BoundSql实际是从新的SqlSource中的getPageBoundSql中拿的,而这个BoundSql实际我们将已经将sql加上了分页信息,同时拿到的parameterMappings集合参数中也加上了分页参数的key的名字(First_PageHelper、Second_PageHelper)。
然后在StatementHandler的parameterize参数化阶段通过parameterHandler的参数处理器给PreparedStatement中的每个位置的参数对应上,然后通过TypeHandler处理每个类型映射值,然后PreparedStatement执行获取最终结果,继续回到PageHelper的代理对象中,给page设置查询结果并返回。
本文暂时没有评论,来添加一个吧(●'◡'●)