mybatis插件的执行顺序
原理
mybatis插件类型
plugins
MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
那么 这些插件的执行顺序是如何的呢?
引入mybatis插件
maven依赖(略)
详见: gitee
配置插件
<!-- 配置SqlSessionFactory对象 --><bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><!-- ..... --><property name="configLocation" value="classpath:mybatis-config.xml"/></bean>
自定义插件
插件的方法内容很简单,即打印当前方法的签名:
@Intercepts({@Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, RowBounds.class,ResultHandler.class })
})
@Slf4j
public class Executor_Interceptor implements Interceptor {@Overridepublic Object intercept(Invocation invocation) throws Throwable {log.info("---------------------"+this.getClass().getSimpleName() +"::"+invocation.getMethod().getName());return invocation.proceed();}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {}
}
同理,以此注入如下的插件:
// Executor2_Interceptor ,Executor3_Interceptor 同 Executor_Interceptor//ParameterHandler 监听setParameters方法
@Intercepts({@Signature( type= ParameterHandler.class,method = "setParameters", args = {PreparedStatement.class})
})
public class ParameterHandler_Interceptor implements Interceptor {}//ResultSetHandler 监听 handleResultSets 方法
@Intercepts({@Signature( type= ResultSetHandler.class,method = "handleResultSets", args = {Statement.class}),
})
public class ResultSetHandler_Interceptor implements Interceptor {}//StatementHandler 监听 prepare和query方法
@Intercepts({@Signature( type= StatementHandler.class,method = "prepare", args = {Connection.class,Integer.class}),@Signature( type= StatementHandler.class,method = "query", args = {Statement.class,ResultHandler.class})
})
public class StatementHandler_Interceptor implements Interceptor {}
同类型插件的执行顺序
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><plugins><plugin interceptor="cn.jhs.framework.mybatis.plugins.Executor2_Interceptor"></plugin><plugin interceptor="cn.jhs.framework.mybatis.plugins.Executor_Interceptor"></plugin><plugin interceptor="cn.jhs.framework.mybatis.plugins.Executor3_Interceptor"></plugin></plugin></plugins>
</configuration>
执行查询方法–日志
结论
相同类型的plugins:按照 注册的
逆序,执行。
不同类型插件的执行顺序
mybatis-config.xml
<plugins><plugin interceptor="cn.jhs.framework.mybatis.plugins.Executor_Interceptor"></plugin><plugin interceptor="cn.jhs.framework.mybatis.plugins.ParameterHandler_Interceptor"></plugin><plugin interceptor="cn.jhs.framework.mybatis.plugins.ResultSetHandler_Interceptor"></plugin><plugin interceptor="cn.jhs.framework.mybatis.plugins.StatementHandler_Interceptor"></plugin>
</plugins>
执行查询方法–日志
猜测
不同类型的plugins:按照 是否注册的顺序
影响呢?
随机顺序-不同plugin
<plugins><plugin interceptor="cn.jhs.framework.mybatis.plugins.ResultSetHandler_Interceptor"></plugin><plugin interceptor="cn.jhs.framework.mybatis.plugins.ParameterHandler_Interceptor"></plugin><plugin interceptor="cn.jhs.framework.mybatis.plugins.StatementHandler_Interceptor"></plugin><plugin interceptor="cn.jhs.framework.mybatis.plugins.Executor_Interceptor"></plugin></plugins>
此时的执行结果,跟上述结果相同
结论
总体顺序
Executor
-> StatementHandler
->ParameterHandler
-> ResultSetHandler
,
但是部分-方法的顺序,不受上述规则的控制。如日志中的StatementHandler.prepare()和StatementHandler.query()
源码分析
我们都知道,XXMapper#selectXXX()
; 转换为 DefaultSqlSession#selectList()
; 下面从DefaultSqlSession入手,分析,上述插件的执行顺序的原理。
构建DefaultSqlSession — 初始化
--DefaultSqlSessionFactory#openSessionFromDataSource()
----configuration#newExecutor(); //1. 创建 executor
------executor = new SimpleExecutor(); //1.1 创建SimpleExecutor
------executor = new CachingExecutor(executor); //1.2 包装成: CachingExecutor
------executor = (Executor) interceptorChain.pluginAll(executor); //1.3 使用 interceptorChain.代理executor
----------for-each -> interceptor.plugin(target); //1.3.1 内部调用: Plugin.wrap(target, this);
----------Plugin#wrap(target, this); //1.3.1.1 生成executor代理对象public static Object wrap(Object target, Interceptor interceptor) {//a. 获取代理interceptor所有签名的<接口,Set<接口methods>>Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);//b. target = CachingExecutor;Class<?> type = target.getClass();//c. type(CachingExecutor.class)与interceptor签名接口 交集; // 此时CachingExecutor实现了Executor接口, 故只有@Signature中包含Executor接口的interceptor,会生成代理对象;Class<?>[] interfaces = getAllInterfaces(type, signatureMap);//d. 如果存在交集, 则生成代理对象if (interfaces.length > 0) {return Proxy.newProxyInstance(type.getClassLoader(),interfaces,new Plugin(target, interceptor, signatureMap));}//e. 如果不存在交集,则返回原始对象return target;}
------返回一个可能存在多重Plugin..warp的(CachingExecutor(SimpleExecutor)) 对象;
-----new DefaultSqlSession(configuration, executor, autoCommit); // 2. 构造DefaultSqlSession对象
此时构建出一个DefaultSqlSession对象,它的内容如下:
public class DefaultSqlSession implements SqlSession {private Configuration configuration;private Executor executor; // 仅包含 plugins中,@Signature中包含Executor接口的interceptorpublic <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {MappedStatement ms = configuration.getMappedStatement(statement);return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);}
}
执行查询
接上一节内容,所有的查询都会转换为executor.query
, 此时的executor为@Signature中包含Executor接口的interceptor生成的Plugin代理对象
。
1. 执行Plugin<Executor.class>
Plugin执行代理方法的逻辑如下。 此时的Plugin代理的是Executor
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Set<Method> methods = signatureMap.get(method.getDeclaringClass()); //1.1 查看当前Plugin支持的方法类型setif (methods != null && methods.contains(method)) { //1.2 检验执行mapper方法类型,与当前Plugin设置的方法 是否一致return interceptor.intercept(new Invocation(target, method, args)); //1.3. 链式调用*执行插件interceptor.intercept方法}return method.invoke(target, args); //1.4 执行原始对象 CachingExecutor();}
2. 执行CachingExecutor#query()
上一步执行完成之后,最终执行真实对象查询方法CachingExecutor#query
.
-------delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); //delegate = SimpleExecutor //SimpleExecutor extends BaseExecutor//delegate.query() 执行 BaseExecutor#query(), 而BaseExecutor内部调用抽象方法doQuery,由 SimpleExecutor实现
---------SimpleExecutor#doQuery(); // 2.1
-----------StatementHandler handler = configuration.newStatementHandler() // 2.1.1
-------------StatementHandler statementHandler = new RoutingStatementHandler(); //2.1.1.1 构造 RoutingStatementHandler
---------------delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); //2.1.1.1.1 内部delegate//delegate 构造内容public class PreparedStatementHandler extends BaseStatementHandler {protected BaseStatementHandler() { this.parameterHandler = configuration.newParameterHandler(); //a. 内部调用创建 ParameterHandlerthis.resultSetHandler = configuration.newResultSetHandler(); //b. 内部调用创建 ResultSetHandler}}
-------------statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); //2.1.1.2 最终通过Plugin.warp(=RoutingStatementHandler);它的delegate类型为PreparedStatementHandler
-----------Statement stmt = prepareStatement(handler, ms.getStatementLog());// 2.1.2
-------------Connection connection = getConnection(statementLog); // 2.1.2.1 获取数据库连接
-------------stmt = handler.prepare(connection, transaction.getTimeout()); // 2.1.2.2 PreparedStatementHandler->BaseStatementHandler#prepare ;
-------------handler.parameterize(stmt); // 2.1.2.3 PreparedStatementHandler#parameterize ; -----------return handler.<E>query(stmt, resultHandler);// 2.1.3
------------PreparedStatementHandler#query()public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {PreparedStatement ps = (PreparedStatement) statement;ps.execute(); //2.1.3.1 执行psreturn resultSetHandler.<E> handleResultSets(ps); //2.1.3.2 resultSetHandler 渲染执行结果}
关键类图
执行顺序
handler.prepare
: StatementHandlerhandler.parameterize
: StatementHandler + ParameterHandlerhandler.<E>query
: StatementHandler + ParameterHandlerresultSetHandler.<E> handleResultSets(ps)
: ResultSetHandler
mybatis插件的执行顺序相关推荐
- 后端技术:mybatis插件原理详解
关注"Java后端技术全栈" 回复"面试"获取全套面试资料 上次发文说到了如何集成分页插件MyBatis插件原理分析,看完感觉自己better了,今天我们接着来 ...
- 框架源码专题:Mybatis启动和执行流程、源码级解析
文章目录 1. Mybatis 启动流程 步骤一: 把xml配置文件解析成Configuration类 步骤二: 创建SqlSession会话 mybatis的三种执行器 步骤三: 在sqlSessi ...
- SpringCloud或SpringBoot+Mybatis-Plus利用mybatis插件实现数据操作记录及更新对比
引文 本文主要介绍如何使用mybatis插件实现拦截数据库操作并根据不同需求进行数据对比分析,主要适用于系统中需要对数据操作进行记录.在更新数据时准确记录更新字段 核心:mybatis插件(拦截器). ...
- WordPress工作原理之程序文件执行顺序
在了解WordPress挂载机制时,一直有一个疑惑,到底是WordPress的内核源文件先执行还是主题文件里functions.php文件先执行.为了解决这个问题,想了解WordPress的工作原理, ...
- c++map的使用_mybatis源码 | mybatis插件及动态代理的使用
学习背景 最近公司在做一些数据库安全方面的事情,如数据库中不能存手机号明文,不能存身份证号明文, 但是项目已经进行了好几个月了, 这时候在应用层面去改显然不太现实, 所以就有了Mybatis的自定义插 ...
- 写过Mybatis插件?那说说自定义插件是如何加载的吧?
大多数框架,都支持插件,用户可通过编写插件来自行扩展功能,Mybatis也不例外. 我们从插件配置.插件编写.插件运行原理.插件注册与执行拦截的时机.初始化插件.分页插件的原理等六个方面展开阐述. 1 ...
- TestNG测试框架之测试用例的执行顺序分析
既然是讨论执行顺序问题,那么用例肯定是批量执行的,批量执行的方法有mvn test.直接运行testng.xml文件,其中直接运行testng.xml文件的效果与pom文件中配置执行testng.xm ...
- 美团一面:你既然写过Mybatis插件,能给我说说它底层是怎么加载一个自定义插件的吗?...
点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达今日推荐:2020年7月程序员工资统计,平均14357元,又跌了,扎心个人原创100W+访问量博客:点击前往,查看更多 大多 ...
- MyBatis(四)MyBatis插件原理
MyBatis插件原理 MyBatis对开发者非常友好,它通过提供插件机制,让我们可以根据自己的需要去增强MyBatis的功能.其底层是使用了代理模式+责任链模式 MyBatis官方https://m ...
- Pytest-ordering自定义用例执行顺序
我们一般在做自动化测试时,用例设计之间应该是可以相互独立执行的,没有一定的前后依赖关系的,如果我们真的有前后依赖,想指定用例的先后顺序,可以用到pytest-ordering插件解决这个问题 1.安装 ...
最新文章
- MySQL数字辅助表
- 一次生产的 JVM 优化案例
- linux samba代码,Linux下Samba服务器源码安装及配置
- 华三云:不做开源的投机者
- openwrt使用linux内核版本,降低OpenWRT的Linux内核版本
- html字符串替换src,替换html字符串中img标签src的值.
- 目标跟踪算法的分类(二)
- 数字化医院网络终端安全管理
- 推荐一款超级好用的开源项目画图工具
- 修改程序版本工具(ResHacker)使用说明20140902
- 从零开始内网渗透学习
- python提取XML信息保存为txt
- docker提交比赛记录
- 关于opencv fitLine直线拟合得斜率及截距
- HttpClient4.5.6设置代理以及代理验证(用户名和密码)
- [NAS] Synology (群晖) DSM 7.X 挂载NTFS硬盘
- 服务器如何安装windows7系统,安装Windows 7系统
- 优化嵌入式软件的几点技巧
- pandas交叉表与透视表pd.crosstab()和pd.pivot_table()函数详解
- 使用idea打包war包
热门文章
- 基于java水果网店管理系统计算机毕业设计源码+系统+lw文档+mysql数据库+调试部署
- Windows11彻底卸载Edge
- 【libmodbus-vs2019】测试使用
- 多种企业常用网管软件介绍及配置说明(带视频)
- python下载pandas库_Python中的pandas库
- python ev3图形化编程软件下载_【stm32图形化编程软件免费版下载】stm32图形化编程软件 v1.0 最新免费版-开心电玩...
- 个人设想中的TCAX GUI生成的带python脚本代码的ASS字幕文件
- TensorFlow实现图像风格迁移
- IE浏览器GET请求中文乱码
- 中国家庭追踪调查(CFPS)数据及问卷(2010-2018年)