這篇文章主要介紹“Mybatis源碼分析之如何理解SQLSession初始化”,在日常操作中,相信很多人在Mybatis源碼分析之如何理解SQLSession初始化問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Mybatis源碼分析之如何理解SQLSession初始化”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!
10年積累的網(wǎng)站設(shè)計(jì)制作、網(wǎng)站制作經(jīng)驗(yàn),可以快速應(yīng)對客戶對網(wǎng)站的新想法和需求。提供各種問題對應(yīng)的解決方案。讓選擇我們的客戶得到更好、更有力的網(wǎng)絡(luò)服務(wù)。我雖然不認(rèn)識(shí)你,你也不認(rèn)識(shí)我。但先做網(wǎng)站后付款的網(wǎng)站建設(shè)流程,更有紅安免費(fèi)網(wǎng)站建設(shè)讓你可以放心的選擇與我們合作。
這次打算寫一個(gè) Mybatis 源碼分析的系列,大致分為
Mybatis 啟動(dòng)流程分析
Mybatis 的SQL 執(zhí)行流程分析
Mybatis 的拓展點(diǎn)以及與 Spring Boot 的整合
這篇文章先來分析 Mybati初始化流程,如何讀取配置文件到,以及創(chuàng)建出 SqlSession 示例.主要內(nèi)容包括
讀取、解析mybatis 全局配置文件
映射 mapper.java 文件
解析 mapper.xml 文件
解析 mapper.xml 各個(gè)節(jié)點(diǎn)配置,包括 namespace、緩存、增刪改查節(jié)點(diǎn)
Mybatis 緩存機(jī)制
構(gòu)建DefaultSqlSessionFactory
SQLSession對外提供了用戶和數(shù)據(jù)庫之間交互需要的所有方法,隱藏了底層的細(xì)節(jié)。默認(rèn)實(shí)現(xiàn)類是DefaultSqlSession
通過一個(gè)mybatis 官方提供的示例,看下如何手動(dòng)創(chuàng)建 SQLSession
//Mybatis 配置文件,通常包含:數(shù)據(jù)庫連接信息,Mapper.class 全限定名包路徑,事務(wù)配置,插件配置等等 String resource = "org/mybatis/builder/mybatis-config.xml"; //以輸入流的方式讀取配置 InputStream inputStream = Resources.getResourceAsStream(resource); //實(shí)例化出 SQLSession 的必要步驟 SqlSessionFactoryBuilder --> SqlSessionFactory --> SqlSession SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(inputStream); SqlSession session = factory.openSession();
接下來就通過new SqlSessionFactoryBuilder() 開始我們的構(gòu)建 SQLSession 源碼分析
//SqlSessionFactory 有4 個(gè)構(gòu)造方法,最終都會(huì)執(zhí)行到全參的構(gòu)造方法 public SqlSessionFactory build(Reader reader, String environment, Properties properties) { try { //首先會(huì)實(shí)例化一個(gè) XMLConfigBuilder ,這里先有個(gè)基本的認(rèn)知:XMLConfigBuilder 就是用來解析 XML 文件配置的 XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties); //經(jīng)過parser.parse()之后,XML配置文件已經(jīng)被解析成了Configuration ,Configuration 對象是包含著mybatis的所有屬性. return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { //... 關(guān)閉流,拋異常 } }
MLConfigBuilder : 解析全局配置文件即 mybatis-config.xml
XMLMapperBuilder : 解析 Mapper 文件,配置在mybatis-config.xml 文件中 mapper.java 的包路徑
XMLStatementBuilder :解析 mapper 文件的節(jié)點(diǎn)中 ,SQL 語句標(biāo)簽:select,update,insert,delete
SQLSourceBuilder:動(dòng)態(tài)解析 SQL 語句,根據(jù) SqlNode 解析 Sql 語句中的標(biāo)簽,比如
當(dāng)然 BaseBuilder 的實(shí)現(xiàn)類不僅這 4 個(gè),這里只介紹這 4 類,在后續(xù)一步步分析中都能看到這幾個(gè)的身影 點(diǎn)進(jìn)去看一下 parser.parse()
public Configuration parse() { // 若已經(jīng)解析過了 就拋出異常 if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } // 設(shè)置解析標(biāo)志位 parsed = true; // 解析mybatis-config.xml的節(jié)點(diǎn),讀取配置文件,加載到 Configuration 中 parseConfiguration(parser.evalNode("/configuration")); return configuration; }
解析成 Configuration 成之前會(huì)先將 xml 配置文件解析成 XNode 對象
public XNode evalNode(Object root, String expression) { //mybatis 自已定義了一個(gè)XPathParser 對象來解析 xml ,其實(shí)對Document做了封裝 Node node = (Node) evaluate(expression, root, XPathConstants.NODE); if (node == null) { return null; } return new XNode(this, node, variables); }
接下來就看看下mybatis 是如何一步步讀取配置文件的
/** * 解析 mybatis-config.xml的 configuration節(jié)點(diǎn) * 解析 XML 中的各個(gè)節(jié)點(diǎn) */ private void parseConfiguration(XNode root) { try { /** * 解析 properties節(jié)點(diǎn) ** 解析到org.apache.ibatis.parsing.XPathParser#variables * org.apache.ibatis.session.Configuration#variables */ propertiesElement(root.evalNode("properties")); /** * 解析我們的mybatis-config.xml中的settings節(jié)點(diǎn) * 具體可以配置哪些屬性:http://www.mybatis.org/mybatis-3/zh/configuration.html#settings * * */ Properties settings = settingsAsProperties(root.evalNode("settings")); /** * 基本沒有用過該屬性 * VFS含義是虛擬文件系統(tǒng);主要是通過程序能夠方便讀取本地文件系統(tǒng)、FTP文件系統(tǒng)等系統(tǒng)中的文件資源。 Mybatis中提供了VFS這個(gè)配置,主要是通過該配置可以加載自定義的虛擬文件系統(tǒng)應(yīng)用程序 解析到:org.apache.ibatis.session.Configuration#vfsImpl */ loadCustomVfs(settings); /** * 指定 MyBatis 所用日志的具體實(shí)現(xiàn),未指定時(shí)將自動(dòng)查找。 * SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING * 解析到org.apache.ibatis.session.Configuration#logImpl */ loadCustomLogImpl(settings); /** * 解析我們的別名 * .............. 解析到oorg.apache.ibatis.session.Configuration#typeAliasRegistry.typeAliases */ typeAliasesElement(root.evalNode("typeAliases")); /** * 解析我們的插件(比如分頁插件) * mybatis自帶的 * Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) ParameterHandler (getParameterObject, setParameters) ResultSetHandler (handleResultSets, handleOutputParameters) StatementHandler (prepare, parameterize, batch, update, query) 解析到:org.apache.ibatis.session.Configuration#interceptorChain.interceptors */ pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); reflectorFactoryElement(root.evalNode("reflectorFactory")); // 設(shè)置settings 和默認(rèn)值 settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 /** * 解析我們的mybatis環(huán)境,解析 DataSource * 解析到:org.apache.ibatis.session.Configuration#environment * 在集成spring情況下由 spring-mybatis提供數(shù)據(jù)源 和事務(wù)工廠 */ environmentsElement(root.evalNode("environments")); /** * 解析數(shù)據(jù)庫廠商 * * 解析到:org.apache.ibatis.session.Configuration#databaseId */ databaseIdProviderElement(root.evalNode("databaseIdProvider")); /** * 解析我們的類型處理器節(jié)點(diǎn) * 解析到:org.apache.ibatis.session.Configuration#typeHandlerRegistry.typeHandlerMap */ typeHandlerElement(root.evalNode("typeHandlers")); /** * 最最重要的就是解析我們的mapper * resource:來注冊我們的class類路徑下的 url:來指定我們磁盤下的或者網(wǎng)絡(luò)資源的 class: 若注冊Mapper不帶xml文件的,這里可以直接注冊 若注冊的Mapper帶xml文件的,需要把xml文件和mapper文件同名 同路徑 --> * 解析 mapper: * 1.解析mapper.java接口 解析到:org.apache.ibatis.session.Configuration#mapperRegistry.knownMappers * 2.解析 mapper.xml 配置 */ mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } } -->
private void mapperElement(XNode parent) throws Exception { if (parent != null) { //獲取我們mappers節(jié)點(diǎn)下的一個(gè)一個(gè)的mapper節(jié)點(diǎn) for (XNode child : parent.getChildren()) { /** * 指定 mapper 的 4 中方式: * 1.指定的 mapper 所在的包路徑,批量注冊 * 2.通過 resource 目錄指定 * 3.通過 url 指定,從網(wǎng)絡(luò)資源或者本地磁盤 * 4.通過 class 路徑注冊 */ //判斷我們mapper是不是通過批量注冊的 if ("package".equals(child.getName())) { String mapperPackage = child.getStringAttribute("name"); configuration.addMappers(mapperPackage); } else { //判斷從classpath下讀取我們的mapper String resource = child.getStringAttribute("resource"); //判斷是不是從我們的網(wǎng)絡(luò)資源讀取(或者本地磁盤得) String url = child.getStringAttribute("url"); //解析這種類型(要求接口和xml在同一個(gè)包下) String mapperClass = child.getStringAttribute("class"); //解析 mapper 文件 if (resource != null && url == null && mapperClass == null) { ErrorContext.instance().resource(resource); // 把mapper文件讀取出一個(gè)流,是不是似曾相識(shí),最開始的時(shí)候讀取mybatis-config.xml 配置文件也是通過輸入流的方式讀取的 InputStream inputStream = Resources.getResourceAsStream(resource); //創(chuàng)建讀取XmlMapper構(gòu)建器對象,用于來解析我們的mapper.xml文件,上面提到過的 XMLMapperBuilder對象 /** * 讀取的 mapper 文件會(huì)被放入到 MapperRegistry 中的 knownMappers中 * Map, MapperProxyFactory>> knownMappers = new HashMap<>(); * 為后續(xù)創(chuàng)建 Mapper 代理對象做準(zhǔn)備 */ XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); //真正的解析我們的mapper.xml配置文件,這里就會(huì)來解析我們的sql 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.xml 中的 SQL 標(biāo)簽
//解析的 SQL 語句節(jié)點(diǎn)會(huì)放在Configuration.MappedStatement.SqlSource 中,SqlSource 中包含了一個(gè)個(gè)的 SQLNode,一個(gè)標(biāo)簽對應(yīng)一個(gè) SQLNode public void parse() { //判斷當(dāng)前的Mapper是否被加載過 if (!configuration.isResourceLoaded(resource)) { //真正的解析我們的mapper configurationElement(parser.evalNode("/mapper")); //把資源保存到我們Configuration中 configuration.addLoadedResource(resource); bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements(); }
解析 mapper.xml 中的各個(gè)節(jié)點(diǎn)
//解析我們的節(jié)點(diǎn) private void configurationElement(XNode context) { try { /** * 解析我們的namespace屬性 * */ String namespace = context.getStringAttribute("namespace"); if (namespace == null || namespace.equals("")) { throw new BuilderException("Mapper's namespace cannot be empty"); } //保存我們當(dāng)前的namespace 并且判斷接口完全類名==namespace builderAssistant.setCurrentNamespace(namespace); /** * 解析我們的緩存引用 * 說明我當(dāng)前的緩存引用和DeptMapper的緩存引用一致 * 解析到org.apache.ibatis.session.Configuration#cacheRefMap<當(dāng)前namespace,ref-namespace> 異常下(引用緩存未使用緩存):org.apache.ibatis.session.Configuration#incompleteCacheRefs */ cacheRefElement(context.evalNode("cache-ref")); /** * 解析我們的cache節(jié)點(diǎn) * 解析到:org.apache.ibatis.session.Configuration#caches org.apache.ibatis.builder.MapperBuilderAssistant#currentCache */ cacheElement(context.evalNode("cache")); /** * 解析paramterMap節(jié)點(diǎn) */ parameterMapElement(context.evalNodes("/mapper/parameterMap")); /** * 解析我們的resultMap節(jié)點(diǎn) * 解析到:org.apache.ibatis.session.Configuration#resultMaps * 異常 org.apache.ibatis.session.Configuration#incompleteResultMaps * */ resultMapElements(context.evalNodes("/mapper/resultMap")); /** * 解析我們通過sql節(jié)點(diǎn) * 解析到org.apache.ibatis.builder.xml.XMLMapperBuilder#sqlFragments * 其實(shí)等于 org.apache.ibatis.session.Configuration#sqlFragments * 因?yàn)樗麄兪峭灰?,在?gòu)建XMLMapperBuilder 時(shí)把Configuration.getSqlFragments傳進(jìn)去了 */ sqlElement(context.evalNodes("/mapper/sql")); /** * 解析我們的select | insert |update |delete節(jié)點(diǎn) * 解析到org.apache.ibatis.session.Configuration#mappedStatements * 最終SQL節(jié)點(diǎn)會(huì)被解析成 MappedStatement,一個(gè)節(jié)點(diǎn)就是對應(yīng)一個(gè)MappedStatement * 準(zhǔn)確的說 sql 節(jié)點(diǎn)被解析成 SQLNode 封裝在 MappedStatement.SqlSource 中 * SQLNode 對應(yīng)的就是 sql 節(jié)點(diǎn)中的子標(biāo)簽,比如 , , 等 */ buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e); } }
著重分析幾個(gè)解析過程
private void cacheElement(XNode context) { if (context != null) { /** * cache元素可指定如下屬性,每種屬性的指定都是針對都是針對底層Cache的一種裝飾,采用的是裝飾器的模式 * 緩存屬性: * 1.eviction: 緩存過期策略:默認(rèn)是LRU * LRU – 最近最少使用的:移除最長時(shí)間不被使用的對象。--> LruCache * FIFO – 先進(jìn)先出:按對象進(jìn)入緩存的順序來移除它們。--> FifoCache * SOFT – 軟引用:移除基于垃圾回收器狀態(tài)和軟引用規(guī)則的對象。--> SoftCache * WEAK – 弱引用:更積極地移除基于垃圾收集器狀態(tài)和弱引用規(guī)則的對象。--> WeakCache * 2.flushInterval: 清空緩存的時(shí)間間隔,單位毫秒,默認(rèn)不清空,指定了之后將會(huì)用 ScheduleCache 封裝 * 3.size :緩存對象的大小,默認(rèn)是 1024,其是針對LruCache而言的,LruCache默認(rèn)只存儲(chǔ)最多1024個(gè)Key * 4.readOnly :默認(rèn)是false,底層SerializedCache包裝,會(huì)在寫緩存的時(shí)候?qū)⒕彺鎸ο筮M(jìn)行序列化,然后在讀緩存的時(shí)候進(jìn)行反序列化,這樣每次讀到的都將是一個(gè)新的對象,即使你更改了讀取到的結(jié)果,也不會(huì)影響原來緩存的對象;true-給所有調(diào)用者返回緩存對象的相同實(shí)例 * 5.blocking : 默認(rèn)為false,當(dāng)指定為true時(shí)將采用BlockingCache進(jìn)行封裝,在進(jìn)行增刪改之后的并發(fā)查詢,只會(huì)有一條去數(shù)據(jù)庫查詢,而不會(huì)并發(fā)訪問 * 6.type: type屬性用來指定當(dāng)前底層緩存實(shí)現(xiàn)類,默認(rèn)是PerpetualCache,如果我們想使用自定義的Cache,則可以通過該屬性來指定,對應(yīng)的值是我們自定義的Cache的全路徑名稱 */ //解析cache節(jié)點(diǎn)的type屬性 String type = context.getStringAttribute("type", "PERPETUAL"); //根據(jù)type的String獲取class類型 Class extends Cache> typeClass = typeAliasRegistry.resolveAlias(type); //獲取緩存過期策略:默認(rèn)是LRU String eviction = context.getStringAttribute("eviction", "LRU"); Class extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction); //flushInterval(刷新間隔)屬性可以被設(shè)置為任意的正整數(shù),設(shè)置的值應(yīng)該是一個(gè)以毫秒為單位的合理時(shí)間量。 默認(rèn)情況是不設(shè)置,也就是沒有刷新間隔,緩存僅僅會(huì)在調(diào)用語句時(shí)刷新。 Long flushInterval = context.getLongAttribute("flushInterval"); //size(引用數(shù)目)屬性可以被設(shè)置為任意正整數(shù),要注意欲緩存對象的大小和運(yùn)行環(huán)境中可用的內(nèi)存資源。默認(rèn)值是 1024。 Integer size = context.getIntAttribute("size"); //只讀)屬性可以被設(shè)置為 true 或 false。只讀的緩存會(huì)給所有調(diào)用者返回緩存對象的相同實(shí)例。 因此這些對象不能被修改。這就提供了可觀的性能提升。而可讀寫的緩存會(huì)(通過序列化)返回緩存對象的拷貝。 速度上會(huì)慢一些,但是更安全,因此默認(rèn)值是 false boolean readWrite = !context.getBooleanAttribute("readOnly", false); boolean blocking = context.getBooleanAttribute("blocking", false); Properties props = context.getChildrenAsProperties(); //把緩存節(jié)點(diǎn)加入到Configuration中 //這里的 builder()方法利用責(zé)任鏈方式循環(huán)實(shí)例化Cache 對象 builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props); } }
MyBatis自帶的緩存有一級緩存和二級緩存
Mybatis一級緩存是指Session緩存。作用域默認(rèn)是一個(gè)SqlSession。默認(rèn)開啟一級緩存,范圍有SESSION和STATEMENT兩種,默認(rèn)是SESSION,如果需要更改一級緩存的范圍,可以在Mybatis的配置文件中,通過localCacheScope指定
Mybatis的二級緩存是指mapper映射文件。二級緩存的作用域是同一個(gè)namespace下的mapper映射文件內(nèi)容,多個(gè)SqlSession共享。二級緩存是默認(rèn)啟用的,但是需要手動(dòng)在 mapper 文件中設(shè)置啟動(dòng)二級緩存
//在 mapper.xml 文件加上此配置,該 mapper 文件對應(yīng)的 SQL就開啟了緩存
或者直接關(guān)閉緩存
//在全局配置文件中關(guān)閉緩存
注意:如果開啟了二級緩存,查詢結(jié)果的映射對象一定要實(shí)現(xiàn)Serializable ,因?yàn)閙ybatis 緩存對象的時(shí)候默認(rèn)是會(huì)對映射對象進(jìn)行序列號操作的
private void buildStatementFromContext(Listlist, String requiredDatabaseId) { //循環(huán)我們的select|delte|insert|update節(jié)點(diǎn) for (XNode context : list) { //創(chuàng)建一個(gè)xmlStatement的構(gòu)建器對象 final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); try { //通過該步驟解析之后 mapper.xml 的 sql 節(jié)點(diǎn)就也被解析了 statementParser.parseStatementNode(); } catch (IncompleteElementException e) { configuration.addIncompleteStatement(statementParser); } } }
至此配置mybatis 的配置文件已經(jīng)解析完成,配置文件已經(jīng)解析成了Configuration,會(huì)到最初,我們的目標(biāo)是獲取 SqlSession 對象,通過new SqlSessionFactoryBuilder().build(reader) 已經(jīng)構(gòu)建出了一個(gè)SqlSessionFactory 工廠對象,還差一步 SqlSession session = sqlMapper.openSession();
通過分析DefaultSqlSession 的 openSession() 來實(shí)例化 SQLSession 對象
//從session中開啟一個(gè)數(shù)據(jù)源 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { //獲取環(huán)境變量 final Environment environment = configuration.getEnvironment(); // 獲取事務(wù)工廠 final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); /** * 創(chuàng)建一個(gè)sql執(zhí)行器對象 * 一般情況下 若我們的mybaits的全局配置文件的cacheEnabled默認(rèn)為ture就返回 * 一個(gè)cacheExecutor,若關(guān)閉的話返回的就是一個(gè)SimpleExecutor */ final Executor executor = configuration.newExecutor(tx, execType); //創(chuàng)建返回一個(gè)DeaultSqlSessoin對象返回 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(); } }
仔細(xì)一點(diǎn)看你會(huì)發(fā)現(xiàn)configuration 就是剛才千辛萬苦創(chuàng)建出來的 Configuration 對象,包含所有 mybatis 配置信息.至此SQLSession 的創(chuàng)建已分析完畢.
總結(jié)一下上述流程:
1:通過XPathParser 讀取xml 配置文件成 XNode 屬性 2:通過 XMLConfigBuilder 解析 mybatis-config.xml 中的各個(gè)節(jié)點(diǎn)配置,包括
解析properties 節(jié)點(diǎn)
解析settings 節(jié)點(diǎn)
加載日志框架
解析 typeAliases
解析拓展插架 plugins
解析數(shù)據(jù)源 DataSource
解析類型處理器 typeHandle
解析 mapper文件
讀取方式有 package,resource,url,class ,最終都會(huì)放入到 Map
1.同樣以輸入流的方式讀取 mapper.xml 文件 2.通過 XMLMapperBuilder 實(shí)例解析 mapper.xml 文件中各個(gè)接點(diǎn)屬性
解析 namespace 屬性
解析緩存引用 cache-ref
解析 cache 節(jié)點(diǎn)
解析 resultMap 節(jié)點(diǎn)
解析 sql 節(jié)點(diǎn)
解析 select | insert |update |delete節(jié)點(diǎn) 3.通過 XMLStatementBuilder 解析SQL 標(biāo)簽
到此,關(guān)于“Mybatis源碼分析之如何理解SQLSession初始化”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!