您好,登錄后才能下訂單哦!
近來想寫一個mybatis的分頁插件,但是在寫插件之前肯定要了解一下mybatis具體的工作原理吧,于是邊參考別人的博客,邊看源碼就開干了。
核心部件:
SqlSession
Executor
StatementHandler
ParameterHandler
ResultSetHandler
TypeHandler
MappedStatement
Configuration
在分析工作原理之前,首先看一下我的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。
第二步:創建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類型的集合。
總結以上步驟就是:
根據具體傳入的參數,動態地生成需要執行的SQL語句,用BoundSql對象表示
為當前的查詢創建一個緩存Key
緩存中沒有值,直接從數據庫中讀取數據
執行查詢,返回List 結果,然后 將查詢的結果放入緩存之中
根據既有的參數,創建StatementHandler對象來執行查詢操作
將創建Statement傳遞給StatementHandler對象,調用parameterize()方法賦值
調用StatementHandler.query()方法,返回List結果集
總結
以上三個步驟所有流程大體可以用一張圖來總結
最后
歡迎大家一起交流,喜歡文章記得點個贊喲,感謝支持!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。