MyBatis進階之普通SQL執行流程

          mybatis作爲一款持久層框架,最爲主要的職責,當然是執行我們在mapper映射文件中寫的sql語句,但是sql語句也分兩種:

(1)普通sql語句

(2)動態sql語句,包含了<if | foreach | choose | when | otherwise | where | set | trim>等可以動態拼接sql的標籤

         本篇博客將以普通的sql語句執行,根據debug源碼的方式,跟蹤sql是如何解析,以及最後是如何執行的。測試代碼,將以之前博客  MyBatis入門之一對多、多對多查詢  的代碼爲例。首先看看mapper映射文件及測試代碼:

UserMapper.xml:

<mapper namespace="com.qxf.mapper.UserMapper">

    <!-- 自定義結果映射:
     id:是主鍵
     property:是Java對象的屬性名
     column:是數據庫中列名,或者查詢中取的列的別名
    -->
    <resultMap id="userMap" type="User">
        <id property="id" column="id"></id>
        <result property="username" column="username"></result>
        <result property="password" column="password"></result>
        <result property="isValid" column="is_valid"></result>

        <!-- 一對多關聯查詢,使用collection,其中:
           collection中的property屬性blogs:是User類中的屬性名稱,對應就可以了(private List<Blog> blogs);
           javaType:是User類中的blogs這個屬性,屬於哪個Java類型,這裏當然是屬於java.util.List
           ofType: 是List泛型中的類型,這裏是Blog類型
           id中的column是t_blog表的id,只是在爲了避免重名衝突,查詢的時候取了別名blog_id
        -->
        <collection property="blogs" javaType="java.util.List" ofType="Blog">
            <id property="id" column="blog_id"></id>
            <result property="title" column="title"></result>
            <result property="userId" column="user_id"></result>
        </collection>
    </resultMap>

    <!--id 與接口的方法名相同,注意:
       這裏的返回值類型不再是resultType了,而是resultMap
       而且resultMap的值,就是上面<resultMap>標籤的id屬性值
    -->
    <select id="getUserAndBlogByUserId" parameterType="string" resultMap="userMap">
        select u.id,u.username,u.password,u.is_valid,b.id as blog_id,b.title,b.user_id
         from t_user u,t_blog b
        where u.id = b.user_id
         and u.id=#{id}
    </select>

</mapper>

測試代碼: 

public class One2ManyQuery {
    public static void main(String[] args) throws IOException {
        //讀取配置信息
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        //根據配置信息,創建SqlSession工廠
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
        //SqlSession工廠創建SqlSession
        SqlSession sqlSession = factory.openSession();
        //獲取接口的代理對象
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.getUserAndBlogByUserId("123");
        System.out.println(user);
    }
}

        這裏,將以下面這句代碼爲起點,我的想法是將mybatis的原理進行劃分成幾個模塊分析,首先從結果分析,然後再倒推。所以現在,關於mybatis如何讀取並解析配置文件的、如何創建SqlSession的、如何創建接口的代理對象的,後面有時間再寫,敬請關注。

User user = mapper.getUserAndBlogByUserId("123");

step into跟進去,發現會調用MapperProxy的invoke()方法,證實了上面的mapper是接口的代理對象:

再跟進去:發現execute()方法,會先判斷sql的類型:INSERT、UPDATE、DELETE、SELECT、FLUSH,我們這裏是select查詢,所以我們只需要關注select分支語句:

      //execute()方法的2個全局變量
      //result:是方法的返回值,Object類型
      //param: 是方法參數,Object類型
      case SELECT:
            if (this.method.returnsVoid() && this.method.hasResultHandler()) {
                this.executeWithResultHandler(sqlSession, args);
                result = null;
            } else if (this.method.returnsMany()) {
                result = this.executeForMany(sqlSession, args);
            } else if (this.method.returnsMap()) {
                result = this.executeForMap(sqlSession, args);
            } else if (this.method.returnsCursor()) {
                result = this.executeForCursor(sqlSession, args);
            } else {
                //最終會走到這裏,先進行參數解析,其中args是我們傳入的參數,Object[]類型
                param = this.method.convertArgsToSqlCommandParam(args);
                //然後執行sqlSession.selectOne獲得sql語句執行結果
                result = sqlSession.selectOne(this.command.getName(), param);
                if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
                    result = Optional.ofNullable(result);
                }
            }
            break;

所以這裏又分爲2步:

(1)解析sql參數爲Object類型

(2)執行sqlSession.selectOne獲得sql語句執行結果 

一、sql參數是如何解析的?

      重點關注這句代碼:

param = this.method.convertArgsToSqlCommandParam(args);

一路跟進去,發現最終執行的是ParamNameResolver的getNamedParams()方法

    public Object getNamedParams(Object[] args) {
        int paramCount = this.names.size();
        if (args != null && paramCount != 0) {
            //沒有參數註解,並且只有一個參數,則直接返回第一個參數
            if (!this.hasParamAnnotation && paramCount == 1) {
                return args[(Integer)this.names.firstKey()];
            } else {
                //否則,返回一個ParamMap對象
                Map<String, Object> param = new ParamMap();
                int i = 0;

                for(Iterator var5 = this.names.entrySet().iterator(); var5.hasNext(); ++i) {
                   
                    //下面的意思是,將傳進來的參數封裝成Map對象
                    //其中key爲:arg0、arg1....以及param1、param2...
                    //而value就是我們依次傳入的參數值 
                    Entry<Integer, String> entry = (Entry)var5.next();
                    param.put((String)entry.getValue(), args[(Integer)entry.getKey()]);
                    String genericParamName = "param" + String.valueOf(i + 1);
                    if (!this.names.containsValue(genericParamName)) {
                        param.put(genericParamName, args[(Integer)entry.getKey()]);
                    }
                }

                return param;
            }
        } else {
            return null;
        }
    }

那麼,ParamMap又是什麼鬼?其源碼如下:

    public static class ParamMap<V> extends HashMap<String, V> {
        private static final long serialVersionUID = -2212268410512043556L;

        public ParamMap() {
        }

        public V get(Object key) {
            if (!super.containsKey(key)) {
                throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + this.keySet());
            } else {
                return super.get(key);
            }
        }
    }

根據源碼,ParamMap繼承了HashMap,只是重寫了get()方法,所以完全可以將它看做是一個HashMap。

根據上面分析:

(1)如果只有一個參數的話,則直接返回一個Object參數

(2)如果有多個參數的話,則會返回一個Map對象,其中key爲:arg0、arg1....以及param1、param2...

        而value就是我們依次傳入的參數值 

 

二、sqlSession.selectOne()方法是如何執行的?

result = sqlSession.selectOne(this.command.getName(), param);

其中,第一個參數是namespace+id組成的字符串,這裏是"com.qxf.mapper.UserMapper.getUserAndBlogByUserId"

第二個參數,是我們傳入的字符串參數,"123"

先看看selectOne的樣子:

發現,它最終還是調用了DefaultSqlSession的selectList()方法,然後根據返回結果判斷:

(1)如果返回的list大小隻有一個,則符合預期,直接返回list的第一個元素

(2)如果list的大小,大於1個,那麼就會拋出異常

(3)返回null值

 

所以,現在的目標,自然是轉向了DefaultSqlSession的selectList()方法,最終執行重載的方法如下:

    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        List var5;
        try {
            //根據statement=namespace+id=com.qxf.mapper.UserMapper.getUserAndBlogByUserId
            //在配置信息中查找對應的MappedStatement
            //每個MappedStatement,其實就是我們的<select|update|insert|delete>等標籤及其內容
            MappedStatement ms = this.configuration.getMappedStatement(statement);
            //通過執行器executor的query()方法執行
            var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
        } catch (Exception var9) {
            throw ExceptionFactory.wrapException("Error querying database.  Cause: " + var9, var9);
        } finally {
            ErrorContext.instance().reset();
        }

        return var5;
    }

其中,RowBounds是一個內存分頁對象,包含offset和limit兩個屬性。

目光轉移到,executor的query()方法:

    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
       //通過傳入的參數,構造BoundSql對象,就是給它相應的屬性賦值,這裏不細究
        BoundSql boundSql = ms.getBoundSql(parameterObject);
       //創建緩存的key值
        CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);
       //執行重載方法
        return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }

這裏邊有很多重載的方法,就不一一列舉了,因爲是第一次查詢,不存在緩存,所以後面會執行到BaseExecutor的queryFromDatabase()方法,顧名思義,就是從數據庫查詢

繼而轉向doQuery()方法:

    public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;

        List var9;
        try {
            Configuration configuration = ms.getConfiguration();
            //構建StatementHandler對象
            StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            //創建Statement
            stmt = this.prepareStatement(handler, ms.getStatementLog());
           //執行查詢
            var9 = handler.query(stmt, resultHandler);
        } finally {
            this.closeStatement(stmt);
        }

        return var9;
    }

這裏有幾個關鍵的地方:

(1)StatementHandler 是什麼?

(2)參數是如何設置的?

(3)sql是怎樣執行的?

(4)結果集是怎樣處理的?

(一)StatementHandler 是什麼?

    對這行代碼進行跟蹤:

//構建StatementHandler對象
StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);

先看下繼承關係(圖片來源於https://www.cnblogs.com/cxuanBlog/p/11295488.html):


 

StatementHandler的默認實現類是RoutingStatementHandler,而RoutingStatementHandler持有一個StatementHandler的對象,也就是變量delegate,而變量delegate則會根據statementType來創建,這裏會創建PreparedStatementHandler:

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

  }

StatementHandler主要負責操作 Statement 對象與數據庫進行交互。

而創建PreparedStatementHandler對象,會調用父類的構造方法:

  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;
    //創建參數處理器對象ParameterHandler
    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    //創建結果處理器對象ResultSetHandler
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
  }

重點關注參數處理器和結果處理器,後面都會用到:

    //創建參數處理器對象ParameterHandler
    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    //創建結果處理器對象ResultSetHandler
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);

 

(二)參數是如何設置的?

回到doQuery方法:

    public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;

        List var9;
        try {
            Configuration configuration = ms.getConfiguration();
            //構建StatementHandler對象
            StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            //創建Statement
            stmt = this.prepareStatement(handler, ms.getStatementLog());
           //執行查詢
            var9 = handler.query(stmt, resultHandler);
        } finally {
            this.closeStatement(stmt);
        }

        return var9;
    }

我們重點關注這句:

//創建Statement
stmt = this.prepareStatement(handler, ms.getStatementLog());

跟進去,會發現調用一個方法 handler.parameterize(stmt);

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
   //獲取連接
    Connection connection = getConnection(statementLog);
    //創建一個Statement對象,這裏創建的是PreparedStatement對象
    stmt = handler.prepare(connection, transaction.getTimeout());
    //處理參數
    handler.parameterize(stmt);
    return stmt;
  }

那麼,我們現在看看, handler.parameterize(stmt)方法的實現,點進去發現有很多重載的方法,後面會調用在

(一)StatementHandler 是什麼? 中,指到的參數處理器的setParameters()方法:

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



    public void setParameters(PreparedStatement ps) {
        ErrorContext.instance().activity("setting parameters").object(this.mappedStatement.getParameterMap().getId());
        List<ParameterMapping> parameterMappings = this.boundSql.getParameterMappings();
        if (parameterMappings != null) {
            for(int i = 0; i < parameterMappings.size(); ++i) {
                ParameterMapping parameterMapping = (ParameterMapping)parameterMappings.get(i);
                if (parameterMapping.getMode() != ParameterMode.OUT) {
                    //屬性名
                    String propertyName = parameterMapping.getProperty();
                    Object value;
                    if (this.boundSql.hasAdditionalParameter(propertyName)) {
                        value = this.boundSql.getAdditionalParameter(propertyName);
                    } else if (this.parameterObject == null) {
                        value = null;
                    } else if (this.typeHandlerRegistry.hasTypeHandler(this.parameterObject.getClass())) {
                        value = this.parameterObject;
                    } else {
                        MetaObject metaObject = this.configuration.newMetaObject(this.parameterObject);
                        value = metaObject.getValue(propertyName);
                    }

                    TypeHandler typeHandler = parameterMapping.getTypeHandler();
                    JdbcType jdbcType = parameterMapping.getJdbcType();
                    if (value == null && jdbcType == null) {
                        jdbcType = this.configuration.getJdbcTypeForNull();
                    }

                    try {
                        //設置參數,value是屬性值
                        typeHandler.setParameter(ps, i + 1, value, jdbcType);
                    } catch (SQLException | TypeException var10) {
                        throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + var10, var10);
                    }
                }
            }
        }

    }

重點關注:

//設置參數,value是屬性值
typeHandler.setParameter(ps, i + 1, value, jdbcType);

最終會發現,它調用的JDBC中PreparedStatement的對應的setXXX(i, parameter);

至此,我們傳遞的參數就設置好了。

 

(三)sql是怎樣執行的?

還是回到doQuery方法:

    public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;

        List var9;
        try {
            Configuration configuration = ms.getConfiguration();
            //構建StatementHandler對象
            StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            //創建Statement
            stmt = this.prepareStatement(handler, ms.getStatementLog());
           //執行查詢
            var9 = handler.query(stmt, resultHandler);
        } finally {
            this.closeStatement(stmt);
        }

        return var9;
    }

重點看這句:

//執行查詢
var9 = handler.query(stmt, resultHandler);

可以看到,調用的是StatementHandler的query方法,更爲具體的來說,是調用PreparedStatementHandler的query方法:

  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.handleResultSets(ps);
  }

層層跟進之後,發現,還是調用JDBC中PreparedStatement的execute()方法執行sql語句的。

至此,sql語句也已經執行完了。

 

(四)結果集是怎樣處理的?

緊接着上面這行代碼:

  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    //執行預編譯的sql語句
    ps.execute();
    //處理結果集
    return resultSetHandler.handleResultSets(ps);
  }

所以,我們要看的是:

//處理結果集
return resultSetHandler.handleResultSets(ps);

看看它的具體實現:

public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    final List<Object> multipleResults = new ArrayList<>();

    int resultSetCount = 0;
    //獲取第一個結果集(ps.getResultSet()即可獲取),並將結果集進行包裝
    ResultSetWrapper rsw = getFirstResultSet(stmt);
    //獲取我們配置的ResultMap
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      //處理結果集
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
      while (rsw != null && resultSetCount < resultSets.length) {
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }

    return collapseSingleResultList(multipleResults);
  }

我們重點關注這句,主要看看是怎麼給實體類的屬性賦值的:

//處理結果集
handleResultSet(rsw, resultMap, multipleResults, null);

看看它的具體實現:

  private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
    try {
      if (parentMapping != null) {
        handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
      } else {
        if (resultHandler == null) {
          DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
          //處理行值
          handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
          multipleResults.add(defaultResultHandler.getResultList());
        } else {
          handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
        }
      }
    } finally {
      // issue #228 (close resultsets)
      closeResultSet(rsw.getResultSet());
    }
  }

然後又會調用    handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);

  public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    //這裏會判斷,有沒有嵌套的resultMap
    if (resultMap.hasNestedResultMaps()) {
      ensureNoRowBounds();
      checkResultHandler();
      //代碼會執行到這裏
      handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    } else {
      handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    }
  }

執行到        handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);

該方法內部會調用:

rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);

繼而調用applyPropertyMappings()方法,對屬性進行賦值:

private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
      throws SQLException {
    final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
    boolean foundValues = false;
    final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
    for (ResultMapping propertyMapping : propertyMappings) {
      //獲取列名
      String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
      if (propertyMapping.getNestedResultMapId() != null) {
        // the user added a column attribute to a nested result map, ignore it
        column = null;
      }
      if (propertyMapping.isCompositeResult()
          || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
          || propertyMapping.getResultSet() != null) {
        //根據列名獲取該列的值,resultSet.getXXX(列名) 獲取
        Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
        // issue #541 make property optional
        final String property = propertyMapping.getProperty();
        if (property == null) {
          continue;
        } else if (value == DEFERRED) {
          foundValues = true;
          continue;
        }
        if (value != null) {
          foundValues = true;
        }
        if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {
          // gcode issue #377, call setter on nulls (value is not 'found')
          //給屬性賦值,property是實體類的屬性名,value則是查詢的值
          metaObject.setValue(property, value);
        }
      }
    }
    return foundValues;
  }

具體表現爲:

(1)獲取列值:

//根據列名獲取該列的值,resultSet.getXXX(列名) 獲取
Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);

(2)給屬性賦值:

//給屬性賦值,property是實體類的屬性名,value則是查詢的值
metaObject.setValue(property, value);

基本上是這樣了。

第一次寫原理類的東西,感覺一下子追源碼追得太深,並不好。

 

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