写在前面

  通过上一偏文章,我们知道:mybatis 的插件开发,主要是集中在Executor(执行器),ParameterHandler(参数处理器),ResultSetHandler(结果集处理器),StatementHandler( 语句处理器)。可以称作myBatis核心的四大金刚

  我们知道了mybatis对外暴露的API(SqlSession)的操作,其实是其持有Executor对于底层的操作。也知道了Executor 的设计采用了模板方法模式。

  查看Executor 的源代码发现,它对底层的操作,其实是其持有StatmentHandler对于 底层的操作。

public class SimpleExecutor extends BaseExecutor {
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
    //更新方法,首先获取StatmentHandler
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
stmt = prepareStatement(handler, ms.getStatementLog());
//对于update 方法,只会有parameterHandler 帮忙绑定预编译的sql的参数。①
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
    //查询方法,首先获取StatmentHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
//对于query方法,parameterHandler 和 resultSetHandler 会前来帮绑定参数和结果集处理 ②
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
//statmentHandler 预编译sql
stmt = handler.prepare(connection);
//statmentHandler 绑定参数
handler.parameterize(stmt);
return stmt;
}
}

  首先说明本文重点:

  文中将的四大对象是指:executor, statementHandler,parameterHandler,resultSetHandler对象

  讲到statementHandler,毫无疑问它是我们四大对象最重要的一个,它的任务就是和数据库对话。在它这里会使用parameterHandler和ResultSetHandler对象为我们绑定SQL参数和组装最后的结果返回。

StatmentHandler 接口

  statmentHandler 位于mybatis的org.apache.ibatis.executor.statement 包下面。也可以发现其和executor的关系

public interface StatementHandler {
//预编译
Statement prepare(Connection connection) throws SQLException;
//绑定参数
void parameterize(Statement statement) throws SQLException;
//批量操作
void batch(Statement statement) throws SQLException;
//更新操作
int update(Statement statement) throws SQLException;
//查询操作
<E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException; BoundSql getBoundSql();
ParameterHandler getParameterHandler();
}

通过对stantmentHandler 接口的结构分析。它和executor的设计几乎一模一样,我可以猜测statmentHandler 也是采用了模板方法模式的设计。

StatmentHandler 的产生

  通过对于Executor 的分析,可以发现executor 产生了一个RoutingStatmentHandler (从名字中可以发现,这是一个路由)

  这里有几个重要的方法,prepare,parameterize和query,update,他们的作用是不一样的。

  在MyBatis实现了statementHandler的有四个类:

  RoutingStatementHandler,这是一个封装类,它不提供具体的实现,只是根据Executor的类型,创建不同的类型StatementHandler。

  SimpleStatementHandler,这个类对应于JDBC的Statement对象,用于没有预编译参数的SQL的运行。

  PreparedStatementHandler 这个用于预编译参数SQL的运行。

  CallableStatementHandler 它将实存储过程的调度。

  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
//statmentHandler 的产生
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}

  根据不同的statatmentType 产生具体的statmentHandler实例

  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());
}
}

  在MyBatis中,Configuration对象会采用new RoutingStatementHandler()来生成StatementHandler对象,换句话说我们真正使用的是RoutingStatementHandler对象,然后它会根据Executor的类型去创建对应具体的statementHandler对象(SimpleStatementHandler,PreparedStatementHandler和CallableStatementHandler)。

  然后利用具体statementHandler的方法完成所需要的功能。那么这个具体的statementHandler是保存在RoutingStatementHandler对象的delegate属性的,所以当我们拦截statementHandler的时候就要常常访问它了。

statmentHandler 的执行

  prepare方法:首先prepare方法是用来编译SQL的,让我们看看它的源码实现。这里我们看到了BaseStatementHandler对prepare方法的实现,

  parameterize方法:上面我们在prepare方法里面预编译了SQL。那么我们这个时候希望设置参数。在Statement中我们是使用parameterize方法进行设置参数的。

  让我们看看PreparedStatementHandler中的parameterize方法:

  @Override
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}

  很显然这里很简单是通过parameterHandler来实现的,我们这篇文章只是停留在statementhandler的程度,等我们讲解parameterHandler的时候再来看它如何实现吧,期待一下吧。

  也就是不管是执行,query和update。parameterHandler 都会前来帮忙,帮助参数的绑定。参见①

对于执行query方法。ResultSetHandler才会前来帮忙,对结果集做处理。参见②

  query/update方法

  我们用了prepare方法预编译了SQL,用了parameterize方法设置参数,那么我们接下来肯定是想执行SQL,而SQL无非是两种:
    一种是进行查询——query,另外就是更新——update。这些方法都很简单,让我们看看PreparedStatementHandler的实现:

  @Override
public int update(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
int rows = ps.getUpdateCount();
Object parameterObject = boundSql.getParameterObject();
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
return rows;
} @Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.<E> handleResultSets(ps);
}

  我们可以看到如果是进行update的,它将会执行生成主键的操作(插入数据要自动生成主键的时候),然后就返回影响行数。
  如果是进行query的就更加简单了,它就是执行SQL语句,然后讲结果使用resultSetHandler去完成我们的结果组装。

    

parameterhandler 和 ResultSetHandler

  parameterhandler 和 ResultSetHandler好比StatmentHandler 的左膀右臂。先后完成了参数绑定设置,已经结果集设置。他们的默认实现了是DefaultParameterHandler和DefaultResultSetHandler

  在构造statmentHandler 实例的时候,会同时产生parameterhandler 和 ResultSetHandler实例。并持有他们的对象。

public abstract class BaseStatementHandler implements StatementHandler {

  protected final Configuration configuration;
protected final ObjectFactory objectFactory;
protected final TypeHandlerRegistry typeHandlerRegistry;
protected final ResultSetHandler resultSetHandler;
protected final ParameterHandler parameterHandler; protected final Executor executor;
protected final MappedStatement mappedStatement;
protected final RowBounds rowBounds; protected BoundSql boundSql; protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
this.configuration = mappedStatement.getConfiguration();
this.executor = executor;
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds; this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.objectFactory = configuration.getObjectFactory(); if (boundSql == null) { // issue #435, get the key before calculating the statement
generateKeys(parameterObject);
boundSql = mappedStatement.getBoundSql(parameterObject);
} this.boundSql = boundSql; this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}
}

ParameterHandler 的作用  

  我们知道ParameterHandler是用来设置参数规则的。档StatementHandler使用prepare()方法后,接下来就是使用它来设置参数,让我们看看它的定义:

public interface ParameterHandler {
Object getParameterObject();
void setParameters(PreparedStatement ps) throws SQLException;
}

  十分简单getParameterObject()是获取参数的,而setParameters()是设置参数的,相当于对一条sql所有的参数都执行ps.setXXX(value);

  让我们看看它的实现类:DefaultParameterHandler中的方法实现

  @Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
} catch (SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}

这里重点是setParameters(),首先它读取了ParameterObject参数对象,然后用typeHandler对参数进行设置,而typeHandler里面需要对jdbcType和javaType进行处理,然后就设置参数了。也很好理解。所以当我们使用TypeHandler的时候完全可以控制如何设置SQL参数。

ResultSetHandler

  ResultSetHandler要比ParameterHandler复杂得多,对其原理要讲清楚真的很困难,但是我们一般在插件里面使用不多。所以我就不分析太详细了,大致讲讲它的大概原理。让我们看看ResultSetHandler的接口定义:

public interface ResultSetHandler {
<E> List<E> handleResultSets(Statement stmt) throws SQLException;
void handleOutputParameters(CallableStatement cs) throws SQLException; }

总结

  通过上面几篇我们懂得了SqlSession下得【四大对象】Executor,StatementHandler,ParameterHandler 和 ResultSetHandler之间的主要方法和协作。

  因为掌握好四大对象的基本方法,了解他们的作用是我们插件编写的根本,没有掌握它们你是没有办法准确的去编写你的插件的。

  StatementHandler是MyBatis四大对象里面最重要的对象,它的方法是十分重要的,也是我们插件的基础。

  当我们需要改变sql的时候,显然我们要在预编译SQL(prepare方法前加入修改的逻辑)。
  当我们需要修改参数的时候我们可以在调用parameterize方法前修改逻辑。或者使用ParameterHandler来改造设置参数。
  我们需要控制组装结果集的时候,也可以在query方法前后加入逻辑,或者使用ResultSetHandler来改造组装结果。
  懂的这些方法,才能理解我需要拦截什么对象,如何处理插件,这是MyBatis的核心内容。

mybatis源码分析(6)-----核心调度对象StatmentHandler的更多相关文章

  1. MyBatis源码分析之核心处理层

    目录 1 传统方式源码剖析 1.1 初始化流程 1.2 执行SQL流程 1.2.1 获取SqlSession 1.2.2 执行SqlSession接口 1.2.3 执行Executor接口 1.2.4 ...

  2. MyBatis源码分析-SQL语句执行的完整流程

    MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ...

  3. Mybatis源码分析

    MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ...

  4. MyBatis源码分析(4)—— Cache构建以及应用

    @(MyBatis)[Cache] MyBatis源码分析--Cache构建以及应用 SqlSession使用缓存流程 如果开启了二级缓存,而Executor会使用CachingExecutor来装饰 ...

  5. MyBatis 源码分析 - 内置数据源

    1.简介 本篇文章将向大家介绍 MyBatis 内置数据源的实现逻辑.搞懂这些数据源的实现,可使大家对数据源有更深入的认识.同时在配置这些数据源时,也会更清楚每种属性的意义和用途.因此,如果大家想知其 ...

  6. MyBatis 源码分析 - 映射文件解析过程

    1.简介 在上一篇文章中,我详细分析了 MyBatis 配置文件的解析过程.由于上一篇文章的篇幅比较大,加之映射文件解析过程也比较复杂的原因.所以我将映射文件解析过程的分析内容从上一篇文章中抽取出来, ...

  7. MyBatis 源码分析系列文章导读

    1.本文速览 本篇文章是我为接下来的 MyBatis 源码分析系列文章写的一个导读文章.本篇文章从 MyBatis 是什么(what),为什么要使用(why),以及如何使用(how)等三个角度进行了说 ...

  8. MyBatis 源码分析-项目总览

    MyBatis 源码分析-项目总览 1.概述 本文主要大致介绍一下MyBatis的项目结构.引用参考资料<MyBatis技术内幕> 此外,https://mybatis.org/mybat ...

  9. (一) Mybatis源码分析-解析器模块

    Mybatis源码分析-解析器模块 原创-转载请说明出处 1. 解析器模块的作用 对XPath进行封装,为mybatis-config.xml配置文件以及映射文件提供支持 为处理动态 SQL 语句中的 ...

  10. 精尽 MyBatis 源码分析 - 基础支持层

    该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

随机推荐

  1. 安装完ODTwithODAC112012,出现ORA-12560:TNS:协议适配器错误

    参考:http://blog.csdn.net/tan_yixiu/article/details/6762357 操作系统:windows2008 Enterprise 64位 开发工具:VS201 ...

  2. Machine Learning系列--深入理解拉格朗日乘子法(Lagrange Multiplier) 和KKT条件

    在求取有约束条件的优化问题时,拉格朗日乘子法(Lagrange Multiplier) 和KKT条件是非常重要的两个求取方法,对于等式约束的优化问题,可以应用拉格朗日乘子法去求取最优值:如果含有不等式 ...

  3. openjudge-NOI 2.6-1759 最长上升子序列

    题目链接:http://noi.openjudge.cn/ch0206/1759/ 题解: 奇怪……之前博客里的o(nlogn)标程在codevs和tyvj上都能AC,偏偏它这里不行 #include ...

  4. 终止函数 atexit()

    函数名: atexit   头文件:#include<stdlib.h>   功 能: 注册终止函数(即main执行结束后调用的函数)   用 法: int atexit(void (*f ...

  5. EF – 4.CRUD与事务

    5.6.1 <Entity Framework数据更新概述>  首先介绍Entity Framework实现CRUD的基本方法,接着介绍了如何使用分部类增强和调整数据实体类的功能与行为特性 ...

  6. NIO-3网络通信(非阻塞)

    import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import ja ...

  7. DotNetOpenAuth实践系列

    写在前面 本人在研究DotNetOpenAuth的过程中,遇到很多问题,很多坑,花费了很多时间才调通这玩意,现在毫无保留的分享出来,希望博友们可以轻松的上手DotNetOpenAuth,减少爬坑时间. ...

  8. js获取或设置当前窗口url参数

    直接上代码 // 获取当前窗口url中param参数的值 function get_param(param){ var query = location.search.substring(1).spl ...

  9. 经常报错:Communications link failure

    连接池配置中配上:<property name="validationQuery" value="SELECT 1 FROM DUAL" />

  10. XML DOM(Document Object Model)

    1.XML DOM 是用于获取.更改.添加或删除 XML 元素的标准.2.节点(XML 文档中的每个成分都是一个节点):        整个文档是一个文档节点:        每个XML元素是一个元素 ...