重新學習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對象的呢?第三篇分析

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