MyBatis運行原理解析

SqlSession獲取

一切從new SqlSessionFactoryBuilder().build(inputStream)說起匙隔,build方法最終通過xml配置文件解析生成一個Configuration對象疑苫,注入到DefaultSqlSessionFactory對象中返回,在這個過程中,Configuration根據(jù)配置文件完成了以下配置:

properties
  主要是把<properties>標(biāo)簽下的屬性-值或者外部properties文件中的屬性-值加載進(jìn)來缀匕,供后面替換變量
settings
  加載常見的配置如defaultExecutorType
typeAliases
  配置類的別名
plugins
  配置插件
objectFactory
objectWrapperFactory
reflectorFactory
environments
  配置dataSource和transactionManager
databaseIdProvider
typeHandlers
mappers

接下來openSession方法默認(rèn)會根據(jù)配置的dataSource來創(chuàng)建:

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

可以看到纳决,生成了一個DefaultSqlSession對象

Executor

在生成DefaultSqlSession對象的過程中,傳入了一個Executor對象乡小,該對象是真正執(zhí)行查詢的對象阔加,我們看看MyBatis中都有哪些Executor類型

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor 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);
    return executor;
  }

這里的ExecutorType就是我們在配置文件中配置的,默認(rèn)為SIMPLE满钟,可以看出胜榔,一共有3種類型,包括SIMPLE湃番、BATCH夭织、REUSE,在返回executor之前吠撮,加入了攔截鏈尊惰,我們可以利用這個來編寫插件(參考作者其他文章),對各種類型的Executor說明可以查看官網(wǎng)或者自行閱讀源碼泥兰,這里以SIMPLE為例說明
<hr />

到目前為止弄屡,SqlSession對象就已經(jīng)準(zhǔn)備好了,我們可以直接用它來進(jìn)行查詢鞋诗,但是不推薦這么做膀捷,而是通過編寫Mapper接口的方式

Mapper的動態(tài)代理

比如我們編寫了一個Mapper接口DemoMapper并且正確在配置文件中進(jìn)行了相關(guān)的配置,那么我們執(zhí)行下面的語句:

DemoMapper demoMapper = sqlSession.getMapper(DemoMapper.class);

首先削彬,sqlSessiongetMapper方法委托給configurationgetMapper方法,最終委托給MapperRegistrygetMapper方法:

final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }

可以看到全庸,最終通過MapperProxyFactory對象來生成代理對象,MyBatis會在加載配置文件時為每個Mapper生成一個MapperProxyFactory

protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

可以看到融痛,MapperProxyFactorynewInstance方法利用JDBC的動態(tài)代理技術(shù)生成了一個T泛型對象(最終就是我們請求的DemoMapper.class

MapperProxy對象

此對象實現(xiàn)了InvocationHandler壶笼,攔截方法調(diào)用

@Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

可以看出,當(dāng)定義method的是一個接口的時候雁刷,會生成一個MapperMethod對象拌消,調(diào)用它的execute方法,截取execute方法片段:

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;

最終還是回到了sqlSession的方法調(diào)用上面安券,因此墩崩,我們通過編寫并配置Mapper來執(zhí)行查詢,而不是直接通過sqlSession

最終的實現(xiàn)--Executor的底層實現(xiàn)

以查詢?yōu)槔蠲悖罱K會跳轉(zhuǎn)到Executorquery方法鹦筹,以SimpleExecutor為例,就是doQuery方法

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();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

首先址貌,創(chuàng)建一個StatementHandler對象铐拐,然后預(yù)編譯SQL語句生成Statement對象徘键,最后執(zhí)行

預(yù)編譯的過程如下
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }

包括了兩個過程,prepare和parameterize

public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      statement = instantiateStatement(connection);
      setStatementTimeout(statement, transactionTimeout);
      setFetchSize(statement);
      return statement;
    } catch (SQLException e) {
      closeStatement(statement);
      throw e;
    } catch (Exception e) {
      closeStatement(statement);
      throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
  }

prepare過程主要是初始化和設(shè)置一些連接參數(shù)如超時時間遍蟋、最大返回行數(shù)

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

parameterize就非常簡單了吹害,委托給ParameterHandler設(shè)置每個占位符上面的參數(shù)和值,最后執(zhí)行query:

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

到這里什么都已經(jīng)準(zhǔn)備好了,所以執(zhí)行查詢變得非常簡單虚青,就是我們編寫JDBC時對PreparedStatement的一般操作它呀,然后通過ResultSetHandler來搜集返回結(jié)果

總結(jié)一下Executor的執(zhí)行流程:

  1. 新建StatementHandler對象對SQL進(jìn)行初始化和預(yù)編譯
  2. 利用ParameterHandler對象對預(yù)編譯的SQL填參數(shù)
  3. 利用PreparedStatement對象執(zhí)行查詢(JDBC
  4. 利用ResultSetHandler對象搜集結(jié)果返回

對于具體的Handler有哪些類型、怎么實現(xiàn)的棒厘,讀者可以自行查閱相關(guān)源碼或文檔纵穿,到這里,整個MyBatis執(zhí)行的過程已經(jīng)很清晰了

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末奢人,一起剝皮案震驚了整個濱河市谓媒,隨后出現(xiàn)的幾起案子人柿,更是在濱河造成了極大的恐慌磁餐,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,542評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件输拇,死亡現(xiàn)場離奇詭異支救,居然都是意外死亡抢野,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評論 3 385
  • 文/潘曉璐 我一進(jìn)店門搂妻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蒙保,“玉大人辕棚,你說我怎么就攤上這事欲主。” “怎么了逝嚎?”我有些...
    開封第一講書人閱讀 158,021評論 0 348
  • 文/不壞的土叔 我叫張陵扁瓢,是天一觀的道長。 經(jīng)常有香客問我补君,道長引几,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,682評論 1 284
  • 正文 為了忘掉前任挽铁,我火速辦了婚禮伟桅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘叽掘。我一直安慰自己楣铁,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,792評論 6 386
  • 文/花漫 我一把揭開白布更扁。 她就那樣靜靜地躺著盖腕,像睡著了一般赫冬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上溃列,一...
    開封第一講書人閱讀 49,985評論 1 291
  • 那天劲厌,我揣著相機與錄音,去河邊找鬼听隐。 笑死补鼻,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的遵绰。 我是一名探鬼主播辽幌,決...
    沈念sama閱讀 39,107評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼椿访!你這毒婦竟也來了乌企?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,845評論 0 268
  • 序言:老撾萬榮一對情侶失蹤成玫,失蹤者是張志新(化名)和其女友劉穎加酵,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體哭当,經(jīng)...
    沈念sama閱讀 44,299評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡猪腕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,612評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了钦勘。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片陋葡。...
    茶點故事閱讀 38,747評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖彻采,靈堂內(nèi)的尸體忽然破棺而出腐缤,到底是詐尸還是另有隱情,我是刑警寧澤肛响,帶...
    沈念sama閱讀 34,441評論 4 333
  • 正文 年R本政府宣布岭粤,位于F島的核電站,受9級特大地震影響特笋,放射性物質(zhì)發(fā)生泄漏剃浇。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,072評論 3 317
  • 文/蒙蒙 一猎物、第九天 我趴在偏房一處隱蔽的房頂上張望虎囚。 院中可真熱鬧,春花似錦蔫磨、人聲如沸淘讥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽适揉。三九已至留攒,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間嫉嘀,已是汗流浹背炼邀。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留剪侮,地道東北人拭宁。 一個月前我還...
    沈念sama閱讀 46,545評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像瓣俯,于是被迫代替她去往敵國和親杰标。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,658評論 2 350

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