MyBatis 拦截器执行顺序
1.原始 jdbc 工作流程
原始 jdbc 工作流程 以查询为例
1.1 加载驱动
Class.forName(Driver.class.getName())
1.2 建立数据库连接
Connection root = DriverManager.getConnection(“xx”, “xx”, “xx”)
1.3 预编译sql语句
PreparedStatement preparedStatement = root.prepareStatement(sql)
1.4 占位符参数赋值
preparedStatement.setString(1,“1”); PS:下标从1开始
1.5 执行sql
1.6 返回结果集
ResultSet resultSet = preparedStatement.executeQuery()
1.7 关闭数据库连接
xxx…close()
2.源码探究 mybatis 工作流程
通过jdbc 的工作流程可以看到大致分为:
预编译sql语句,处理参数,执行sql语句,封装结果集
同样 mybatis 工作流程大致也是这样的。但是mybatis 在初始化封装 MappedStatement 对象的时候就已经完成了预编译。
大致分为:选择执行器,处理参数,执行sql语句,封装结果集
对应工作的mybatis 四大对象分别为:
Executor ParameterHandler StatementHandler ResultSetHandler
非常相似,因为mybatis 底层就是封装的 jdbc
执行器
类图
1.选择执行器
mybatis 官网中 也有价绍,在mybatis 初始化的时候可以在配置文件的settings节点配置 defaultExecutorType 类型 ,默认的执行器为SIMPLE 还有另外两个即REUSE,BATCH。
区别:以批量插入为例
SIMPLE 每执行一次update操作,就开启一个Statement对象,用完立刻关闭Statement对象。
源码:SimpleExecutor
@Overridepublic <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {Statement stmt = null;try {Configuration configuration = ms.getConfiguration();StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);stmt = prepareStatement(handler, ms.getStatementLog());return handler.query(stmt, resultHandler);} finally {closeStatement(stmt);}}
REUSE 每执行一次update操作,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map内,供下一次使用。简言之,就是重复使用Statement对象
源码:ReuseExecutor
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {Configuration configuration = ms.getConfiguration();StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);Statement stmt = prepareStatement(handler, ms.getStatementLog());return handler.query(stmt, resultHandler);}
BATCH 每执行一次update操作,就缓存一个Statement对象,然后统一执行
源码:BatchExecutor
public <E> List<E> doQuery(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)throws SQLException {Statement stmt = null;try {flushStatements();Configuration configuration = ms.getConfiguration();StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameterObject, rowBounds, resultHandler, boundSql);Connection connection = getConnection(ms.getStatementLog());stmt = handler.prepare(connection, transaction.getTimeout());handler.parameterize(stmt);return handler.query(stmt, resultHandler);} finally {closeStatement(stmt);}}
2 实例化执行器
在通过 SqlSessionFactory 实例化 SqlSession 的时候完成了执行器的初始化
SqlSession sqlSession = sqlSessionFactory.openSession()
//默认的SqlSessionFactory public SqlSession openSession() {return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);}
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);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();}}
初始化数据库信息
Environment environment = configuration.getEnvironment();
初始化事务信息
TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
主要的一行
final Executor executor = configuration.newExecutor(tx, execType);
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) interceptorChain.pluginAll(executor);return executor;}
3 通过执行器选择执行方法
当我们进行查询的时候,会通过命名空间+方法名 封装成一个 id ,去MappedStatement 集合里面获取到当前的 MappedStatement 对象,并开始通过执行器开始执行查询方法,源码:DefaultSqlSession
public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {try {MappedStatement ms = configuration.getMappedStatement(statement);executor.query(ms, wrapCollection(parameter), rowBounds, handler);} catch (Exception e) {throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);} finally {ErrorContext.instance().reset();}}
3.通过mybatis 工作流程 窥探拦截器执行顺序
调用拦截器
executor.query(ms, wrapCollection(parameter), rowBounds, handler);
这一行是通过执行器的代理对象 去执行query的方法。
官网提供的拦截器插件文档
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
如果命中到拦截器就会执行拦截器的拦截方法,如果有条件限制 要么放行,要么执行拦截逻辑,代理对象是如何生成的呢?
创建代理对象
1 创建 Executor 代理对象
最终会进入到两个分支BaseExecutor 和 CachingExecutor
BaseExecutor 有三个实现类
CachingExecutor 内部维护了一个 Executor接口,在执行query方法的时候会再次进入 BaseExecutor 的 query 方法里面
BaseExecutor#query(MappedStatement, Object, RowBounds, ResultHandler, CacheKey, BoundSql)
这个方法里面有一个去查询的方法
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
到这个方法就是上面提到的不同执行器的 doQuery 方法
以SimpleExecutor 的 doQuery 方法为例
@Overridepublic <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {Statement stmt = null;try {Configuration configuration = ms.getConfiguration();StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);stmt = prepareStatement(handler, ms.getStatementLog());return handler.query(stmt, resultHandler);} finally {closeStatement(stmt);}}
而在我们实例化执行器的时候 ,提到过这样一行代码
final Executor executor = configuration.newExecutor(tx, execType);
在这个newExecutor 里面去实例化了一个 执行器,并且
executor = (Executor) interceptorChain.pluginAll(executor);
会生成 Executor 的代理对象(在初始化的mybatis 环境的时候就已经将自定义的拦截器全部添加到一个内部维护的集合里面去了)
2 创建 StatementHandler 代理对象
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);return statementHandler;}
先创建 一种 RoutingStatementHandler 的实例
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {switch (ms.getStatementType()) {case STATEMENT:delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);break;case PREPARED:delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);break;case CALLABLE:delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);break;default:throw new ExecutorException("Unknown statement type: " + ms.getStatementType());}}
3 创建 ParameterHandler 代理对象
三个分支里面全部都有这样的一行代码
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);return parameterHandler;}
4 创建 ResultSetHandler 代理对象
三个分支里面全部都有这样的一行代码
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,ResultHandler resultHandler, BoundSql boundSql) {ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);return resultSetHandler;}
调用拦截器
官网提供的拦截器插件文档
StatementHandler (prepare, parameterize, batch, update, query)
以SimpleExecutor 的 doQuery 方法为例
@Overridepublic <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {Statement stmt = null;try {Configuration configuration = ms.getConfiguration();StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);stmt = prepareStatement(handler, ms.getStatementLog());return handler.query(stmt, resultHandler);} finally {closeStatement(stmt);}}
stmt = prepareStatement(handler, ms.getStatementLog());
RoutingStatementHandler
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {return delegate.prepare(connection, transactionTimeout);}
再调用 BaseStatementHandler 的 prepare 方法
再回来 执行拦截器拦截的StatementHandler 的 prepare 方法,要么放行,要么执行拦截逻辑。再回来执行代码的下一行
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {Statement stmt;Connection connection = getConnection(statementLog);stmt = handler.prepare(connection, transaction.getTimeout());handler.parameterize(stmt);return stmt;}
即 StatementHandler handler.parameterize(stmt);又会拦截
调用拦截器
即 StatementHandler handler.parameterize(stmt);
一共四种策略
CallableStatementHandler #parameterize
public void parameterize(Statement statement) throws SQLException {registerOutputParameters((CallableStatement) statement);parameterHandler.setParameters((CallableStatement) statement);}
官网提供的拦截器插件文档
ParameterHandler (getParameterObject, setParameters)
执行 CallableStatementHandler 类型的 setParameters 方法的拦截器
PreparedStatementHandler #parameterize
@Overridepublic void parameterize(Statement statement) throws SQLException {parameterHandler.setParameters((PreparedStatement) statement);}
执行 PreparedStatementHandler 类型的 setParameters 方法的拦截器
RoutingStatementHandler #parameterize
@Overridepublic void parameterize(Statement statement) throws SQLException {delegate.parameterize(statement);}
通过路由到某一种StatementHandler 类型的 setParameters 方法的拦截器
SimpleStatementHandler #parameterize
@Overridepublic void parameterize(Statement statement) {// N/A}
此类型的 setParameters 无拦截器工作
调用拦截器
回到上面的 doQuery 方法的最后一行
return handler.query(stmt, resultHandler);
官网提供的拦截器插件文档
StatementHandler (prepare, parameterize, batch, update, query)
ResultSetHandler (handleResultSets, handleOutputParameters)
调用拦截器
CallableStatementHandler #query
@Overridepublic <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {CallableStatement cs = (CallableStatement) statement;cs.execute();List<E> resultList = resultSetHandler.handleResultSets(cs);resultSetHandler.handleOutputParameters(cs);return resultList;}
调用拦截器
List resultList = resultSetHandler.handleResultSets(cs);
调用拦截器
resultSetHandler.handleOutputParameters(cs);
public void handleOutputParameters(CallableStatement cs) throws SQLException {final Object parameterObject = parameterHandler.getParameterObject();//省略......}
调用拦截器
ParameterHandler # getParameterObject
另外两种
PreparedStatementHandler
SimpleStatementHandler
只有这一行调用了一次拦截器
List resultList = resultSetHandler.handleResultSets(cs);
而RoutingStatementHandler 是从新路由到上面三种的其中一种。
这样一整个doQuery 方法的拦截器调用完成
图1:SqlSessionFactory 到 doQuery
图2:doQuery - close
所以一个正常的查询被拦截器拦截的顺序应为:
Executor -> query
StatementHandler -> prepare
StatementHandler -> parameterize
ParameterHandler -> setParameters
StatementHandler -> query
ResultSetHandler -> handleResultSets
ResultSetHandler -> handleOutputParameters
ParameterHandler -> getParameterObject
MyBatis 拦截器执行顺序相关推荐
- 拦截器原理多个拦截器执行顺序
拦截器原理多个拦截器执行顺序 1.根据当前请求,找到**HandlerExecutionChain[可以处理请求的handler以及handler的所有 拦截器] 2.先来顺序执行 所有拦截器的 pr ...
- java 拦截器顺序_Springmvc拦截器执行顺序及各方法作用详解
实现HandlerInterceptor接口或者继承HandlerInterceptor的子类,比如Spring 已经提供的实现了HandlerInterceptor 接口的抽象类HandlerInt ...
- Mybatis 拦截器执行原理分析
目录 1.拦截器执行流程 2.拦截器实现原理 3.拦截器用法 1.mybatis拦截器实现原理 2.拦截器实现原理 在Mybaits中 拦截器需实现Interceptor接口,加上如下注解 @Inte ...
- 犯罪心理解读Mybatis拦截器
原文链接:"犯罪心理"解读Mybatis拦截器 Mybatis拦截器执行过程解析 文章写过之后,我觉得 "Mybatis 拦截器案件"背后一定还隐藏着某种设计动 ...
- 关于Mybatis拦截器的使用
关于Mybatis拦截器的使用 1 Mybatis拦截器的使用 1 自定义拦截器 1 Interceptor接口 2 @Intercepts注解 3 @Signature注解 2 注册拦截器 3 拦截 ...
- 面试官:你能说说MyBatis拦截器原理吗?
点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 作者:Format cnblogs.com/fangjian042 ...
- MyBatis拦截器原理探究MyBatis拦截器原理探究
MyBatis拦截器介绍 MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能.那么拦截器拦截MyBatis中的哪些内容呢? 我们进入官网看一看: MyBatis拦截 ...
- MyBatis拦截器有哪些以及分析
MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能.那么拦截器拦截MyBatis中的哪些内容呢? 我们进入官网看一看: MyBatis 允许你在已映射语句执行过程中 ...
- MyBatis拦截器原理探究
MyBatis拦截器介绍 MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能.那么拦截器拦截MyBatis中的哪些内容呢? 我们进入官网看一看: MyBatis 允 ...
- insert into select 主键自增_springboot2结合mybatis拦截器实现主键自动生成
点击上方蓝字关注我们 1 01 前言 前阵子和朋友聊天,他说他们项目有个需求,要实现主键自动生成,不想每次新增的时候,都手动设置主键.于是我就问他,那你们数据库表设置主键自动递增不就得了.他的回答是他 ...
最新文章
- html5边框有圆弧,CSS圆角有立体感的DIV边框
- Linux学习(十四)---大数据定制篇Shell编程
- 在.net开发中使用Log4Net组件
- Redis简介、与memcached比较、存储方式、应用场景、生产经验教训、安全设置、key的建议、安装和常用数据类型介绍、ServiceStack.Redis使用(1)...
- Java基础之写文件——缓冲区中的多条记录(PrimesToFile3)
- wedo2.0编程模块介绍_能量黑科技模块系列十:RFID魔块
- python3.6和3.7的区别_python3.6和3.7有什么区别
- java中catalina.out_catalina.out 和 catalina.log 的区别和用途
- 第十一篇:稳定性之面向失败设计【过载保护】
- AsyncTask 异步任务基本使用-下载视频
- g++: internal compiler error: Killed (program cc1plus)Please submit a full bug report,内存不足问题解决
- 坐地起价?三星首款折叠屏手机 1.3 万起!
- paip.PHP-asp—jsp实现事件机制 WEBFORM式开发
- odoo 中实现多列搜索
- html空格字符转义存入数据库,HTML中多种空格转义字符
- android 金山电池医生,金山电池医生3.0(android版).PDF
- 信息系统项目管理师考试怎么复习最有效?
- 芝加哥打字机_芝加哥打字机不是打字机
- 2021年学web前端需要什么学历?
- 异常:Fatal error loading the DB: Invalid argument. Exiting
热门文章
- 超期天数计算机函数公式大全,Excel计算天数的函数与公式总结
- 微信支付(1)---功能测试点
- 字典写入excel_Excel中“先出式”出货的问题,以后出库太方便了
- selenium自动化测试之鼠标模拟操作
- 像Selenium爬网页一样爬手机App,可见即可爬——appium 教程(一)appium安装windows版
- 【教学类-10-02】20221025《空心图案4*2-不重复》( 随机图案拼贴)(大班主题《动物花花衣》)
- JMeter详细使用教程及实际案例
- BI工具:cboard\superset 比较
- 《计算传播学导论》读书笔记:第五章 网络传播与传播网络
- 图计算:社区发现算法