Mybatis插件机制以及PageHelper插件的原理
首先现在已经有很多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);
}
}
Mybatis插件机制以及PageHelper插件的原理的更多相关文章
- mybatis插件机制及分页插件原理
MyBatis 插件原理与自定义插件: MyBatis 通过提供插件机制,让我们可以根据自己的需要去增强MyBatis 的功能.需要注意的是,如果没有完全理解MyBatis 的运行原理和插件的工作方式 ...
- mybatis(六)插件机制及分页插件原理
转载:https://www.cnblogs.com/wuzhenzhao/p/11120848.html MyBatis 通过提供插件机制,让我们可以根据自己的需要去增强MyBatis 的功能.需要 ...
- WordPress 插件机制的简单用法和原理(Hook 钩子)
WordPress 的插件机制实际上只的就是这个 Hook 了,它中文被翻译成钩子,允许你参与 WordPress 核心的运行,是一个非常棒的东西,下面我们来详细了解一下它. PS:本文只是简单的总结 ...
- mybatis插件机制原理
mybatis插件机制及分页插件原理 参考链接:mybatis插件机制及分页插件原理 如何编写一个自定义mybatis插件 参考链接:mybatis 自定义插件的使用
- 传统方式和插件方式 分别实现 分页 功能 pageHelper 插件
实现分页 这里提供两种方式 一种是传统的分页方式 一种是基于pageHelper插件 实现的分类 推荐使用后者 前者是一般开发的方式 思路 先手动创建一个 pageUtil 工具 ...
- CLI子命令扩展-插件机制实现
开发CLI工具过程中,为了便于扩展,将CLI的实现分为基础功能和扩展功能.基础功能包括init.build.lint.publish等伴随工程从初始化到最终发布到生产环境,也即为CLI 的core.扩 ...
- 跟着辛星用PHP的反射机制来实现插件
我的博文的前一篇解说了PHP的反射机制是怎么回事,假设读者还不清楚反射机制,能够搜索下或者看我的博文,都是不错的选择.我们開始解说一下怎么用PHP来实现插件机制.所谓插件机制.就是我们定义一个接口.即 ...
- Mybatis插件扩展以及与Spring整合原理
@ 目录 前言 正文 插件扩展 1. Interceptor核心实现原理 2. Mybatis的拦截增强 Mybatis与Spring整合原理 1. SqlSessionFactory的创建 2. 扫 ...
- 精尽MyBatis源码分析 - 插件机制
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
随机推荐
- Webpack4教程 - 第二部分,使用loader处理scss,图片以及转换JS
转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 原文出处:https://wanago.io/2018/07/16/webpack-4-course-par ...
- git错误--ssh: Could not resolve hostname ssh.github.com: Name or service not known--解决方式
错误如下: git push origin ssh: Could not resolve hostname ssh.github.com: Name or service not known fata ...
- 搭建 structs2 环境
前言 环境: window 10 ,JDK 1.8 ,Tomcat 7 ,MyEclipse 2014 pro 搭建 SSH 环境的步骤 创建 JavaWeb 项目 导入 structs2 的jar包 ...
- ubuntu18.04修改网卡名称为eth0
1.修改grub文件 vim /etc/default/grub 查找 GRUB_CMDLINE_LINUX="" 修改为 GRUB_CMDLINE_LINUX="net ...
- Docker容器镜像删除
好吧,本来认为删除镜像是一件很容易的事情,但刚开始上手,还是有点百思不得其解.删着删着,发现果然很容易.分享下本人的心得: 分两种情况:那么要删除镜像,首先得删除容器,删除容器时,确保容器已停止运行: ...
- lombok使用
下载地址 链接:https://pan.baidu.com/s/19Rz7sgasVv5Gc7vw1A4whA 提取码:6bgg lombok的安装: 使用lombox是需要安装的,如果不安装,IDE ...
- [POI2015]PUS
嘟嘟嘟 这题只要往正确的方面想,就很简单. 首先,这是一道图论题! 想到这,这题就简单了.对于两个数\(i\)和\(j\),如果\(i\)比\(j\)大,就从\(i\)向\(j\)连边.然后如果图中存 ...
- MYSQL的information_schema数据库中你可以得到的信息!!!
1.COLUMNS 记录了所有表字段的一些基本信息,例如权限信息等. 2:TABLES :使用该表可以查询每一个表的详细信息,例如数据占用空间大小.索引大小以及表的更新时间以及表的行数等 3:视图 可 ...
- 性能测试中TPS上不去的几种原因浅析
转:https://www.cnblogs.com/imyalost/p/8309468.html 下面就说说压测中为什么TPS上不去的原因: 1.网络带宽 在压力测试中,有时候要模拟大量的用户请求, ...
- 随心测试_数据库_001<论数据的重要性>
测试工作中,数据的重要性 软测工程师:作为综合运用多学科知识,保障软件质量的重要岗位.需要我们学以致用,在工作中不断学习提升.以下:软测人员必备_数据库核心技能学习点,供大家学习参考. Q1:什么是: ...