文档章节

Mybatis3.3.x技术内幕(六):StatementHandler(Box stop here)

祖大俊
 祖大俊
发布于 2016/04/30 09:26
字数 1682
阅读 2144
收藏 16

神通广大的猴哥SqlSession,把琐事委托给二弟Executor来处理,二弟Executor可不那么傻,于是它又把事情委托给三弟StatementHandler,三弟憨厚老实,本着Box stop here的精神,无怨无悔不说,还任劳任怨,于是,一代伟人就此诞生了。

三弟StatementHandler从跑龙套开始,逐渐崛起,先后担任武术指导、制片、监制等职位,最后,经验丰富的它当上了导演,拍了属于自己的作品:三弟电影,又称3D电影。

有关Box stop here的故事,请自行了解。



1. 数据库操作invoke时序图

(Made In Visual Paradigm)

本文重点分析StatementHandler和ParameterHandler是如何与Executor共襄盛举的。(上图中的execute()失误画错了,应该是executeQuery()

2. Executor内使用StatementHandler模板

Statement stmt;
StatementHandler handler;
// 判断缓存内是否存在stmt
if (...) {
   // 不存在,就创建一个Statement(可能是Statement、PrepareStatement、CallableStatement)
    stmt = handler.prepare(connection);
}
handler.parameterize(stmt);

无论是何种Executor实现类,都使用上面的模板方法调用,所以,StatementHandler隐藏了创建Statement对象和parameterize初始化参数的秘密。


3. StatementHandler接口设计与类结构图

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

类结构图。

(Made In Intellij Idea IDE)

SimpleStatementHandler:用于处理Statement对象的数据库操作。

PreparedStatementHandler:用于处理PreparedStatement对象的数据库操作。

CallableStatementHandler:用于处理CallableStatement对象的数据库操作。(存储过程)


RoutingStatementHandler:用于创建上面三种Handler的策略类。(简直是装饰设计模式的极大错误示范,别以为长的帅,我就不抨击它,根本没有存在的理由


4. 基类BaseStatementHandler源码解析

protected abstract Statement instantiateStatement(Connection connection) throws SQLException;

对于BaseStatementHandler,完成了Sql相关信息的保存工作,也就是把通用食材准备好了。我们重点关注上面的抽象方法即可,也就是创建什么样的Statement对象,具体由子类去实现,子类相当于厨师,面对相同的食材,厨师对其烹饪的手法略有不同。


4.1. SimpleStatementHandler源码解析

   @Override
  public void batch(Statement statement) throws SQLException {
    String sql = boundSql.getSql();
    statement.addBatch(sql);
  }
  
  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    String sql = boundSql.getSql();
    statement.execute(sql);
    return resultSetHandler.<E>handleResultSets(statement);
  }
  
  @Override
  protected Statement instantiateStatement(Connection connection) throws SQLException {
    if (mappedStatement.getResultSetType() != null) {
      return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    } else {
      return connection.createStatement();
    }
  }

  @Override
  public void parameterize(Statement statement) throws SQLException {
    // N/A
  }

创建了一个Statement对象,由于Statement对象不支持“?”参数,所以,parameterize()是空实现。


4.2. PreparedStatementHandler源码解析

  @Override
  protected Statement instantiateStatement(Connection connection) throws SQLException {
    String sql = boundSql.getSql();
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
      String[] keyColumnNames = mappedStatement.getKeyColumns();
      if (keyColumnNames == null) {
        return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
      } else {
        return connection.prepareStatement(sql, keyColumnNames);
      }
    } else if (mappedStatement.getResultSetType() != null) {
      return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    } else {
      return connection.prepareStatement(sql);
    }
  }

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

创建了一个PrepareStatement对象,parameterize()则委托给ParameterHandler去设置。


4.3. CallableStatementHandler源码解析

  @Override
  protected Statement instantiateStatement(Connection connection) throws SQLException {
    String sql = boundSql.getSql();
    if (mappedStatement.getResultSetType() != null) {
      return connection.prepareCall(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    } else {
      return connection.prepareCall(sql);
    }
  }

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

创建了一个CallableStatement对象,parameterize()则委托给ParameterHandler去设置。


5. DefaultParameterHandler源码解析

对于ParameterHandler,它只有一个唯一的实现类:DefaultParameterHandler.setParameter()。

  @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);
            // ...
          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);
          }
        }
      }
    }
  }

请看方法参数,参数要求是一个PreparedStatement对象,当然可以处理PreparedStatement。然而,为何它还可以处理CallableStatement对象呢?

原因在这里,请看JDK中有关java.sql.CallableStatement的接口描述。

IN parameter values are set using the set methods inherited from  PreparedStatement

对于存储过程的IN参数来说,CallableStatement继承自PreparedStatement,传递进来的CallableStatement对象,其实也是PreparedStatement对象


6. 抨击RoutingStatementHandler的错误设计示范

public class RoutingStatementHandler implements StatementHandler {

  private final StatementHandler delegate;

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

  }

  @Override
  public Statement prepare(Connection connection) throws SQLException {
    return delegate.prepare(connection);
  }
  // ...

众所周知,装饰设计模式,是为了扩展对象功能,避免无限继承,而存在的一种设计模式。Mybatis倒好,居然把装饰设计模式当策略类来使用,又不做任何功能扩展,简直就是非常错误的设计示范。完全可以使用一个普通的策略类来完成,何必搞一个装饰设计模式?

幸好我不是罗果先生,否则说的话,比这个要难听十倍。


7. StatementHandler的创建时机和创建策略控制

  @Override
  public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
    Statement stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.update(stmt);
  }

Executor每执行一个query或update动作,都会创建一个StatementHandler对象。

StatementHandler创建策略有三种。(默认为PREPARED)

public enum StatementType {
  STATEMENT, PREPARED, CALLABLE
}

创建策略到底如何控制?可以在Mapper.xml内配置statementType属性。

<select id="findAllStudents" resultMap="StudentResult" statementType="STATEMENT">
	SELECT * FROM STUDENTS
</select>

获取StatementType的源码如下。

org.apache.ibatis.builder.xml.XMLStatementBuilder.parseStatementNode()。

StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));

默认值StatementType.PREPARED。


至此,一个Sql命令,经过马拉松式的长跑(SqlSession-->Executor-->StatementHandler-->Statement-->DB),终于如愿以偿的到达了终点。



8. 看不懂本篇博文内容的自救良药

作为博文作者,很多时候,很想向读者描述清楚一个东西到底是什么,但是,由于读者用功程度和浏览习惯不同,很多人依然看不明白写那么多到底阐述了些什么。没关系,咱有自救良药。

8.1 忘记SimpleStatementHandler,回归本真

SimpleStatementHandler等于下面两句话。

Statement stm = conn.createStatement()
return stm.execute(sql);


8.2 忘记PreparedStatementHandler,回归本真

PreparedStatementHandler等于下面三句话。

PreparedStatement pstm = conn.prepareStatement(sql);
pstm.setString(1, "Hello");
return pstm.execute();


8.3 忘记CallableStatementHandler,回归本真

CallableStatementHandler等于下面六句话。

CallableStatement cs = conn.prepareCall("{call pr_add(?,?,?)}");
cs.registerOutParameter(3, Types.INTEGER);
cs.setInt(1, 10);
cs.setString(2, "Hello");
cs.execute();
return cs.getInt(3);


版权提示:文章出自开源中国社区,若对文章感兴趣,可关注我的开源中国社区博客(http://my.oschina.net/zudajun)。(经过网络爬虫或转载的文章,经常丢失流程图、时序图,格式错乱等,还是看原版的比较好)

© 著作权归作者所有

共有 人打赏支持
祖大俊
粉丝 748
博文 32
码字总数 52477
作品 0
昌平
私信 提问
加载中

评论(3)

郑龙飞
郑龙飞
真羡慕学会设计模式的大佬,不爽就是怼😄
日落北极
日落北极
DefaultParameter在哪个包下?作用就是给PreparedStatement设置参数的吗?
O
Oxyz
请问楼主是张龙的学生吗~ >_<
Mybatis3.3.x技术内幕(十四):Mybatis之KeyGenerator

在Mybatis中,执行insert操作时,如果我们希望返回数据库生成的自增主键值,那么就需要使用到KeyGenerator对象。 需要注意的是,KeyGenerator的作用,是返回数据库生成的自增主键值,而不是生...

祖大俊
2016/05/11
890
3
Mybatis3.3.x技术内幕(一):SqlSession和SqlSessionFactory列传

前言:我长大了,成年了,有需求,但我单身,所以我要讨个媳妇,要求是:漂亮、高挑、身材好、笑容甜美…… 和A相亲:漂亮,不够高挑。 和B相亲:高挑,身材不够好。 和C相亲:身材好,笑容不...

祖大俊
2016/04/25
3.3K
2
Mybatis3.4.x技术内幕(十九):Mybatis之plugin插件设计原理

大多数框架,都支持插件,用户可通过编写插件来自行扩展功能,Mybatis也不例外。 我们从插件配置、插件编写、插件运行原理、插件注册与执行拦截的时机、初始化插件、分页插件的原理等六个方面...

祖大俊
2016/08/28
2.6K
2
【深入浅出MyBatis系列八】SQL自动生成插件

深入浅出MyBatis系列 【深入浅出MyBatis系列一】MyBatis入门 【深入浅出MyBatis系列二】配置简介(MyBatis源码篇) 【深入浅出MyBatis系列三】Mapper映射文件配置 【深入浅出MyBatis系列四】...

陶邦仁
2015/12/25
818
1
Mybatis3.3.x技术内幕(十二):Mybatis之TypeHandler

Mybatis中的TypeHandler有两个功能,一个是完成javaType至jdbcType的转换,另外一个是完成jdbcType至javaType的转换。 public interface TypeHandler<T> { void setParameter(PreparedStatem......

祖大俊
2016/05/06
991
0

没有更多内容

加载失败,请刷新页面

加载更多

RadosClient OSDC

RadosClient.h class librados::RadosClient : public Dispatcher//继承自Dispatcher(消息分发类){public: using Dispatcher::cct; md_config_t *conf;//配置文件private: ......

banwh
43分钟前
1
0
如果让你写一个消息队列,该如何进行架构设计?

面试题 如果让你写一个消息队列,该如何进行架构设计?说一下你的思路。 面试官心理分析 其实聊到这个问题,一般面试官要考察两块: 你有没有对某一个消息队列做过较为深入的原理的了解,或者...

李红欧巴
今天
4
0
错题

无知的小狼
今天
2
0
PowerShell因为在此系统中禁止执行脚本的解决方法

参考:window系统包管理工具--chocolatey 报错提示: & : 无法加载文件 C:\Users\liuzidong\AppData\Local\Temp\chocolatey\chocInstall\tools\chocolateyInstall.ps1,因为在此系统上禁止运...

近在咫尺远在天涯
今天
3
0
TP5 跨域请求处理

https://blog.csdn.net/a593706205/article/details/81774987 https://blog.csdn.net/wyk9916/article/details/82315700...

15834278076
今天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部