Mybatis源码分析八之StatementHandler一

一、StatementHandler

我们在分析Executor的最后发现,对数据库的实际操作都是通过StatementHandler来实现,本文接着Executor中调用StatementHandler开始分析,一步步的走向最底层。

  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();
      //实例化handler对象
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      //初始化Statement对象
      stmt = prepareStatement(handler, ms.getStatementLog());
      //调用handler的查询方法。
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

handler的实例是通过Configuration对象来初始化。查看newStatementHandler,我们得到的handler实例是RoutingStatementHandler对象(route即路由的意思,也就是说这个对象只是一个中间对象,会根据实际的需求路由到具体的handler上,所以查看其源码发现,其有个StatementHandler delegate,即委托对象,它是构造方法来决定具体使用的是什么类型的Handler,有simple、prepare、callable三种类型,可以通过statementType来配置,默认是prepare,他们分别对应着普通Statment、PreparedStatement、CallableStatement),实例化完成后会有一个插件调用的过程,所以如果我们有自定义的插件,会在这里执行一次(最早的调用应该是在实例化Executor的时候),对于插件我们就先不展开。

实例化完StatementHandler,就会调用prepareStatement得到Statement实例

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    //获取链接,从数据源DataSource中获取连接
    Connection connection = getConnection(statementLog);
    //调用handler的prepare方法获取Statement对象
    stmt = handler.prepare(connection, transaction.getTimeout());
    //参数赋值
    handler.parameterize(stmt);
    return stmt;
  }

所以handler.prepare默认调用的是PreparedStatementHandler的prepare方法,具体的实现在父类BaseStatementHandler中

  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
    //初始化Statement对象
      statement = instantiateStatement(connection);
      //设置超时时间
      setStatementTimeout(statement, transactionTimeout);
      //设置大小
      setFetchSize(statement);
      return statement;
    } catch (SQLException e) {
      closeStatement(statement);
      throw e;
    } catch (Exception e) {
      closeStatement(statement);
      throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
  }

初始化Statement对象instantiateStatement(connection),同样有三种不同的实现,普通Statement、PreparedStatement、CallableStatement。但是这里有一些属性值需要注意。
先简单介绍一下ResultSetType有四种取值:

  1. DEFAULT:默认值,也就是不需要做任何设置
  2. FORWARD_ONLY:对应着ResultSet.TYPE_FORWARD_ONLY,结果集只能一行一行的取,不能回滚到之前和之后。
  3. SCROLL_INSENSITIVE:对应着ResultSet.TYPE_SCROLL_INSENSITIVE,结果集可以进行回滚向前或向后
  4. SCROLL_SENSITIVE:对应着ResultSet.TYPE_SCROLL_SENSITIVE,结果集可以进行回滚向前或向后,并且如果数据库在这中间修改了数据,也可以反映到结果集上(需要具体的驱动支持)

ConcurrencyType这是伴随着ResultSetType设置的

  1. CONCUR_READ_ONLY:只读,不可以进行增删改
  2. CONCUR_UPDATABLE:可以利用结果集去进行增删改操作

初始化Statement对象是设置过期时间,以及fetchSize大小(也就是ResultSet每次next的时候可以取多少条数据,对于需要批量读取的,设置这个值可以提升执行效率)。

获取到Statement对象后,接下来就是进行参数值的设置,然后执行语句,处理结果集。

二、参数赋值

第一部分我们分析到获取Statement对象,接下来就是进行参数赋值的过程,源码中直接调用的就是handler.parameterize(stmt)方法-> parameterHandler.setParameters((PreparedStatement) statement)所以参数的处理有专门的ParameterHandler接口。
他的实例化是在BaseStatementHandler的构造方法里。

   //最后得到的是DefaultParameterHandler实现
    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);

ParameterHandler中最重要的方法就是setParameters()方法。

  public void setParameters(PreparedStatement ps) {
  //首先我们从BoundSql实例中获取到参数的映射集合
     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 {
          //根据具体的类型转换器去执行set操作,
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException | SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

从上述代码看,参数赋值比较简单,大致来说就是从BoundSql对象中获取参数映射集合,然后遍历该集合进行参数赋值。如果有特殊的类型处理器,就会调用具体的类型处理器来设置参数值,没有就是默认类型。之所以这么简单,是因为映射文件中的sql语句已经进行处理过了的,具体后续再分析。

三、ResultSetHandler

参数赋值完成,就该执行具体sql语句,然后处理结果集。结果集的处理是通过ResultSetHandler接口来实现的,他的实例也是在BaseStatementHandler的构造方法中完成。

this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);

最后得到的是DefaultResultSetHandler实例,通过调用handleResultSets()方法,完成结果集的处理工作。至于ResultSetHandler是怎么工作的,以及怎么一步步映射的我们将单独列出来分析。

四、总结

Executor最后都会调用StatementHandler来执行具体的数据库操作,configuration对象会实例化一个RoutingStatementHandler然后在委派到具体的StatementHandler(Prepared、Simple、Callable)。此时,具体的StatementHandler会在构造方法中实例化两个Handler,一个是ParameterHandler,用来处理参数赋值,另一个是ResultSetHandler,用来处理结果集映射,这样整个执行逻辑就算是完成。
但是结果集是怎么映射的?sql语句又是怎么解析的?为什么直接调用接口方法就可以执行数据库操作?又是怎么和spring结合的?后续我们将一一进行分析,然后再不断地拓展。

以上,有任何不对的地方,请指正,敬请谅解。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

菜鸟+1024

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值