中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

每天用Mybatis,但是Mybatis的工作原理你真的知道嗎?

發布時間:2020-06-03 22:34:21 來源:網絡 閱讀:335 作者:架構師追風 欄目:編程語言

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

核心部件:

  • SqlSession

  • Executor

  • StatementHandler

  • ParameterHandler

  • ResultSetHandler

  • TypeHandler

  • MappedStatement

  • Configuration

每天用Mybatis,但是Mybatis的工作原理你真的知道嗎?

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

<?xml?version="1.0"?encoding="UTF-8"??>
<!DOCTYPE?configuration
PUBLIC?"-//mybatis.org//DTD?Config?3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
????<!--?和spring整合后?environments配置將廢除?-->
????<environments?default="development">
????????<environment?id="development">
????????????<!--?使用jdbc事務管理?-->
????????????<transactionManager?type="JDBC"?/>
????????????<!--?數據庫連接池?-->
????????????<dataSource?type="POOLED">
????????????????<property?name="driver"?value="com.mysql.jdbc.Driver"?/>
????????????????<property?name="url"
????????????????????value="jdbc:mysql://localhost:3306/test?characterEncoding=utf-8"?/>
????????????????<property?name="username"?value="root"?/>
????????????????<property?name="password"?value="123456"?/>
????????????</dataSource>
????????</environment>
????</environments>
????<mappers>
???????<mapper??resource="sqlMapper/userMapper.xml"/>
????</mappers>
</configuration>

第一步:創建一個sqlSessionFactory

在了解如何創建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)?{//進入該判斷
????????????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.");
??????????}
????????}
??????}
????}
??}

根據以上代碼可以分析,在寫mapper映射文件的地址時不僅可以寫成resource,還可以寫成url和mapperClass的形式,由于我們用的是resource,所以直接進入第一個判斷,最后解析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語句節點的是

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

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

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

最后進入方法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;
??}

最后回到我們的創建sqlSessionFactory上,之前的一切都是為了生成一個sqlSessionFactory服務的

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為參數build()方法生成DefautSqlSessionFactory。

每天用Mybatis,但是Mybatis的工作原理你真的知道嗎?

第二步:創建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,默認使用DefaultSqlSession?
?public?DefaultSqlSession(Configuration?configuration,?Executor?executor,?boolean?autoCommit)?{
????this.configuration?=?configuration;
????this.executor?=?executor;
????this.dirty?=?false;
????this.autoCommit?=?autoCommit;
??}

executor在這一步得到創建,具體的使用在下一步。

第三步:執行具體的sql請求

在我的代碼里執行的是

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

具體到里面的方法就是

public?<E>?List<E>?selectList(String?statement,?Object?parameter,?RowBounds?rowBounds)?{
????try?{
?????//1.根據Statement?Id,在mybatis?配置對象Configuration中查找和配置文件相對應的MappedStatement
??????MappedStatement?ms?=?configuration.getMappedStatement(statement);
??????//2.?將查詢任務委托給MyBatis?的執行器?Executor
??????List<E>?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根據Statement ID, 在mybatis配置對象Configuration中獲取到對應的MappedStatement對象,然后調用mybatis執行器來執行具體的操作。

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

@SuppressWarnings("unchecked")
??public?<E>?List<E>?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<E>?list;
????try?{
??????queryStack++;
??????list?=?resultHandler?==?null???(List<E>)?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?<E>?List<E>?queryFromDatabase(MappedStatement?ms,?Object?parameter,?RowBounds?rowBounds,?ResultHandler?resultHandler,?CacheKey?key,?BoundSql?boundSql)?throws?SQLException?{
????List<E>?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;
??}

在這兩個方法里面會為當前的查詢創建一個緩存key,如果緩存中沒有值,直接從數據庫中讀取,執行查詢后將得到的list結果放入緩存之中。

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

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

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

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

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?<E>?List<E>?query(Statement?statement,?ResultHandler?resultHandler)?throws?SQLException?{
????PreparedStatement?ps?=?(PreparedStatement)?statement;
????ps.execute();
????return?resultSetHandler.<E>?handleResultSets(ps);
??}

prepareStatement()是創建Statement的具體實現方法,調用parameterize()對創建的Statement對象設置參數,即為我們設為占位符的地方賦上指定的參數,parameterize()方法再深入進去就是調用ParameterHandler的setParameters()方法具體賦值了。歡迎大家關注我的公眾號【程序員追風】,文章都會在里面更新,整理的資料也會放在里面

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

每天用Mybatis,但是Mybatis的工作原理你真的知道嗎?

總結以上步驟就是:

  1. 根據具體傳入的參數,動態地生成需要執行的SQL語句,用BoundSql對象表示

  2. 為當前的查詢創建一個緩存Key

  3. 緩存中沒有值,直接從數據庫中讀取數據

  4. 執行查詢,返回List 結果,然后 將查詢的結果放入緩存之中

  5. 根據既有的參數,創建StatementHandler對象來執行查詢操作

  6. 將創建Statement傳遞給StatementHandler對象,調用parameterize()方法賦值

  7. 調用StatementHandler.query()方法,返回List結果集

總結

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

每天用Mybatis,但是Mybatis的工作原理你真的知道嗎?

最后

歡迎大家一起交流,喜歡文章記得點個贊喲,感謝支持!


向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

建昌县| 上栗县| 新源县| 兴安县| 儋州市| 竹山县| 辰溪县| 安福县| 沁源县| 叶城县| 衡南县| 福清市| 英超| 岱山县| 吉林市| 贵德县| 南阳市| 康平县| 阳朔县| 绥阳县| 集贤县| 淅川县| 怀集县| 广宁县| 临桂县| 稻城县| 潼南县| 剑河县| 绿春县| 贵州省| 罗平县| 万源市| 南澳县| 泰州市| 舒兰市| 丰宁| 五峰| 新安县| 临桂县| 收藏| 瑞昌市|