Mybatis SqlSession執(zhí)行過程說明

記錄是一種精神塔逃,是加深理解最好的方式之一弃揽。

最近看了下Mybatis的源碼讹蘑,了解了下SqlSession執(zhí)行Sql的過程蓄诽,在這里把他記下來
曹金桂 cao_jingui@163.com(如有遺漏之處還請指教)
時間:2016年10月5日14:50

SqlSession的delete/update/insert執(zhí)行過程

調(diào)用過程說明

  1. 用戶代碼獲取到SqlSession對象后(DefaultSqlSession)绽慈,調(diào)動SqlSession的insert/update/delete
public int update(String statement, Object parameter) {   
      try {      
            dirty = true;
            MappedStatement ms = configuration.getMappedStatement(statement); //獲取MappedStatement對象恨旱,此對象包含了對應(yīng)Mapper的所有配置信息
            return executor.update(ms, wrapCollection(parameter));   //調(diào)用Executor對象的update方法
      } catch (Exception e) {     
            throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);   
      } finally {      
            ErrorContext.instance().reset();   
      }
}

SqlSession中的Executor對象在Configuration中創(chuàng)建的

public Executor newExecutor(Transaction transaction, ExecutorType executorType) { 
      //確保ExecutorType不為空(defaultExecutorType有可能為空) 
      executorType = executorType == null ? defaultExecutorType : executorType;
      executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
      Executor executor;
      // 根據(jù)ExecutorType類別創(chuàng)建Executor對象
      if (ExecutorType.BATCH == executorType) {
            executor = new BatchExecutor(this, transaction);    
      } else if (ExecutorType.REUSE == executorType) {
            executor = new ReuseExecutor(this, transaction);    
      } else {
            executor = new SimpleExecutor(this, transaction);   
      } if (cacheEnabled) {
            executor = new CachingExecutor(executor);   
      }
      executor = (Executor) interceptorChain.pluginAll(executor); //調(diào)用插件攔截器
      return executor;
}
  1. 通過Executor執(zhí)行Sql操作(這里以SimpleExecutor為例)
    SqlSession的update/insert/delete操作會調(diào)用BaseExecutor的update方法
public int update(MappedStatement ms, Object parameter) throws SQLException { 
      ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());    
      if (closed) {
            throw new ExecutorException("Executor was closed.");    
      } 
      clearLocalCache(); //先清局部緩存,再更新坝疼,如何更新由子類實現(xiàn)搜贤,模板方法模式
      return  doUpdate(ms, parameter); //由子類實現(xiàn)
}

下面看下子類SimpleExecutor的doUpdate方法

public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
      Statement stmt = null;
      try {
            Configuration configuration = ms.getConfiguration();
            //獲得statementHandler        
            StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
            stmt = handler.prepare(getConnection(ms.getStatementLog()));  //獲取Statement對象
            handler.parameterize(stmt);//設(shè)置參數(shù)        
            return handler.update(stmt); //最終是一個statement進(jìn)行處理 
      } finally {
            closeStatement(stmt);    
      }
}
  1. 繼續(xù)看StatementHandler接口對象的創(chuàng)建過程
    StatementHandler對象是通過Configuration的newStatementHandler方法創(chuàng)建的
//創(chuàng)建Statement對象(**會調(diào)用過濾器**)
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {   
      StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
      // 這里會調(diào)用Mybatis的所有插件,返回代理對象(責(zé)任鏈模式)
      statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
      return statementHandler;
}

Configuration中創(chuàng)建的是RoutingStatementHandler對象钝凶,其實這個對象就是StatementHandler的代理對象(靜態(tài)代理)仪芒,創(chuàng)建過程如下:

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

以上根據(jù)StatementType創(chuàng)建不同的StatementHandler子類,所有RoutingStatementHandler的操作都會調(diào)用delegate對象來調(diào)用(<b>靜態(tài)代理模式</b>)耕陷。

  1. 以PreparedStatementHandler為例掂名,繼續(xù)看SimpleExecutor.doUpdate方法調(diào)用的實現(xiàn)
    先看StatementHandler.prepare()方法;這個方法會調(diào)用PreparedStatementHandler的父類BaseStatementHandler的prepare方法,父類方法會調(diào)用子類instantiateStatement的實現(xiàn)方法創(chuàng)建Statement對象哟沫,然后對生成的Statement對象做必要的設(shè)置
public Statement prepare(Connection connection) throws SQLException {
       ErrorContext.instance().sql(boundSql.getSql());
       Statement statement = null;
       try {
           statement = instantiateStatement(connection);// 子類實現(xiàn)方法獲取到Statument對象
           setStatementTimeout(statement); // 配置的設(shè)置
           setFetchSize(statement);
           return statement;
       } catch (Exception e) {
           closeStatement(statement);
           throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
       }
}

繼續(xù)看子類(PreparedStatementHandler)方法instantiateStatement怎么創(chuàng)建Statement對象

// 看這里的代碼就知道了饺蔑,就是java JDBC的操作
protected Statement instantiateStatement(Connection connection) throws SQLException {
       String sql = boundSql.getSql(); //獲取執(zhí)行的sql
       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() != null) {
           return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
       } else {
           return connection.prepareStatement(sql);
       }
}

以上就是通過PreparedStatementHandler對象獲取到了JDBC的Statement對象,那拿到Statement對象之后嗜诀,按照J(rèn)DBC的流程肯定就是設(shè)置sql執(zhí)行參數(shù)猾警,然后執(zhí)行。 我們回到SimpleExecutor.doUpdate方法隆敢,在獲取到Statement對象之后发皿,調(diào)用了StatementHandler的parameterize來設(shè)置對應(yīng)的參數(shù)

public void parameterize(Statement statement) throws SQLException {
       //這個方法就一句代碼,調(diào)用parameterHandler.setParameters方法實現(xiàn)
       parameterHandler.setParameters((PreparedStatement) statement);
}

我們繼續(xù)看下ParameterHandler的唯一實現(xiàn)類DefaultParameterHandler筑公,看下具體的setParameters方法實現(xiàn)

public void setParameters(PreparedStatement ps) throws SQLException {
       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)) {
                       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();
                   }
                   //ps.setXXX();設(shè)置參數(shù)值
                   typeHandler.setParameter(ps, i + 1, value, jdbcType);
               }
           }
      }
}

到此我們獲取到了Statement對象雳窟,執(zhí)行的參數(shù)值也設(shè)置好了,最后只要調(diào)用Statement的update方法即可執(zhí)行相應(yīng)的sql語句匣屡》饩龋看PreparedStatementHandler的update方法實現(xiàn),很簡單捣作,返回sql執(zhí)行受影響的行數(shù)誉结,如果有自增列則處理

public int update(Statement statement) throws SQLException {
       PreparedStatement ps = (PreparedStatement) statement;
       ps.execute(); //很簡單,調(diào)用JDBC代碼
       int rows = ps.getUpdateCount(); //獲取sql執(zhí)行受影響的行數(shù)
       Object parameterObject = boundSql.getParameterObject();
       KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
       keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
       return rows;
}
  1. 到此我們SqlSession對象的insert/update/delete的操作調(diào)用過程結(jié)束券躁。

小結(jié)

在SqlSession接口調(diào)用的insert/update/delete方法中惩坑,所有的操作都交給了Executor來操作掉盅。SqlSession接口是Mybatis框架暴露的外部接口,而Executor是內(nèi)部的實現(xiàn)接口以舒。在Executor的實現(xiàn)中趾痘,又是調(diào)用StatementHandler來處理的。當(dāng)然蔓钟,在調(diào)用StatementHandler設(shè)置參數(shù)時候永票,需要ParameterHandler來設(shè)置相應(yīng)的參數(shù),具體如下圖:



當(dāng)然滥沫,這里我們分析的是sql的insert/update/delete侣集,沒有分析select。所以沒有涉及到ResultSetHandler接口對結(jié)果集處理(后面文章繼續(xù))兰绣。

Mybatis四大接口對象(本篇涉及到三個):Executor StatementHandler ParameterHandler ResultSetHander

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末世分,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子缀辩,更是在濱河造成了極大的恐慌臭埋,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件臀玄,死亡現(xiàn)場離奇詭異斋泄,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)镐牺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來魁莉,“玉大人睬涧,你說我怎么就攤上這事∑煅洌” “怎么了畦浓?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長检疫。 經(jīng)常有香客問我讶请,道長,這世上最難降的妖魔是什么屎媳? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任夺溢,我火速辦了婚禮,結(jié)果婚禮上烛谊,老公的妹妹穿的比我還像新娘风响。我一直安慰自己,他們只是感情好丹禀,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布状勤。 她就那樣靜靜地躺著鞋怀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪持搜。 梳的紋絲不亂的頭發(fā)上密似,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天,我揣著相機(jī)與錄音葫盼,去河邊找鬼残腌。 笑死,一個胖子當(dāng)著我的面吹牛剪返,可吹牛的內(nèi)容都是我干的废累。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼脱盲,長吁一口氣:“原來是場噩夢啊……” “哼邑滨!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起钱反,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤掖看,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后面哥,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體哎壳,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年尚卫,在試婚紗的時候發(fā)現(xiàn)自己被綠了归榕。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡吱涉,死狀恐怖刹泄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情怎爵,我是刑警寧澤特石,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站鳖链,受9級特大地震影響姆蘸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜芙委,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一逞敷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧灌侣,春花似錦兰粉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽愕秫。三九已至,卻和暖如春焰络,著一層夾襖步出監(jiān)牢的瞬間戴甩,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工闪彼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留甜孤,地道東北人。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓畏腕,卻偏偏與公主長得像缴川,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子描馅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評論 2 348

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