<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          MyBatis 的工作原理,你了解過嗎?

          共 13371字,需瀏覽 27分鐘

           ·

          2020-10-31 01:08

          來源:cnblogs.com/scuury/p/10371246.html

          • 第一步:創(chuàng)建一個sqlSessionFactory
          • 第二步:創(chuàng)建sqlSession
          • 第三步:執(zhí)行具體的sql請求
          • 總結(jié)
          • 參考


          近來想寫一個mybatis的分頁插件,但是在寫插件之前肯定要了解一下mybatis具體的工作原理吧,于是邊參考別人的博客,邊看源碼就開干了。

          核心部件:

          • SqlSession
          • Executor
          • StatementHandler
          • ParameterHandler
          • ResultSetHandler
          • TypeHandler
          • MappedStatement
          • Configuration

          在分析工作原理之前,首先看一下我的mybatis全局配置文件

          "1.0"?encoding="UTF-8"??>
          PUBLIC?"-//mybatis.org//DTD?Config?3.0//EN"
          "http://mybatis.org/dtd/mybatis-3-config.dtd">

          ????
          ????default="development">
          ????????"development">
          ????????????
          ????????????"JDBC"?/>
          ????????????
          ????????????"POOLED">
          ????????????????"driver"?value="com.mysql.jdbc.Driver"?/>
          ????????????????"url"
          ????????????????????value="jdbc:mysql://localhost:3306/test?characterEncoding=utf-8"?/>
          ????????????????"username"?value="root"?/>
          ????????????????"password"?value="123456"?/>
          ????????????
          ????????
          ????
          ????
          ???????"sqlMapper/userMapper.xml"/>
          ????


          第一步:創(chuàng)建一個sqlSessionFactory

          在了解如何創(chuàng)建sqlSessionFactory之前,先看一下mybatis是如何加載全局配置文件,解析xml文件生成Configuration的

          public?Configuration?parse()?{
          ????if?(parsed)?{
          ??????throw?new?BuilderException("Each?XMLConfigBuilder?can?only?be?used?once.");
          ????}
          ????parsed?=?true;
          ????parseConfiguration(parser.evalNode("/configuration"));
          ????return?configuration;
          ??}
          private?void?parseConfiguration(XNode?root)?{
          ????try?{
          ??????propertiesElement(root.evalNode("properties"));?//issue?#117?read?properties?first
          ??????typeAliasesElement(root.evalNode("typeAliases"));
          ??????pluginElement(root.evalNode("plugins"));
          ??????objectFactoryElement(root.evalNode("objectFactory"));
          ??????objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
          ??????settingsElement(root.evalNode("settings"));
          ??????environmentsElement(root.evalNode("environments"));?//?read?it?after?objectFactory?and?objectWrapperFactory?issue?#631
          ??????databaseIdProviderElement(root.evalNode("databaseIdProvider"));
          ??????typeHandlerElement(root.evalNode("typeHandlers"));
          ??????mapperElement(root.evalNode("mappers"));
          ????}?catch?(Exception?e)?{
          ??????throw?new?BuilderException("Error?parsing?SQL?Mapper?Configuration.?Cause:?"?+?e,?e);
          ????}
          ??}

          在上面的第二段代碼中有一句

          mapperElement(root.evalNode("mappers"));

          剛好我們的全局配置文件中有一個mapper的配置,由此可見,mapperElemet()方法是解析mapper映射文件的,具體代碼如下

          private?void?mapperElement(XNode?parent)?throws?Exception?{
          ????if?(parent?!=?null)?{
          ??????for?(XNode?child?:?parent.getChildren())?{
          ????????if?("package".equals(child.getName()))?{
          ??????????String?mapperPackage?=?child.getStringAttribute("name");
          ??????????configuration.addMappers(mapperPackage);
          ????????}?else?{
          ??????????String?resource?=?child.getStringAttribute("resource");
          ??????????String?url?=?child.getStringAttribute("url");
          ??????????String?mapperClass?=?child.getStringAttribute("class");
          ??????????if?(resource?!=?null?&&?url?==?null?&&?mapperClass?==?null)?{//進(jìn)入該判斷
          ????????????ErrorContext.instance().resource(resource);
          ????????????InputStream?inputStream?=?Resources.getResourceAsStream(resource);
          ????????????XMLMapperBuilder?mapperParser?=?new?XMLMapperBuilder(inputStream,?configuration,?resource,?configuration.getSqlFragments());
          ????????????mapperParser.parse();
          ??????????}?else?if?(resource?==?null?&&?url?!=?null?&&?mapperClass?==?null)?{
          ????????????ErrorContext.instance().resource(url);
          ????????????InputStream?inputStream?=?Resources.getUrlAsStream(url);
          ????????????XMLMapperBuilder?mapperParser?=?new?XMLMapperBuilder(inputStream,?configuration,?url,?configuration.getSqlFragments());
          ????????????mapperParser.parse();
          ??????????}?else?if?(resource?==?null?&&?url?==?null?&&?mapperClass?!=?null)?{
          ????????????Class?mapperInterface?=?Resources.classForName(mapperClass);
          ????????????configuration.addMapper(mapperInterface);
          ??????????}?else?{
          ????????????throw?new?BuilderException("A?mapper?element?may?only?specify?a?url,?resource?or?class,?but?not?more?than?one.");
          ??????????}
          ????????}
          ??????}
          ????}
          ??}

          根據(jù)以上代碼可以分析,在寫mapper映射文件的地址時不僅可以寫成resource,還可以寫成url和mapperClass的形式,由于我們用的是resource,所以直接進(jìn)入第一個判斷,最后解析mapper映射文件的方法是

          private?void?configurationElement(XNode?context)?{
          ????try?{
          ??????String?namespace?=?context.getStringAttribute("namespace");
          ??????if?(namespace.equals(""))?{
          ??????????throw?new?BuilderException("Mapper's?namespace?cannot?be?empty");
          ??????}
          ??????builderAssistant.setCurrentNamespace(namespace);
          ??????cacheRefElement(context.evalNode("cache-ref"));
          ??????cacheElement(context.evalNode("cache"));
          ??????parameterMapElement(context.evalNodes("/mapper/parameterMap"));
          ??????resultMapElements(context.evalNodes("/mapper/resultMap"));
          ??????sqlElement(context.evalNodes("/mapper/sql"));
          ??????buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
          ????}?catch?(Exception?e)?{
          ??????throw?new?BuilderException("Error?parsing?Mapper?XML.?Cause:?"?+?e,?e);
          ????}
          ??}

          其中具體解析每一個sql語句節(jié)點的是

          buildStatementFromContext(context.evalNodes("select|insert|update|delete"));

          進(jìn)入這個方法一層層深究,最后到這里可以知道MappedStatement是由builderAssistant(即MapperBuildAssistant)創(chuàng)建的。

          public?void?parseStatementNode()?{
          ????...
          ????builderAssistant.addMappedStatement(id,?sqlSource,?statementType,?sqlCommandType,
          ????????fetchSize,?timeout,?parameterMap,?parameterTypeClass,?resultMap,?resultTypeClass,
          ????????resultSetTypeEnum,?flushCache,?useCache,?resultOrdered,
          ????????keyGenerator,?keyProperty,?keyColumn,?databaseId,?langDriver,?resultSets);
          ??}

          最后進(jìn)入方法addMappedStatement(),mappedStatement最后以id為鍵保存在了Configuration中的一個map變量mappedStatements中。

          public?MappedStatement?addMappedStatement(
          ??????String?id,
          ??????SqlSource?sqlSource,
          ??????StatementType?statementType,
          ??????SqlCommandType?sqlCommandType,
          ??????Integer?fetchSize,
          ??????Integer?timeout,
          ??????String?parameterMap,
          ??????Class?parameterType,
          ??????String?resultMap,
          ??????Class?resultType,
          ??????ResultSetType?resultSetType,
          ??????boolean?flushCache,
          ??????boolean?useCache,
          ??????boolean?resultOrdered,
          ??????KeyGenerator?keyGenerator,
          ??????String?keyProperty,
          ??????String?keyColumn,
          ??????String?databaseId,
          ??????LanguageDriver?lang,
          ??????String?resultSets)
          ?
          {

          ????if?(unresolvedCacheRef)?throw?new?IncompleteElementException("Cache-ref?not?yet?resolved");

          ????id?=?applyCurrentNamespace(id,?false);
          ????boolean?isSelect?=?sqlCommandType?==?SqlCommandType.SELECT;

          ????MappedStatement.Builder?statementBuilder?=?new?MappedStatement.Builder(configuration,?id,?sqlSource,?sqlCommandType);
          ????statementBuilder.resource(resource);
          ????statementBuilder.fetchSize(fetchSize);
          ????statementBuilder.statementType(statementType);
          ????statementBuilder.keyGenerator(keyGenerator);
          ????statementBuilder.keyProperty(keyProperty);
          ????statementBuilder.keyColumn(keyColumn);
          ????statementBuilder.databaseId(databaseId);
          ????statementBuilder.lang(lang);
          ????statementBuilder.resultOrdered(resultOrdered);
          ????statementBuilder.resulSets(resultSets);
          ????setStatementTimeout(timeout,?statementBuilder);

          ????setStatementParameterMap(parameterMap,?parameterType,?statementBuilder);
          ????setStatementResultMap(resultMap,?resultType,?resultSetType,?statementBuilder);
          ????setStatementCache(isSelect,?flushCache,?useCache,?currentCache,?statementBuilder);

          ????MappedStatement?statement?=?statementBuilder.build();
          ????configuration.addMappedStatement(statement);
          ????return?statement;
          ??}

          最后回到我們的創(chuàng)建sqlSessionFactory上,之前的一切都是為了生成一個sqlSessionFactory服務(wù)的

          public?SqlSessionFactory?build(InputStream?inputStream,?String?environment,?Properties?properties)?{
          ????try?{
          ??????XMLConfigBuilder?parser?=?new?XMLConfigBuilder(inputStream,?environment,?properties);
          ??????return?build(parser.parse());
          ????}?catch?(Exception?e)?{
          ??????throw?ExceptionFactory.wrapException("Error?building?SqlSession.",?e);
          ????}?finally?{
          ??????ErrorContext.instance().reset();
          ??????try?{
          ????????inputStream.close();
          ??????}?catch?(IOException?e)?{
          ????????//?Intentionally?ignore.?Prefer?previous?error.
          ??????}
          ????}
          ??}

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

          從上面的代碼可以看出最后是通過以Configuration為參數(shù)build()方法生成DefautSqlSessionFactory。

          第二步:創(chuàng)建sqlSession

          ?public?SqlSession?openSession()?{
          ????return?openSessionFromDataSource(configuration.getDefaultExecutorType(),?null,?false);
          ??}
          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();
          ????}
          ??}
          //返回一個SqlSession,默認(rèn)使用DefaultSqlSession
          ?public?DefaultSqlSession(Configuration?configuration,?Executor?executor,?boolean?autoCommit)?{
          ????this.configuration?=?configuration;
          ????this.executor?=?executor;
          ????this.dirty?=?false;
          ????this.autoCommit?=?autoCommit;
          ??}

          executor在這一步得到創(chuàng)建,具體的使用在下一步。

          第三步:執(zhí)行具體的sql請求

          在我的代碼里執(zhí)行的是

          User?user?=?sqlSession.selectOne("test.findUserById",?1);

          具體到里面的方法就是

          public??List?selectList(String?statement,?Object?parameter,?RowBounds?rowBounds)?{
          ????try?{
          ?????//1.根據(jù)Statement?Id,在mybatis?配置對象Configuration中查找和配置文件相對應(yīng)的MappedStatement
          ??????MappedStatement?ms?=?configuration.getMappedStatement(statement);
          ??????//2.?將查詢?nèi)蝿?wù)委托給MyBatis?的執(zhí)行器?Executor
          ??????List?result?=?executor.query(ms,?wrapCollection(parameter),?rowBounds,?Executor.NO_RESULT_HANDLER);
          ??????return?result;
          ????}?catch?(Exception?e)?{
          ??????throw?ExceptionFactory.wrapException("Error?querying?database.??Cause:?"?+?e,?e);
          ????}?finally?{
          ??????ErrorContext.instance().reset();
          ????}
          ??}

          在這里通過statementId拿到了我們在第一步存在map里面的MappedStatement。在這里引用參考博客的一句話:

          SqlSession根據(jù)Statement ID, 在mybatis配置對象Configuration中獲取到對應(yīng)的MappedStatement對象,然后調(diào)用mybatis執(zhí)行器來執(zhí)行具體的操作。

          再繼續(xù)看query()和queryFromDatabase()這兩個方法

          @SuppressWarnings("unchecked")
          ??public??List?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?list;
          ????try?{
          ??????queryStack++;
          ??????list?=?resultHandler?==?null???(List)?localCache.getObject(key)?:?null;
          ??????if?(list?!=?null)?{
          ????????handleLocallyCachedOutputParameters(ms,?key,?parameter,?boundSql);
          ??????}?else?{
          ????????list?=?queryFromDatabase(ms,?parameter,?rowBounds,?resultHandler,?key,?boundSql);
          ??????}
          ????}?finally?{
          ??????queryStack--;
          ????}
          ????if?(queryStack?==?0)?{
          ??????for?(DeferredLoad?deferredLoad?:?deferredLoads)?{
          ????????deferredLoad.load();
          ??????}
          ??????deferredLoads.clear();?//?issue?#601
          ??????if?(configuration.getLocalCacheScope()?==?LocalCacheScope.STATEMENT)?{
          ????????clearLocalCache();?//?issue?#482
          ??????}
          ????}
          ????return?list;
          ??}
          private??List?queryFromDatabase(MappedStatement?ms,?Object?parameter,?RowBounds?rowBounds,?ResultHandler?resultHandler,?CacheKey?key,?BoundSql?boundSql)?throws?SQLException?{
          ????List?list;
          ????localCache.putObject(key,?EXECUTION_PLACEHOLDER);
          ????try?{
          ??????list?=?doQuery(ms,?parameter,?rowBounds,?resultHandler,?boundSql);
          ????}?finally?{
          ??????localCache.removeObject(key);
          ????}
          ????localCache.putObject(key,?list);
          ????if?(ms.getStatementType()?==?StatementType.CALLABLE)?{
          ??????localOutputParameterCache.putObject(key,?parameter);
          ????}
          ????return?list;
          ??}

          在這兩個方法里面會為當(dāng)前的查詢創(chuàng)建一個緩存key,如果緩存中沒有值,直接從數(shù)據(jù)庫中讀取,執(zhí)行查詢后將得到的list結(jié)果放入緩存之中。

          緊接著看doQuery()在SimpleExecutor類中重寫的方法

          public??List?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.query(stmt,?resultHandler);
          ????}?finally?{
          ??????closeStatement(stmt);
          ????}
          ??}

          Statement連接對象就是在這里創(chuàng)建的,因此Executor的作用之一就是創(chuàng)建Statement了,創(chuàng)建完后又把Statement丟給StatementHandler返回List查詢結(jié)果。

          接下來再看一下這里的兩個方法prepareStatement()和query()的具體實現(xiàn)

          private?Statement?prepareStatement(StatementHandler?handler,?Log?statementLog)?throws?SQLException?{
          ????Statement?stmt;
          ????Connection?connection?=?getConnection(statementLog);
          ????stmt?=?handler.prepare(connection);
          ????handler.parameterize(stmt);
          ????return?stmt;
          ??}

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

          prepareStatement()是創(chuàng)建Statement的具體實現(xiàn)方法,調(diào)用parameterize()對創(chuàng)建的Statement對象設(shè)置參數(shù),即為我們設(shè)為占位符的地方賦上指定的參數(shù),parameterize()方法再深入進(jìn)去就是調(diào)用ParameterHandler的setParameters()方法具體賦值了。

          這里的query()是調(diào)用了ResultSetHandler的handleResultSets(Statement) 方法。作用就是把ResultSet結(jié)果集對象轉(zhuǎn)換成List類型的集合。

          總結(jié)以上步驟就是:

          1. 根據(jù)具體傳入的參數(shù),動態(tài)地生成需要執(zhí)行的SQL語句,用BoundSql對象表示
          2. 為當(dāng)前的查詢創(chuàng)建一個緩存Key
          3. 緩存中沒有值,直接從數(shù)據(jù)庫中讀取數(shù)據(jù)
          4. 執(zhí)行查詢,返回List 結(jié)果,然后 將查詢的結(jié)果放入緩存之中
          5. 根據(jù)既有的參數(shù),創(chuàng)建StatementHandler對象來執(zhí)行查詢操作
          6. 將創(chuàng)建Statement傳遞給StatementHandler對象,調(diào)用parameterize()方法賦值
          7. 調(diào)用StatementHandler.query()方法,返回List結(jié)果集

          總結(jié)

          以上三個步驟所有流程大體可以用一張圖來總結(jié)

          (END)

          更多精彩:
          有了這款可視化工具,Java 應(yīng)用性能調(diào)優(yōu)超簡單!
          10k+點贊的 SpringBoot 后臺管理系統(tǒng)竟然出了詳細(xì)教程!
          程序員 1024 刷屏漫畫,非常有趣!
          求求你別再用System.out.println 了,有更好的選擇!
          使用IntelliJ IDEA查看類圖,內(nèi)容極度舒適
          關(guān)注公眾號,查看更多優(yōu)質(zhì)文章

          最近,整理一份Java資料Java從0到1,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫、數(shù)據(jù)結(jié)構(gòu)等等。
          獲取方式:關(guān)注公眾號并回復(fù)?Java?領(lǐng)取,更多Java內(nèi)容陸續(xù)奉上。
          明天見(??ω??)??

          瀏覽 54
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美操逼高清 | 欧美黄色一级视频 | 精品51日韩 | 老司机日比视频 | 我爱大香蕉欧美高清无 |