Mybatis 的工作原理,写得太好了!
來源:cnblogs.com/scuury/p/10371246.html
近來想寫一個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><environments?default="development"><environment?id="development"><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>????}parsed?=?true;parseConfiguration(parser.evalNode("/configuration"));return?configuration;} private?void?parseConfiguration(XNode?root)?{try?{propertiesElement(root.evalNode("properties"));?//issue?#117?read?properties?firsttypeAliasesElement(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?#631databaseIdProviderElement(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>??????????}}}}}根據以上代碼可以分析,在寫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,默認使用DefaultSqlSessionpublic?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中查找和配置文件相對應的MappedStatementMappedStatement?ms?=?configuration.getMappedStatement(statement);//2.?將查詢任務委托給MyBatis?的執行器?ExecutorList<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?#601if?(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結果集
總結
以上三個步驟所有流程大體可以用一張圖來總結
?
?
參考
https://blog.csdn.net/luanlouis/article/details/40422941
https://blog.csdn.net/a412451848/article/details/82723754
總結
以上是生活随笔為你收集整理的Mybatis 的工作原理,写得太好了!的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 从考研说起:大学、职场、个人选择
- 下一篇: 一文让你轻松了解 JAVA 开发中的四种