重新学习Mybatis(二)

这里主要分析Mybatis是如何把我们的参数和SQL拼接成一起的?上一篇文章说到,insert操作最关键的步骤是SimpleExecutor.doUpdate()方法里的两行代码,return 那里和return上面的一行代码。以下代码均为Mybatis源码

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

通过configuration实例的newStatementHandler获得一个StatementHandler的实例,debug进去如下

进入new RoutingHandler方法如下

可以看到这里的这里的handler被实例成PrepareHandler对象的实例,实例化PrepareHandler对象的时候,会附加实例化BoundSql这个对象,如下图:

可以看到这个对象又是从mapperStatement对象中拿的,在深入一点呢?

可以看到boundSql来自于sqlSource,sqlSource又来自于mapperStatement对象。

第一篇里我们也说过这个重要的东西,这个对象什么时候出现的呢的,以后会解释。

那实例化prepareStatement这个对象有什么用呢?

接下来进入prepareStatement方法里,找到倒数第三行代码,handler.prepare(),看方法名字,类似于对handler进行预处理,那到底预处理了什么?

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

debug进去,后面的方法就是设置超时时间和FetchSize,重点关注instantiateStatement这个方法,instantiate--实例化的意思,那这个方法就是实例化Statement,究竟实例化干了哪些东西呢?

 @Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      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);
    }
  }

debug的时候发现,有三个类型供我们选择,从上面知道,获得的是PrepareHandler对象的实例,所以肯定是进入这个类的方法,细心的童鞋肯定看到了当前的SQL不是我们原始的,并且还有boundSql对象,那什么时候Mybatis装换的呢?后面详细解释。

具体代码如下,可以看到所有的return语句都是connection.prepareStatement(sql,...);到这里特别熟悉,这里就是传统JDBC的

PreparedStatement prepare = conn.prepareStatement(sql);这一句代码,实例化为PreparedStatement对象

  @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() == ResultSetType.DEFAULT) {
      return connection.prepareStatement(sql);
    } else {
      return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    }
  }

OK,至此为止SimpleExecutor.prepareStatement()方法中第一个重要的方法执行完毕,已经实例化Statement为PrepareStatement对象了。那接下来套用传统JDBC的路子就是给参数赋值

进入handler.parameterize()方法,方法名字就是确定handler的参数,可以看出这里就是给PrepareStatement对象赋值的对象的地方了。debug进去

这里Mybatis做的工作基本上就是为参数赋值,在boundSql对象中拿到parametermappings。上面已经说了,boundSql和parametermappings是怎么来的

  @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 | SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

这里就是根据每个语句中的字段找到其中对应的值,拿第一个字段id赋值过程进行举例

这里可以看出获取到id的值为7,并且将知道了JavaType为Integer,至于parameterMappings是什么时候生成的,后面会详细解释。

并且根据当前parameterMappings获得其具体的TypeHandler,这个TypeHandler有很多实例化的类,什么int,long基本上和数据库类型对应,这个东西是后面给SQL语句中赋值用的,

进入IntegerTypeHandler会看到,如果当前字段没有设置jdbcType,就是在XML文件里没有这样写#{id,jdbcType=int}这种

则会根据当前属性的类型设置值,这里id属性的类型为Integer,所以就是setInt(i,parameter);这里是不是想起了传统的JDBC的部分代码?

就这样循环把该SQL语句中的字段和参数一一设置。

这里Mybatis完成的工作就是传统JDBC中

prepare.setString(1,user.getId);

prepare.setString(2,user.getName);

prepare.setInt(3,user.getAge);

上面这三步。

到此为止,文章开头那里的doUpdate()方法里的prepareStatement就执行完了。到这里为止传统的JDBC中的prepareStatement.executeUpdate()之前的步骤都已经执行完毕了,接下来肯定就是这一步了,那么handler.update()就是这一步,在上一篇里已经说了,ps.execute()。

至此,Mybatis如何获取PrepareStatement对象以及获取参数,并给参数设置正确的jdbcType,最终执行SQL。这些就一步一步的真真切切的看到了。

接下来研究Mybatis是如何从哪里开始就拿到了我们XML文件里的SQL语句并把它实例化成一个MapperStatement对象的呢?第三篇分析

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章