首先现在已经有很多Mybatis源码分析的文章,之所以重复造轮子,只是为了督促自己更好的理解源码。

1.先看一段PageHelper拦截器的配置,在mybatis的配置文件<configuration>标签下配置。

<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!--当设置为true的时候,如果pagesize设置为0(或RowBounds的limit=0),就不执行分页,返回全部结果-->
<property name="pageSizeZero" value="false"/>
</plugin>
</plugins>

1.1 其它的PageHelper的属性配置以及默认值可以参考com.github.pagehelper.page.PageParams类

public class PageParams {
//RowBounds参数offset作为PageNum使用 - 默认不使用
protected boolean offsetAsPageNum = false;
//RowBounds是否进行count查询 - 默认不查询
protected boolean rowBoundsWithCount = false;
//当设置为true的时候,如果pagesize设置为0(或RowBounds的limit=0),就不执行分页,返回全部结果
protected boolean pageSizeZero = false;
//分页合理化
protected boolean reasonable = false;
//是否支持接口参数来传递分页参数,默认false
protected boolean supportMethodsArguments = false;
//默认count(0)
protected String countColumn = "0"; public void setProperties(Properties properties) {
//offset作为PageNum使用
String offsetAsPageNum = properties.getProperty("offsetAsPageNum");
this.offsetAsPageNum = Boolean.parseBoolean(offsetAsPageNum);
//RowBounds方式是否做count查询
String rowBoundsWithCount = properties.getProperty("rowBoundsWithCount");
this.rowBoundsWithCount = Boolean.parseBoolean(rowBoundsWithCount);
//当设置为true的时候,如果pagesize设置为0(或RowBounds的limit=0),就不执行分页
String pageSizeZero = properties.getProperty("pageSizeZero");
this.pageSizeZero = Boolean.parseBoolean(pageSizeZero);
//分页合理化,true开启,如果分页参数不合理会自动修正。默认false不启用
String reasonable = properties.getProperty("reasonable");
this.reasonable = Boolean.parseBoolean(reasonable);
//是否支持接口参数来传递分页参数,默认false
String supportMethodsArguments = properties.getProperty("supportMethodsArguments");
this.supportMethodsArguments = Boolean.parseBoolean(supportMethodsArguments);
//默认count列
String countColumn = properties.getProperty("countColumn");
if(StringUtil.isNotEmpty(countColumn)){
this.countColumn = countColumn;
}
//当offsetAsPageNum=false的时候,不能
//参数映射
PageObjectUtil.setParams(properties.getProperty("params"));
} }

1.2 添加配置后自定义的拦截器需要增加注解@Intercepts否则会报错

@Intercepts(
{
          //拦截excutor类型里面的query方法
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
}
)
public class PageInterceptor implements Interceptor {
private volatile Dialect dialect;
private String countSuffix = "_COUNT";
protected Cache<String, MappedStatement> msCountMap = null;
private String default_dialect_class = "com.github.pagehelper.PageHelper";
}
 private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
// issue #251
//查看拦截器是否有Intercepts注解 没有抛出异常
if (interceptsAnnotation == null) {
throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
}
Signature[] sigs = interceptsAnnotation.value();
Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
for (Signature sig : sigs) {
Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
try {
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;
}

1.3使用PageHelper

 public static void main(String[] args) throws IOException {

         InputStream is = Resources.getResourceAsStream("config/mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);//DefaultSqlSessionFactory SqlSession session = sqlSessionFactory.openSession();//打开session会执行 InterceptorChina.plugAll() --->Plugin.wrap()生成代理对象,被代理的就是Excutor IStudentDao sudentDaoProxy = session.getMapper(IStudentDao.class);
     //加上这句就会拦截查询方法并进行分页
Page page = PageHelper.startPage(1,1,"name");
List<Student> student = sudentDaoProxy.findStudentById(null);
}

2.第一部分是如何使用拦截器,第二部分则是拦截器如何执行的。Mybatis插件的核心接口是 org.apache.ibatis.plugin.Interceptor。

public interface Interceptor {
//拦截器执行业务逻辑方法
Object intercept(Invocation invocation) throws Throwable;
//设置拦截的真实对象
Object plugin(Object target);
//设置拦截器初始化属性
void setProperties(Properties properties);
}

2.1.XMLConfigBuilder类解析xml所有配置包括<Plugins>标签,然后放Mybatis全局配置类Configuration中

//XMLConfigBuilder类
private void pluginElement(XNode parent) throws Exception {
//如果有Plugins标签
if (parent != null) {
//拿到所有的子标签也就是<Plugin>,所有的拦截器配置
for (XNode child : parent.getChildren()) {
//拦截器的类
String interceptor = child.getStringAttribute("interceptor");
//<Property>配置
Properties properties = child.getChildrenAsProperties();
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
//把property属性设置到拦截器里面
interceptorInstance.setProperties(properties);
//加入到拦截器链中List<Interceptor> interceptors
configuration.addInterceptor(interceptorInstance);
}
}
}

2.2.解析完Configuration后生成DefaultSqlSessionFactory,打开session执行pluginAll

//DefaultSqlSessionFactory
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//选择excutor没有配置默认使用SimpleExecutor
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}

newExecutor()

//Configuration
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
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);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
} //给Executor生成代理对象
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
//PageInterceptor
public Object plugin(Object target) {
return Plugin.wrap(target, this);
} public class Plugin implements InvocationHandler {
  //生成Plugin代理对象
public static Object wrap(Object target, Interceptor interceptor) {
//拿到PageInterceptor类型的需要拦截的方法即query方法
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
//生成Plugin代理对象,所以当后面执行executor的query时其实执行下面的invoke方法
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
} @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
//执行PageInterceptor的intercept方法
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
} }

2.3.已上就是为什么配置了拦截器后会执行拦截器的调用流程,本质上就是给Executor类型生成Plugin代理对象,以后executor执行的query方法通过plugin的invoke方法执行,invoke方法会调用自定义拦截器的intercept()方法。

2.4.所以PagerHelper分页现在需要做2件事情:1.拿到executor的查询sql变成 Select count(0) from table 形式查询count值;2.通过前面的coun值计算分页边界生成select * from table limit ?,?查询结果;

3. PagerHelper中的拦截方法比较清晰

public class PageInterceptor implements Interceptor {

    @Override
public Object intercept(Invocation invocation) throws Throwable {
try {
Object[] args = invocation.getArgs();
MappedStatement ms = (MappedStatement) args[0];
Object parameter = args[1];
RowBounds rowBounds = (RowBounds) args[2];
ResultHandler resultHandler = (ResultHandler) args[3];
Executor executor = (Executor) invocation.getTarget();
CacheKey cacheKey;
BoundSql boundSql;
//由于逻辑关系,只会进入一次
if (args.length == 4) {
//4 个参数时
boundSql = ms.getBoundSql(parameter);
cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
} else {
//6 个参数时
cacheKey = (CacheKey) args[4];
boundSql = (BoundSql) args[5];
}
checkDialectExists(); List resultList;
//调用方法判断是否需要进行分页,如果不需要,直接返回结果
if (!dialect.skip(ms, parameter, rowBounds)) {
//判断是否需要进行 count 查询
if (dialect.beforeCount(ms, parameter, rowBounds)) {
//查询总数
Long count = count(executor, ms, parameter, rowBounds, resultHandler, boundSql);
//处理查询总数,返回 true 时继续分页查询,false 时直接返回
if (!dialect.afterCount(count, parameter, rowBounds)) {
//当查询总数为 0 时,直接返回空的结果
return dialect.afterPage(new ArrayList(), parameter, rowBounds);
}
}
resultList = ExecutorUtil.pageQuery(dialect, executor,
ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);
} else {
//rowBounds用参数值,不使用分页插件处理时,仍然支持默认的内存分页
resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
}
return dialect.afterPage(resultList, parameter, rowBounds);
} finally {
dialect.afterAll();
}
} }

通过ExecutorUtil.executeAutoCount()生成对应的countSql ----->经过了很多步到达CountSqlParser.sqlToCount()

public void sqlToCount(Select select, String name) {
SelectBody selectBody = select.getSelectBody();
// 是否能简化count查询
List<SelectItem> COUNT_ITEM = new ArrayList<SelectItem>();
COUNT_ITEM.add(new SelectExpressionItem(new Column("count(" + name +")")));
if (selectBody instanceof PlainSelect && isSimpleCount((PlainSelect) selectBody)) {
//selectItems 就是sql中的select xxxx from table;把xxxx替换成count(0)
((PlainSelect) selectBody).setSelectItems(COUNT_ITEM);
} else {
PlainSelect plainSelect = new PlainSelect();
SubSelect subSelect = new SubSelect();
subSelect.setSelectBody(selectBody);
subSelect.setAlias(TABLE_ALIAS);
plainSelect.setFromItem(subSelect);
plainSelect.setSelectItems(COUNT_ITEM);
select.setSelectBody(plainSelect);
}
}

3.1 ExecutorUtil.pageQuery()分页查询

public static  <E> List<E> pageQuery(Dialect dialect, Executor executor, MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler,
BoundSql boundSql, CacheKey cacheKey) throws SQLException {
//判断是否需要进行分页查询
if (dialect.beforePage(ms, parameter, rowBounds)) {
//生成分页的缓存 key
CacheKey pageKey = cacheKey;
//处理参数对象
parameter = dialect.processParameterObject(ms, parameter, boundSql, pageKey);
//调用方言获取分页 sql
String pageSql = dialect.getPageSql(ms, boundSql, parameter, rowBounds, pageKey);
BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), pageSql, boundSql.getParameterMappings(), parameter); Map<String, Object> additionalParameters = getAdditionalParameter(boundSql);
//设置动态参数
for (String key : additionalParameters.keySet()) {
pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
}
//执行分页查询
return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql);
} else {
//不执行分页的情况下,也不执行内存分页
return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql);
}
}

最新文章

  1. 在PowerShell中使用curl(Invoke-WebRequest)
  2. 用iMindMap如何提高我们绩效
  3. CSS样式基础总结
  4. xpth 字符串截取
  5. ADO.NET、NHibernate和Entity Framework的比较
  6. ubuntu方块乱码
  7. [2014.01.27]wfImage 图像处理组件 3.3
  8. ImageLoader实现图片异步加载
  9. netstat -an 提示:不是内部或外部命令
  10. Codeforces Round #342 (Div. 2) C. K-special Tables 构造
  11. thymeleaf 和其它标签组合 获取数据
  12. Java原型模式之基础
  13. MySql 如何实现不同数据库同步【2个】
  14. 【NOIP模拟】board(线段树维护二进制,树序号化为二进制)
  15. Android:触屏事件
  16. 浏览器将URL变成一个屏幕上显示的网页的过程?
  17. CodeForces Round #545 Div.2
  18. PHP源码安装后设置别名
  19. F#周报2019年第7期
  20. CNPM 安装 for angularjs

热门文章

  1. (五) Keras Adam优化器以及CNN应用于手写识别
  2. selenium-确认进入了预期页面
  3. Android Studio 添加引用Module项目
  4. DVWA 黑客攻防演练(十)反射型 XSS 攻击 Reflected Cross Site Scripting
  5. Jmeter使用JDBC请求简介
  6. Python编写的Linux邮件发送工具
  7. July 11th, 2018. Wednesday, Week 28th.
  8. HTTP对静态资源的优化
  9. 四十二、在线预览pdf文件
  10. java接口测试入门