Mybatis 源碼剖析

傳統(tǒng)方式源碼剖析

源碼剖析-初始化陈轿,點進build方法可以看下

InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//這一行代碼正是初始化工作的開始
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);

進入源碼分析:

// 1.最初調(diào)用的build
   public SqlSessionFactory build(InputStream inputStream) {
// 調(diào)用了重載方法
        return this.build((InputStream)inputStream, (String)null, (Properties)null);
    }
//2.調(diào)用的重載方法
    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        SqlSessionFactory var5;
        try {
//XMLConfigBuilder是專門解析mybatis的配置文件的類
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//這里又調(diào)用了一個重載方法拱燃,parser.parse()返回的是configuration對象
            var5 = this.build(parser.parse());
        } catch (Exception var14) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
        } finally {
            ErrorContext.instance().reset();

            try {
                inputStream.close();
            } catch (IOException var13) {
            }

        }

        return var5;
    }

Mybatis在初始化的時候,會將Mybatis的配置信息全部加載到內(nèi)存中沸久,使用org.apache.ibatis.session.Configuration實例來維護坛缕。
下面進入對配置文件解析部分:
首先對Configuration對象進行介紹:
Configuration對象的結(jié)構(gòu)和xml配置文件的對象基本相同。
回顧一下xml在配置有哪些:
properties(屬性)胡诗,settings(設(shè)置)邓线,typeAliass(類型別名),typeHandless(類型處理器)煌恢,objectFactory(對象工廠)骇陈,mapper(映射器)等。Configuration也有相應(yīng)的對象屬性來封裝它們瑰抵。
也就是說你雌,初始化配置文件信息的本質(zhì)就是創(chuàng)建Configuration對象,將解析的xml數(shù)據(jù)封裝到Configuration內(nèi)部屬性中二汛。

觀察一下上面源碼中的parser.parse() (XMLConfigBuilder#parse

//解析xml成Configuration對象
    public Configuration parse() {
//若已解析婿崭,拋出BuilderException異常
        if (this.parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        } else {
//標(biāo)識已解析
            this.parsed = true;
//解析 xml configuration 節(jié)點            this.parseConfiguration(this.parser.evalNode("/configuration"));
            return this.configuration;
        }
    }

XMLConfigBuilder#parseConfiguration

private void parseConfiguration(XNode root) {
        try {
/*
*下邊的代碼基本都是都是解析標(biāo)簽
*/ 
            this.propertiesElement(root.evalNode("properties"));
            Properties settings = this.settingsAsProperties(root.evalNode("settings"));
            this.loadCustomVfs(settings);
            this.typeAliasesElement(root.evalNode("typeAliases"));
            this.pluginElement(root.evalNode("plugins"));
            this.objectFactoryElement(root.evalNode("objectFactory"));
            this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
            this.settingsElement(settings);
            this.environmentsElement(root.evalNode("environments"));
            this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            this.typeHandlerElement(root.evalNode("typeHandlers"));
            this.mapperElement(root.evalNode("mappers"));
        } catch (Exception var3) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
        }
    }

介紹一下MappedStatement:
作用:MappedStatement和Mapper配置文件中的一個select/update/insert/delete節(jié)點相對應(yīng)。mapper中配置的標(biāo)簽都被封裝到了此對象中肴颊,主要用途是描述一條SQL語句氓栈。
初始化過程:回顧剛開始介紹的加載配置文件的過程中,會對mybatis-config.xml中的各個標(biāo)簽進行解析婿着,其中mappers標(biāo)簽都用來引入mapper.xml文件或者配置mapper接口的目錄授瘦。

<select id ="getUser" resultType="user">
 select * from user where id=#{id}
</select>

這樣一個select標(biāo)簽會在初始化配置文件時被封裝成一個MappedStatement對象,然后存儲在Configuration對象的mappedStatements中

protected final Map<String, MappedStatement> mappedStatements;
...
this.mappedStatements = new Configuration.StrictMap("Mapped Statements collection");

mappedStatements 是一個HashMap竟宋,存儲時key=全限定類名+方法名提完,value=對應(yīng)的MappedStatement對象。

  • 在XMLConfigBuilder中處理:(XMLConfigBuilder#parseConfiguration
            this.mapperElement(root.evalNode("mappers"));

到此對xml配置文件的解析就結(jié)束了袜硫,回到步驟2調(diào)用的重載build方法(SqlSessionFactoryBuilder#build

    public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
    }

源碼剖析-執(zhí)行SQL流程

先簡單介紹一下SqlSession

SqlSession是一個接口氯葬,它有兩個實現(xiàn)類: DefaultSqlSession(默認(rèn))和SqlSessionManager(棄用不做介紹)
SqlSessions是MyBatis用于和數(shù)據(jù)庫交互的頂層類,通常它與ThreadLocal綁定婉陷,一個會話使用一個SqlSession帚称,并且在使用完畢后需要close

Executor

Executor是一個接口官研,他有三個常用的實現(xiàn)類
BatchExecutor(重用語句執(zhí)行并執(zhí)行批量更新)
ReuseExecutor(重用預(yù)處理語句prepared statements)
SimpleExecutor(普通的執(zhí)行器)

繼續(xù)分析,初始化完畢后闯睹,我們就要執(zhí)行SQL了

SqlSession sqlSession = factory.openSession();
List<User> list = 
sqlSession.selectList("com.lagou.mapper.UserMapper.getUserByName");

SqlSessionFactory#openSession

  public SqlSession openSession() {
//getDefaultExecutorType 傳遞的是SimpleExecutor
        return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
    }

...
// 進入openSessionFromDataSource
// ExecutorType 為Executor的類型戏羽,TransactionIsolationLevel 為事務(wù)隔離級別,autoCommit為是否開啟事務(wù)
//openSessionFromDataSource的重載方法可以指定獲得的SqlSession類型和事務(wù)處理
  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//根據(jù)參數(shù)創(chuàng)建指定類型的Executor
      final Executor executor = configuration.newExecutor(tx, execType);
//返回的是DefaultSqlSession
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

執(zhí)行sqlsession的api - selectList

  @Override
  public <E> List<E> selectList(String statement) {
    return this.selectList(statement, null);
  }

  @Override
  public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
  }

  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
//根據(jù)傳入的全限定名+方法從映射的Map中取出MappedStatenent對象
      MappedStatement ms = configuration.getMappedStatement(statement);
//調(diào)用Executor中的方法處理
//RowBounds是用來邏輯分頁的
//wrapCollection用來裝飾集合或者數(shù)組參數(shù)
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

源碼剖析-executor
繼續(xù)源碼中的步驟楼吃,進入BaseExecutor#query()

@Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//根據(jù)傳入的參數(shù)動態(tài)獲取sql始花,最后返回用BoundSql對象表示
    BoundSql boundSql = ms.getBoundSql(parameter);
//為本次查詢創(chuàng)建查詢緩存
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }

//進入query的重載方法
  @SuppressWarnings("unchecked")
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
//如果緩存中沒有本次查詢的值,那么從數(shù)據(jù)庫中查詢
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

去數(shù)據(jù)庫查詢

  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
//查詢的方法
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);
    }
//查詢結(jié)果放入緩存中
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

doQuery方法(SimpleExecutor#doQuery)

  @Override
  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();
//傳入?yún)?shù)創(chuàng)建StatementHandler對象來執(zhí)行查詢
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//創(chuàng)建jdbc的statement對象
      stmt = prepareStatement(handler, ms.getStatementLog());
//statement進行處理
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

創(chuàng)建Statement的方法(SimpleExecutor#prepareStatement

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
//這條代碼中的getConnection方法經(jīng)過重重調(diào)用最后會調(diào)用openConnection方法孩锡,從連接池中獲取到連接
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }

JdbcTransaction#openConnection

  protected void openConnection() throws SQLException {
    if (log.isDebugEnabled()) {
      log.debug("Opening JDBC Connection");
    }
//從連接池獲得連接
    connection = dataSource.getConnection();
    if (level != null) {
      connection.setTransactionIsolation(level.getLevel());
    }
    setDesiredAutoCommit(autoCommmit);
  }

上述的Executor.query()方法幾經(jīng)轉(zhuǎn)折酷宵,最后會創(chuàng)建一個StatementHandler對象,然后將必要的參數(shù)傳遞給StatementHandler躬窜,使用StatementHandler來完成對數(shù)據(jù)庫的查詢浇垦,最終返回List結(jié)果集。
從上述的代碼我們可以看除荣挨,Executor的功能和作用:

    1. 根據(jù)傳遞的參數(shù)男韧,完成對SQL語句的動態(tài)解析,生成BoundSql對象默垄,供StatementHandler使用
    1. 為查詢創(chuàng)建緩存此虑,以提高性能
    1. 創(chuàng)建JDBC的statement連接對象,傳遞給 StatementHandler對象口锭,返回List查詢結(jié)果

源碼剖析-StatementHandler
StatementHandler對象主要完成兩個工作:

  • 對JDBC的preparedStatement類型的對象朦前,創(chuàng)建的過程中,我們使用的SQL語句字符串會包含若干個“讹弯?”占位符况既,我們在其后再對占位符進行設(shè)值这溅。StatementHandler通過parametize(statement)方法對Statenebr進行設(shè)值组民。
  • StatementHandler通過List query(Statement statement, ResultHandler resultHandler) 方法來完成執(zhí)行statement,和將statement對象返回的resultSet封裝成List;

進入到StatementHandler的parameterize(statement)方法的實現(xiàn)(PreparedStatementHandler#parameterize

  @Override
  public void parameterize(Statement statement) throws SQLException {
    parameterHandler.setParameters((PreparedStatement) statement);
  }
//對某一個Statement設(shè)置參數(shù)
  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);
          }
//每一個mapping都有一個TypeHandler悲靴,根據(jù)TypeHandler來對preparedStatement進行設(shè)置參數(shù)
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
//設(shè)置參數(shù)
            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);
          }
        }
      }
    }
  }

從上述代碼可以看到臭胜,StatementHandler的parameterize(Statement)方法調(diào)用了ParameterHandler.setParameters方法
ParameterHandler的setParameters方法負(fù)責(zé)根據(jù)我們輸入的參數(shù),對statement對象的“癞尚?”占位符處進行賦值耸三。
進入到StatementHandler的<E> List<E> query(Statement statement, ResultHandler resultHandler)方法的實現(xiàn)
PreparedStatementHandler#query

  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
//調(diào)用PreparedStatement.execute方法,然后將resultSet交給resultSetHandler處理
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
//使用resultSetHandler來處理resultSet
    return resultSetHandler.<E> handleResultSets(ps);
  }

resultSetHandler的handleResultSets方法會將Statement語句執(zhí)行后生成的resultSet結(jié)果集轉(zhuǎn)換成List結(jié)果集浇揩,源碼
DefaultResultSetHandler#handleResultSets

public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
 //多resultSet結(jié)果集合仪壮,每一個ResultSet對應(yīng)一個Object對象,而實際上胳徽,每一個Object是List<Object>對象
//在不考慮存儲過程的多resultSet的情況积锅,普通的查詢爽彤,實際就是一個resultSet,也就是說multipleResults 最多就一個元素
    final List<Object> multipleResults = new ArrayList<Object>();

    int resultSetCount = 0;
//獲得首個resultSet對象,并封裝成ResultSetWrapper 對象
    ResultSetWrapper rsw = getFirstResultSet(stmt);
//獲取resultMaps數(shù)組
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
//校驗
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
//獲得ResultMap對象
      ResultMap resultMap = resultMaps.get(resultSetCount);
//處理ResultSet缚陷,將結(jié)果添加multipleResults中
      handleResultSet(rsw, resultMap, multipleResults, null);
//獲得下一個ResultSet對象适篙,并封裝成ResultSetWrapper對象
      rsw = getNextResultSet(stmt);
//清理
      cleanUpAfterHandlingResultSet();
//resultSetCount++
      resultSetCount++;
    }
//mappedStatement的resultSet  只在存儲過程中使用 ,暫時不考慮
    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);
  }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末箫爷,一起剝皮案震驚了整個濱河市嚷节,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌虎锚,老刑警劉巖硫痰,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異窜护,居然都是意外死亡碍论,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門柄慰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鳍悠,“玉大人,你說我怎么就攤上這事坐搔〔匮校” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵概行,是天一觀的道長蠢挡。 經(jīng)常有香客問我,道長凳忙,這世上最難降的妖魔是什么业踏? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮涧卵,結(jié)果婚禮上勤家,老公的妹妹穿的比我還像新娘。我一直安慰自己柳恐,他們只是感情好伐脖,可當(dāng)我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著乐设,像睡著了一般讼庇。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上近尚,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天蠕啄,我揣著相機與錄音,去河邊找鬼戈锻。 笑死歼跟,一個胖子當(dāng)著我的面吹牛却嗡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播嘹承,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼窗价,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了叹卷?” 一聲冷哼從身側(cè)響起撼港,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎骤竹,沒想到半個月后帝牡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡蒙揣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年靶溜,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片懒震。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡罩息,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出个扰,到底是詐尸還是另有隱情瓷炮,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布递宅,位于F島的核電站娘香,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏办龄。R本人自食惡果不足惜烘绽,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望俐填。 院中可真熱鬧安接,春花似錦、人聲如沸玷禽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽矢赁。三九已至,卻和暖如春贬丛,著一層夾襖步出監(jiān)牢的瞬間撩银,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工豺憔, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留额获,地道東北人够庙。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像抄邀,于是被迫代替她去往敵國和親耘眨。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容