Mybatis源碼學(xué)習(xí)記錄(ParameterHandler篇)

前言

在上一篇文章中我們分析了StatementHandler的源碼,本文我們會(huì)分析Mybatis中四大接口之一的ParameterHandler的源碼部分

源碼

ParameterHandler.java

參數(shù)處理器符糊,用來(lái)給PreparedStatement設(shè)置參數(shù)的

接口定義

public interface ParameterHandler {
  // 獲取當(dāng)前執(zhí)行實(shí)際傳入的參數(shù)
  Object getParameterObject();
  
  // 給PreparedStatement設(shè)置參數(shù)
  void setParameters(PreparedStatement ps)
      throws SQLException;
}

接口定義好了凫海,活就需要有人來(lái)做了,下面看看ParameterHandler接口有哪些實(shí)現(xiàn)

DefaultParameterHandler.java

Mybatis僅僅提供了一個(gè)實(shí)現(xiàn)類男娄,我們直接看其源碼

DefaultParameterHandler.png

看其實(shí)現(xiàn)類的源碼之前行贪,我們先設(shè)想一下,如果是讓你自己來(lái)實(shí)現(xiàn)這個(gè)接口模闲,你的實(shí)現(xiàn)中應(yīng)該有哪些屬性呢? 確定實(shí)現(xiàn)有哪些屬性之前我們先仔細(xì)分析一下接口中定義的方法建瘫,接口方法只有兩個(gè),一個(gè)獲取當(dāng)前實(shí)際執(zhí)行的參數(shù)對(duì)象尸折,注意是這種形式的({{"phone", "15800000000"}, {"param1", "15800000000"}}

所以實(shí)現(xiàn)中必然需要有一個(gè)屬性暖混,類似下面所示

// 當(dāng)前實(shí)際執(zhí)行前的參數(shù)對(duì)象
private final Object parameterObject;

除此之外的另一個(gè)接口方法是為PreparedStatement對(duì)象設(shè)置動(dòng)態(tài)運(yùn)行參數(shù),既然是設(shè)置參數(shù)翁授,我就需要拿到整個(gè)MappedStatement對(duì)象以及BoundSql對(duì)象,通過(guò)BoundSql我就可以得到帶有?號(hào)的sql語(yǔ)句晾咪,當(dāng)前傳遞的參數(shù)以及整個(gè)sql語(yǔ)句的參數(shù)映射關(guān)系List<ParameterMapping>收擦,(這個(gè)映射關(guān)系尤為重要,可以說(shuō)是ParameterHandler實(shí)現(xiàn)的關(guān)鍵)谍倦,通過(guò)參數(shù)映射關(guān)系我就可以準(zhǔn)確的找到哪個(gè)參數(shù)對(duì)應(yīng)哪個(gè)TypeHandler塞赂,以及該參數(shù)是入?yún)?/code>還是出參類型,其javaType是什么昼蛀,jdbcType是什么等等信息

ok宴猾,分析到這里基本上差不多了,我們還是直接看源碼吧

public class DefaultParameterHandler implements ParameterHandler {
  // 屬性
  // 持有typeHandler注冊(cè)器
  private final TypeHandlerRegistry typeHandlerRegistry;

  // 持有MappedStatement實(shí)例叼旋,這是一個(gè)靜態(tài)的xml的一個(gè)數(shù)據(jù)庫(kù)操作節(jié)點(diǎn)的靜態(tài)信息而已
  private final MappedStatement mappedStatement;
  
  // 持有當(dāng)前操作傳入的實(shí)際參數(shù)
  private final Object parameterObject;

  // 動(dòng)態(tài)語(yǔ)言被執(zhí)行后的結(jié)果sql
  private final BoundSql boundSql;
  private final Configuration configuration;
  
  // 構(gòu)造函數(shù)
  public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    this.mappedStatement = mappedStatement;
    this.configuration = mappedStatement.getConfiguration();
    this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
    this.parameterObject = parameterObject;
    this.boundSql = boundSql;
  }
  
  // 實(shí)現(xiàn)方法
  @Override
  public Object getParameterObject() {
    return parameterObject;
  }
  
  @Override
  public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    // 1. 獲取boundSql中的參數(shù)映射信息列表
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      // 1.1. 遍歷參數(shù)映射列表仇哆,這個(gè)列表信息就是我們xml文件中定義的某個(gè)查詢語(yǔ)句的所有參數(shù)映射信息,注意這個(gè)List中的參數(shù)映射元素的順序是和真實(shí)xml中sql的參數(shù)順序?qū)?yīng)的
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        // 1.2. 只有入?yún)㈩愋筒艜?huì)設(shè)置PreparedStatement
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          // 取出參數(shù)名夫植,這里比如說(shuō)是'phone'
          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 {
            // 1.3. 這一步的工作就是從當(dāng)前實(shí)際傳入的參數(shù)中獲取到指定key('phone')的value值讹剔,比如是'15800000000'
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          
          // 2. 獲取該參數(shù)對(duì)應(yīng)的typeHandler
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          
          // 2.1. 獲取該參數(shù)對(duì)應(yīng)的jdbcType
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
            // 3. 重點(diǎn)是調(diào)用每個(gè)參數(shù)對(duì)應(yīng)的typeHandler的setParameter方法為該ps設(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);
          }
        }
      }
    }
  }
}

這里截取一個(gè)運(yùn)行時(shí)的debug信息看一下就知道了


debug.png

至此我們可以知道,每一次的調(diào)用查詢(數(shù)據(jù)庫(kù)查詢详民,不走M(jìn)ybatis緩存)延欠,Executor在執(zhí)行doQuery的時(shí)候都會(huì)創(chuàng)建一個(gè)StatementHandler實(shí)例,每個(gè)StatementHandler在實(shí)例化的時(shí)候沈跨,都會(huì)創(chuàng)建并持有兩個(gè)處理器即ParameterHandlerResultSetHandler

這樣statementHandler就可以利用ParameterHandler完成預(yù)處理語(yǔ)句的參數(shù)化設(shè)置由捎,以及結(jié)果查詢出來(lái)以后再利用ResultSetHandler處理結(jié)果集

這里我們?cè)俅钨N一下BaseStatementHandler的構(gòu)造函數(shù)部分源碼

// BaseStatementHandler.java
// ...

protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    // 省略其他屬性設(shè)置...
    
    // 1. parameterHandler
    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    
    // 2. resultSetHandler
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
  }

我們看下parameterHandler的創(chuàng)建過(guò)程,可以看到是利用了configuration

// Configuration.java
// ...

// new一個(gè)參數(shù)化處理器
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    // 1. 內(nèi)部就是調(diào)用了DefaultParameterHandler的構(gòu)造方法饿凛,new了一個(gè)實(shí)例返回
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    // 2. 看到這里狞玛,又是Mybatis的插件软驰,提供給開發(fā)人員攔截ParameterHandler的調(diào)用邏輯
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }

代碼分析至此,我們簡(jiǎn)單總結(jié)一下

  1. 當(dāng)真實(shí)的數(shù)據(jù)庫(kù)查詢被執(zhí)行時(shí)为居,SimpleExecutor執(zhí)行器會(huì)通過(guò)configuration對(duì)象new出一個(gè)StatementHandler出來(lái)碌宴,后續(xù)的所有操作都是利用這個(gè)statementHandler來(lái)完成,這個(gè)handler實(shí)際上是返回了經(jīng)過(guò)開發(fā)人員自定義插件的代理類蒙畴,可以基于此處自定義插件

  2. 當(dāng)statementHandler實(shí)例有了以后贰镣,我們需要?jiǎng)?chuàng)建出真實(shí)的JDBC Statement實(shí)例,此處調(diào)用了statementHandler的prepare方法(此處可自定義插件攔截哦)實(shí)現(xiàn)膳凝,根據(jù)handler的類型不同則會(huì)創(chuàng)建不同的JDBC Statement實(shí)例返回碑隆,這里簡(jiǎn)單的提一下,如果是ReuseExecutor不會(huì)為同一個(gè)BoundSql中的sql語(yǔ)句創(chuàng)建多個(gè)prepareStatement返回蹬音,而是在內(nèi)部維護(hù)一個(gè)Map<String, Statement>來(lái)重用Statement實(shí)例

  3. 當(dāng)statement實(shí)例返回時(shí)上煤,就會(huì)進(jìn)行參數(shù)化設(shè)置(如果是一個(gè)Statement實(shí)例而非PrepareStatement實(shí)例,參數(shù)化設(shè)置方法的內(nèi)部就不作任何處理)著淆,調(diào)用statementHandler的parameterize方法完成劫狠,由于我們每個(gè)statementHandler都持有兩個(gè)處理器即ParameterHandlerResultSetHandler,這個(gè)時(shí)候就是ParameterHandler上場(chǎng)的時(shí)候了永部,PrepareStatementHandler類型的handler在參數(shù)化方法內(nèi)部就是委托給了ParameterHandler独泞,調(diào)用了其setParameters方法進(jìn)行參數(shù)化設(shè)置,而setParameters方法的核心實(shí)現(xiàn)就是拿到當(dāng)前調(diào)用的boundSql實(shí)例中的parameterMappings集合和parameterObject實(shí)例苔埋,通過(guò)遍歷這個(gè)集合懦砂,對(duì)所有入?yún)㈩愋?ParameterMode.IN)的參數(shù)進(jìn)行逐個(gè)獲取其參數(shù)對(duì)應(yīng)TypeHandler,再利用TypeHandlersetParameter方法對(duì)當(dāng)前的preparedStatement實(shí)例進(jìn)行參數(shù)化設(shè)置组橄,而設(shè)置需要的參數(shù)值就是從parameterObject中獲取的

  4. 當(dāng)參數(shù)設(shè)置完成以后荞膘,statementHandler又拿到了調(diào)用權(quán),開始調(diào)用query/update(Statement)方法執(zhí)行數(shù)據(jù)庫(kù)操作玉工,不同的handler則有不同的實(shí)現(xiàn)羽资,比如SimpleStatementHandler就是調(diào)用的Statement.execute(sql)執(zhí)行,而PreparedStatementHandler則是直接ps.execute()執(zhí)行即可

  5. 數(shù)據(jù)庫(kù)執(zhí)行完成后瓮栗,就拿到了結(jié)果集削罩,這個(gè)時(shí)候就是ResultSetHandler發(fā)揮的時(shí)候了,下文我們會(huì)繼續(xù)分析ResultSetHandler的源碼

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末费奸,一起剝皮案震驚了整個(gè)濱河市弥激,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌愿阐,老刑警劉巖微服,帶你破解...
    沈念sama閱讀 216,692評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異缨历,居然都是意外死亡以蕴,警方通過(guò)查閱死者的電腦和手機(jī)糙麦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)丛肮,“玉大人赡磅,你說(shuō)我怎么就攤上這事”τ耄” “怎么了焚廊?”我有些...
    開封第一講書人閱讀 162,995評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)习劫。 經(jīng)常有香客問(wèn)我咆瘟,道長(zhǎng),這世上最難降的妖魔是什么诽里? 我笑而不...
    開封第一講書人閱讀 58,223評(píng)論 1 292
  • 正文 為了忘掉前任袒餐,我火速辦了婚禮,結(jié)果婚禮上谤狡,老公的妹妹穿的比我還像新娘灸眼。我一直安慰自己,他們只是感情好墓懂,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評(píng)論 6 388
  • 文/花漫 我一把揭開白布幢炸。 她就那樣靜靜地躺著,像睡著了一般拒贱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上佛嬉,一...
    開封第一講書人閱讀 51,208評(píng)論 1 299
  • 那天逻澳,我揣著相機(jī)與錄音,去河邊找鬼暖呕。 笑死斜做,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的湾揽。 我是一名探鬼主播瓤逼,決...
    沈念sama閱讀 40,091評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼库物!你這毒婦竟也來(lái)了霸旗?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,929評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤戚揭,失蹤者是張志新(化名)和其女友劉穎诱告,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體民晒,經(jīng)...
    沈念sama閱讀 45,346評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡精居,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評(píng)論 2 333
  • 正文 我和宋清朗相戀三年锄禽,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片靴姿。...
    茶點(diǎn)故事閱讀 39,739評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡沃但,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出佛吓,到底是詐尸還是另有隱情宵晚,我是刑警寧澤,帶...
    沈念sama閱讀 35,437評(píng)論 5 344
  • 正文 年R本政府宣布辈毯,位于F島的核電站坝疼,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏谆沃。R本人自食惡果不足惜钝凶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望唁影。 院中可真熱鬧耕陷,春花似錦、人聲如沸据沈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)锌介。三九已至嗜诀,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間孔祸,已是汗流浹背隆敢。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留崔慧,地道東北人拂蝎。 一個(gè)月前我還...
    沈念sama閱讀 47,760評(píng)論 2 369
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像惶室,于是被迫代替她去往敵國(guó)和親温自。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評(píng)論 2 354

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