MyBatis原理系列(三)-手把手帶你了解SqlSession,SqlSessionFactory,SqlSessionFactoryBuilder的關(guān)系

MyBatis原理系列(一)-手把手帶你閱讀MyBatis源碼
MyBatis原理系列(二)-手把手帶你了解MyBatis的啟動流程
MyBatis原理系列(三)-手把手帶你了解SqlSession,SqlSessionFactory,SqlSessionFactoryBuilder的關(guān)系
MyBatis原理系列(四)-手把手帶你了解MyBatis的Executor執(zhí)行器
MyBatis原理系列(五)-手把手帶你了解Statement蝶押、StatementHandler碎浇、MappedStatement間的關(guān)系
MyBatis原理系列(六)-手把手帶你了解BoundSql的創(chuàng)建過程
MyBatis原理系列(七)-手把手帶你了解如何自定義插件
MyBatis原理系列(八)-手把手帶你了解一級緩存和二級緩存
MyBatis原理系列(九)-手把手帶你了解MyBatis事務(wù)管理機(jī)制

在上篇文章中察迟,我們講解了MyBatis的啟動流程骤宣,以及啟動過程中涉及到的組件,在本篇文中碳柱,我們繼續(xù)探索SqlSession,SqlSessionFactory,SqlSessionFactoryBuilder的關(guān)系读跷。SqlSession作為MyBatis的核心組件,可以說MyBatis的所有操作都是圍繞SqlSession來展開的末誓。對SqlSession理解透徹,才能全面掌握MyBatis书蚪。

1. SqlSession初識

SqlSession在一開始就介紹過是高級接口,類似于JDBC操作的connection對象迅栅,它包裝了數(shù)據(jù)庫連接殊校,通過這個接口我們可以實現(xiàn)增刪改查,提交/回滾事物读存,關(guān)閉連接为流,獲取代理類等操作。SqlSession是個接口让簿,其默認(rèn)實現(xiàn)是DefaultSqlSession敬察。SqlSession是線程不安全的,每個線程都會有自己唯一的SqlSession尔当,不同線程間調(diào)用同一個SqlSession會出現(xiàn)問題莲祸,因此在使用完后需要close掉蹂安。


SqlSession的方法

2. SqlSession的創(chuàng)建

SqlSessionFactoryBuilder的build()方法使用建造者模式創(chuàng)建了SqlSessionFactory接口對象,SqlSessionFactory接口的默認(rèn)實現(xiàn)是DefaultSqlSessionFactory锐帜。SqlSessionFactory使用實例工廠模式來創(chuàng)建SqlSession對象田盈。SqlSession,SqlSessionFactory,SqlSessionFactoryBuilder的關(guān)系如下(圖畫得有點丑...):

類圖

DefaultSqlSessionFactory中openSession是有兩種方法一種是openSessionFromDataSource,另一種是openSessionFromConnection缴阎。這兩種是什么區(qū)別呢允瞧?從字面意義上將,一種是從數(shù)據(jù)源中獲取SqlSession對象蛮拔,一種是由已有連接獲取SqlSession述暂。SqlSession實際是對數(shù)據(jù)庫連接的一層包裝,數(shù)據(jù)庫連接是個珍貴的資源建炫,如果頻繁的創(chuàng)建銷毀將會影響吞吐量畦韭,因此使用數(shù)據(jù)庫連接池化技術(shù)就可以復(fù)用數(shù)據(jù)庫連接了。因此openSessionFromDataSource會從數(shù)據(jù)庫連接池中獲取一個連接踱卵,然后包裝成一個SqlSession對像廊驼。openSessionFromConnection則是直接包裝已有的連接并返回SqlSession對像。

openSessionFromDataSource 主要經(jīng)歷了以下幾步:

  1. 從獲取configuration中獲取Environment對象惋砂,Environment包含了數(shù)據(jù)庫配置
  2. 從Environment獲取DataSource數(shù)據(jù)源
  3. 從DataSource數(shù)據(jù)源中獲取Connection連接對象
  4. 從DataSource數(shù)據(jù)源中獲取TransactionFactory事物工廠
  5. 從TransactionFactory中創(chuàng)建事物Transaction對象
  6. 創(chuàng)建Executor對象
  7. 包裝configuration和Executor對象成DefaultSqlSession對象
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);
      final Executor executor = configuration.newExecutor(tx, execType);
      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();
    }
  }

  private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
    try {
      boolean autoCommit;
      try {
        autoCommit = connection.getAutoCommit();
      } catch (SQLException e) {
        // Failover to true, as most poor drivers
        // or databases won't support transactions
        autoCommit = true;
      }
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      final Transaction tx = transactionFactory.newTransaction(connection);
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

3. SqlSession的使用

SqlSession 獲取成功后妒挎,我們就可以使用其中的方法了,比如直接使用SqlSession發(fā)送sql語句西饵,或者通過mapper映射文件的方式來使用酝掩,在上兩篇文章中我們都是通過mapper映射文件來使用的,接下來就介紹第一種眷柔,直接使用SqlSession發(fā)送sql語句期虾。

public static void main(String[] args){
        try {
            // 1. 讀取配置
            InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
            // 2. 獲取SqlSessionFactory對象
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            // 3. 獲取SqlSession對象
            SqlSession sqlSession = sqlSessionFactory.openSession();
            // 4. 執(zhí)行sql
            TTestUser user = sqlSession.selectOne("com.example.demo.dao.TTestUserMapper.selectByPrimaryKey", 13L);
            log.info("user = [{}]", JSONUtil.toJsonStr(user));
            // 5. 關(guān)閉連接
            sqlSession.close();
            inputStream.close();
        } catch (Exception e){
            log.error("errMsg = [{}]", e.getMessage(), e);
        }

    }

其中com.example.demo.dao.TTestUserMapper.selectByPrimaryKey指定了TTestUserMapper中selectByPrimaryKey這個方法,在對應(yīng)的mapper/TTestUserMapper.xml我們定義了id一致的sql語句

  <select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
    select 
    <include refid="Base_Column_List" />
    from t_test_user
    where id = #{id,jdbcType=BIGINT}
  </select>

Mybatis會在一開始加載的時候?qū)⒚總€標(biāo)簽中的sql語句包裝成MappedStatement對象驯嘱,并以類全路徑名+方法名為key镶苞,MappedStatement為value緩存在內(nèi)存中。在執(zhí)行對應(yīng)的方法時鞠评,就會根據(jù)這個唯一路徑找到TTestUserMapper.xml這條sql語句并且執(zhí)行返回結(jié)果茂蚓。

4. SqlSession的執(zhí)行原理

4. 1 SqlSession的selectOne的執(zhí)行原理

SqlSession的selectOne代碼如下,其實是調(diào)用selectList()方法獲取第一條數(shù)據(jù)的剃幌。其中參數(shù)statement就是statement的id聋涨,parameter就是參數(shù)。

public <T> T selectOne(String statement, Object parameter) {
        List<T> list = this.selectList(statement, parameter);
        if (list.size() == 1) {
            return list.get(0);
        } else if (list.size() > 1) {
            throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
        } else {
            return null;
        }
    }

RowBounds 對象是分頁對象负乡,主要拼接sql中的start,limit條件牍白。并且可以看到兩個重要步驟:

  1. 從configuration的成員變量mappedStatements中獲取MappedStatement對象绣张。mappedStatements是Map<String, MappedStatement>類型的緩存結(jié)構(gòu)呻惕,其中key就是mapper接口全類名+方法名,MappedStatement就是對標(biāo)簽中配置的sql一個包裝
  2. 使用executor成員變量來執(zhí)行查詢并且指定結(jié)果處理器,并且返回結(jié)果笼沥。Executor也是mybatis的一個重要的組件印蓖。sql的執(zhí)行都是由Executor對象來操作的低矮。
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        List var5;
        try {
            MappedStatement ms = this.configuration.getMappedStatement(statement);
            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;
    }

MappedStatement對象的具體內(nèi)容和Executor對象的類型神汹,我們將在其它文章中詳述。

4. 2 SqlSession的通過mapper對象使用的執(zhí)行原理

在啟動流程那篇文章中仑性,我們大致了解了sqlSession.getMapper返回的其實是個代理類MapperProxy惶楼,然后調(diào)mapper接口的方法其實都是調(diào)用MapperProxy的invoke方法,進(jìn)而調(diào)用MapperMethod的execute方法诊杆。

public static void main(String[] args) {
       try {
           // 1. 讀取配置
           InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
           // 2. 創(chuàng)建SqlSessionFactory工廠
           SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
           // 3. 獲取sqlSession
           SqlSession sqlSession = sqlSessionFactory.openSession();
           // 4. 獲取Mapper
           TTestUserMapper userMapper = sqlSession.getMapper(TTestUserMapper.class);
           // 5. 執(zhí)行接口方法
           TTestUser userInfo = userMapper.selectByPrimaryKey(16L);
           System.out.println("userInfo = " + JSONUtil.toJsonStr(userInfo));
           // 6. 提交事物
           sqlSession.commit();
           // 7. 關(guān)閉資源
           sqlSession.close();
           inputStream.close();
       } catch (Exception e){
           log.error(e.getMessage(), e);
       }
   }

MapperMethod的execute方法中使用命令模式進(jìn)行增刪改查操作歼捐,其實也是調(diào)用了sqlSession的增刪改查方法。

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
          if (method.returnsOptional()
              && (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName()
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }

總結(jié)

在這篇文章中我們詳細(xì)介紹了SqlSession的作用晨汹,創(chuàng)建過程豹储,使用方法,以及執(zhí)行原理等淘这,對SqlSession已經(jīng)有了比較全面的了解剥扣。其中涉及到的Executor對象,MappedStatement對象铝穷,ResultHandler我們將在其它文章中講解钠怯。歡迎在評論區(qū)中討論指正,一起進(jìn)步曙聂。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末晦炊,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子宁脊,更是在濱河造成了極大的恐慌断国,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件榆苞,死亡現(xiàn)場離奇詭異稳衬,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)坐漏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進(jìn)店門宋彼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人仙畦,你說我怎么就攤上這事∫羯簦” “怎么了慨畸?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長衣式。 經(jīng)常有香客問我寸士,道長檐什,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任弱卡,我火速辦了婚禮乃正,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘婶博。我一直安慰自己瓮具,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布凡人。 她就那樣靜靜地躺著名党,像睡著了一般。 火紅的嫁衣襯著肌膚如雪挠轴。 梳的紋絲不亂的頭發(fā)上传睹,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天,我揣著相機(jī)與錄音岸晦,去河邊找鬼欧啤。 笑死,一個胖子當(dāng)著我的面吹牛启上,可吹牛的內(nèi)容都是我干的邢隧。 我是一名探鬼主播,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼碧绞,長吁一口氣:“原來是場噩夢啊……” “哼府框!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起讥邻,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤迫靖,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后兴使,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體系宜,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年发魄,在試婚紗的時候發(fā)現(xiàn)自己被綠了盹牧。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡励幼,死狀恐怖汰寓,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情苹粟,我是刑警寧澤有滑,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布,位于F島的核電站嵌削,受9級特大地震影響毛好,放射性物質(zhì)發(fā)生泄漏望艺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一肌访、第九天 我趴在偏房一處隱蔽的房頂上張望找默。 院中可真熱鬧,春花似錦吼驶、人聲如沸惩激。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽咧欣。三九已至,卻和暖如春轨帜,著一層夾襖步出監(jiān)牢的瞬間魄咕,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工蚌父, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留哮兰,地道東北人。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓苟弛,卻偏偏與公主長得像喝滞,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子膏秫,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,724評論 2 351

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