<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>

          [文末贈(zèng)書(shū)]今天架構(gòu)師和我分享了 MyBatis的原理

          共 60320字,需瀏覽 121分鐘

           ·

          2021-06-01 02:01

          點(diǎn)擊上方 Java學(xué)習(xí)之道,選擇 設(shè)為星標(biāo)

          每天18:30點(diǎn),干貨準(zhǔn)時(shí)奉上!

          來(lái)源: jianshu.com/p/ec40a82cae28
          作者: 七寸知架構(gòu)

          Part1引言

          本文主要講解JDBC怎么演變到Mybatis的漸變過(guò)程,重點(diǎn)講解了為什么要將JDBC封裝成Mybaits這樣一個(gè)持久層框架。再而論述Mybatis作為一個(gè)數(shù)據(jù)持久層框架本身有待改進(jìn)之處。

          Part2JDBC實(shí)現(xiàn)查詢分析

          我們先看看我們最熟悉也是最基礎(chǔ)的通過(guò)JDBC查詢數(shù)據(jù)庫(kù)數(shù)據(jù),一般需要以下七個(gè)步驟:

          • 加載JDBC驅(qū)動(dòng);

          • 建立并獲取數(shù)據(jù)庫(kù)連接;

          • 創(chuàng)建 JDBC Statements 對(duì)象;

          • 設(shè)置SQL語(yǔ)句的傳入?yún)?shù);

          • 執(zhí)行SQL語(yǔ)句并獲得查詢結(jié)果;

          • 對(duì)查詢結(jié)果進(jìn)行轉(zhuǎn)換處理并將處理結(jié)果返回;

          • 釋放相關(guān)資源(關(guān)閉Connection,關(guān)閉Statement,關(guān)閉ResultSet);

          以下是具體的實(shí)現(xiàn)代碼:

          public static List<Map<String,Object>> queryForList(){  
              Connection connection = null;  
              ResultSet rs = null;  
              PreparedStatement stmt = null;  
              List<Map<String,Object>> resultList = new ArrayList<Map<String,Object>>();  
                    
              try {  
                  // 加載JDBC驅(qū)動(dòng)  
                  Class.forName("oracle.jdbc.driver.OracleDriver").newInstance();  
                  String url = "jdbc:oracle:thin:@localhost:1521:ORACLEDB";  
                        
                  String user = "trainer";   
                  String password = "trainer";   
                        
                  // 獲取數(shù)據(jù)庫(kù)連接  
                  connection = DriverManager.getConnection(url,user,password);   
                        
                  String sql = "select * from userinfo where user_id = ? ";  
                  // 創(chuàng)建Statement對(duì)象(每一個(gè)Statement為一次數(shù)據(jù)庫(kù)執(zhí)行請(qǐng)求)  
                  stmt = connection.prepareStatement(sql);  
                        
                  // 設(shè)置傳入?yún)?shù)  
                  stmt.setString(1"zhangsan");  
                        
                  // 執(zhí)行SQL語(yǔ)句  
                  rs = stmt.executeQuery();  
                        
                  // 處理查詢結(jié)果(將查詢結(jié)果轉(zhuǎn)換成List<Map>格式)  
                  ResultSetMetaData rsmd = rs.getMetaData();  
                  int num = rsmd.getColumnCount();  
                        
                  while(rs.next()){  
                      Map map = new HashMap();  
                      for(int i = 0;i < num;i++){  
                          String columnName = rsmd.getColumnName(i+1);  
                          map.put(columnName,rs.getString(columnName));  
                      }  
                      resultList.add(map);  
                  }  
                        
              } catch (Exception e) {  
                  e.printStackTrace();  
              } finally {  
                  try {  
                      // 關(guān)閉結(jié)果集  
                      if (rs != null) {  
                          rs.close();  
                          rs = null;  
                      }  
                      // 關(guān)閉執(zhí)行  
                      if (stmt != null) {  
                          stmt.close();  
                          stmt = null;  
                      }  
                      if (connection != null) {  
                          connection.close();  
                          connection = null;  
                      }  
                  } catch (SQLException e) {  
                      e.printStackTrace();  
                  }  
              }        
              return resultList;  
          }

          Part3JDBC演變到Mybatis過(guò)程

          上面我們看到了實(shí)現(xiàn)JDBC有七個(gè)步驟,哪些步驟是可以進(jìn)一步封裝的,減少我們開(kāi)發(fā)的代碼量。

          3.1 第一步優(yōu)化:連接獲取和釋放

          問(wèn)題描述:

          數(shù)據(jù)庫(kù)連接頻繁的開(kāi)啟和關(guān)閉本身就造成了資源的浪費(fèi),影響系統(tǒng)的性能。

          解決問(wèn)題:

          數(shù)據(jù)庫(kù)連接的獲取和關(guān)閉我們可以使用數(shù)據(jù)庫(kù)連接池來(lái)解決資源浪費(fèi)的問(wèn)題。通過(guò)連接池就可以反復(fù)利用已經(jīng)建立的連接去訪問(wèn)數(shù)據(jù)庫(kù)了。減少連接的開(kāi)啟和關(guān)閉的時(shí)間。

          問(wèn)題描述:

          但是現(xiàn)在連接池多種多樣,可能存在變化,有可能采用DBCP的連接池,也有可能采用容器本身的JNDI數(shù)據(jù)庫(kù)連接池。

          解決問(wèn)題:

          我們可以通過(guò)DataSource進(jìn)行隔離解耦,我們統(tǒng)一從DataSource里面獲取數(shù)據(jù)庫(kù)連接,DataSource具體由DBCP實(shí)現(xiàn)還是由容器的JNDI實(shí)現(xiàn)都可以,所以我們將DataSource的具體實(shí)現(xiàn)通過(guò)讓用戶配置來(lái)應(yīng)對(duì)變化。

          3.2 第二步優(yōu)化:SQL統(tǒng)一存取##

          問(wèn)題描述:

          我們使用JDBC進(jìn)行操作數(shù)據(jù)庫(kù)時(shí),SQL語(yǔ)句基本都散落在各個(gè)JAVA類(lèi)中,這樣有三個(gè)不足之處:

          第一,可讀性很差,不利于維護(hù)以及做性能調(diào)優(yōu)。

          第二,改動(dòng)Java代碼需要重新編譯、打包部署。

          第三,不利于取出SQL在數(shù)據(jù)庫(kù)客戶端執(zhí)行(取出后還得刪掉中間的Java代碼,編寫(xiě)好的SQL語(yǔ)句寫(xiě)好后還得通過(guò)+號(hào)在Java進(jìn)行拼湊)。

          解決問(wèn)題:

          我們可以考慮不把SQL語(yǔ)句寫(xiě)到Java代碼中,那么把SQL語(yǔ)句放到哪里呢?首先需要有一個(gè)統(tǒng)一存放的地方,我們可以將這些SQL語(yǔ)句統(tǒng)一集中放到配置文件或者數(shù)據(jù)庫(kù)里面(以key-value的格式存放)。然后通過(guò)SQL語(yǔ)句的key值去獲取對(duì)應(yīng)的SQL語(yǔ)句。

          既然我們將SQL語(yǔ)句都統(tǒng)一放在配置文件或者數(shù)據(jù)庫(kù)中,那么這里就涉及一個(gè)SQL語(yǔ)句的加載問(wèn)題。

          3.3 第三步優(yōu)化:傳入?yún)?shù)映射和動(dòng)態(tài)SQL##

          問(wèn)題描述:

          很多情況下,我們都可以通過(guò)在SQL語(yǔ)句中設(shè)置占位符來(lái)達(dá)到使用傳入?yún)?shù)的目的,這種方式本身就有一定局限性,它是按照一定順序傳入?yún)?shù)的,要與占位符一一匹配。但是,如果我們傳入的參數(shù)是不確定的(比如列表查詢,根據(jù)用戶填寫(xiě)的查詢條件不同,傳入查詢的參數(shù)也是不同的,有時(shí)是一個(gè)參數(shù)、有時(shí)可能是三個(gè)參數(shù)),那么我們就得在后臺(tái)代碼中自己根據(jù)請(qǐng)求的傳入?yún)?shù)去拼湊相應(yīng)的SQL語(yǔ)句,這樣的話還是避免不了在Java代碼里面寫(xiě)SQL語(yǔ)句的命運(yùn)。既然我們已經(jīng)把SQL語(yǔ)句統(tǒng)一存放在配置文件或者數(shù)據(jù)庫(kù)中了,怎么做到能夠根據(jù)前臺(tái)傳入?yún)?shù)的不同,動(dòng)態(tài)生成對(duì)應(yīng)的SQL語(yǔ)句呢?

          解決問(wèn)題:

          第一,我們先解決這個(gè)動(dòng)態(tài)問(wèn)題,按照我們正常的程序員思維是,通過(guò)if和else這類(lèi)的判斷來(lái)進(jìn)行是最直觀的,這個(gè)時(shí)候我們想到了JSTL中的這樣的標(biāo)簽,那么,能不能將這類(lèi)的標(biāo)簽引入到SQL語(yǔ)句中呢?假設(shè)可以,那么我們這里就需要一個(gè)專(zhuān)門(mén)的SQL解析器來(lái)解析這樣的SQL語(yǔ)句,但是,if判斷的變量來(lái)自于哪里呢?傳入的值本身是可變的,那么我們得為這個(gè)值定義一個(gè)不變的變量名稱(chēng),而且這個(gè)變量名稱(chēng)必須和對(duì)應(yīng)的值要有對(duì)應(yīng)關(guān)系,可以通過(guò)這個(gè)變量名稱(chēng)找到對(duì)應(yīng)的值,這個(gè)時(shí)候我們想到了key-value的Map。解析的時(shí)候根據(jù)變量名的具體值來(lái)判斷。

          假如前面可以判斷沒(méi)有問(wèn)題,那么假如判斷的結(jié)果是true,那么就需要輸出的標(biāo)簽里面的SQL片段,但是怎么解決在標(biāo)簽里面使用變量名稱(chēng)的問(wèn)題呢?這里我們需要使用一種有別于SQL的語(yǔ)法來(lái)嵌入變量(比如使用#變量名#)。這樣,SQL語(yǔ)句經(jīng)過(guò)解析后就可以動(dòng)態(tài)的生成符合上下文的SQL語(yǔ)句。

          還有,怎么區(qū)分開(kāi)占位符變量和非占位變量?有時(shí)候我們單單使用占位符是滿足不了的,占位符只能為查詢條件占位,SQL語(yǔ)句其他地方使用不了。這里我們可以使用#變量名#表示占位符變量,使用變量名表示非占位符變量。

          3.4 第四步優(yōu)化:結(jié)果映射和結(jié)果緩存##

          問(wèn)題描述:

          執(zhí)行SQL語(yǔ)句、獲取執(zhí)行結(jié)果、對(duì)執(zhí)行結(jié)果進(jìn)行轉(zhuǎn)換處理、釋放相關(guān)資源是一整套下來(lái)的。假如是執(zhí)行查詢語(yǔ)句,那么執(zhí)行SQL語(yǔ)句后,返回的是一個(gè)ResultSet結(jié)果集,這個(gè)時(shí)候我們就需要將ResultSet對(duì)象的數(shù)據(jù)取出來(lái),不然等到釋放資源時(shí)就取不到這些結(jié)果信息了。我們從前面的優(yōu)化來(lái)看,以及將獲取連接、設(shè)置傳入?yún)?shù)、執(zhí)行SQL語(yǔ)句、釋放資源這些都封裝起來(lái)了,只剩下結(jié)果處理這塊還沒(méi)有進(jìn)行封裝,如果能封裝起來(lái),每個(gè)數(shù)據(jù)庫(kù)操作都不用自己寫(xiě)那么一大堆Java代碼,直接調(diào)用一個(gè)封裝的方法就可以搞定了。

          解決問(wèn)題:

          我們分析一下,一般對(duì)執(zhí)行結(jié)果的有哪些處理,有可能將結(jié)果不做任何處理就直接返回,也有可能將結(jié)果轉(zhuǎn)換成一個(gè)JavaBean對(duì)象返回、一個(gè)Map返回、一個(gè)List返回等,結(jié)果處理可能是多種多樣的。從這里看,我們必須告訴SQL處理器兩點(diǎn):第一,需要返回什么類(lèi)型的對(duì)象;第二,需要返回的對(duì)象的數(shù)據(jù)結(jié)構(gòu)怎么跟執(zhí)行的結(jié)果映射,這樣才能將具體的值copy到對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)上。

          接下來(lái),我們可以進(jìn)而考慮對(duì)SQL執(zhí)行結(jié)果的緩存來(lái)提升性能。緩存數(shù)據(jù)都是key-value的格式,那么這個(gè)key怎么來(lái)呢?怎么保證唯一呢?即使同一條SQL語(yǔ)句幾次訪問(wèn)的過(guò)程中由于傳入?yún)?shù)的不同,得到的執(zhí)行SQL語(yǔ)句也是不同的。那么緩存起來(lái)的時(shí)候是多對(duì)。但是SQL語(yǔ)句和傳入?yún)?shù)兩部分合起來(lái)可以作為數(shù)據(jù)緩存的key值。

          3.5 第五步優(yōu)化:解決重復(fù)SQL語(yǔ)句問(wèn)題##

          問(wèn)題描述:

          由于我們將所有SQL語(yǔ)句都放到配置文件中,這個(gè)時(shí)候會(huì)遇到一個(gè)SQL重復(fù)的問(wèn)題,幾個(gè)功能的SQL語(yǔ)句其實(shí)都差不多,有些可能是SELECT后面那段不同、有些可能是WHERE語(yǔ)句不同。有時(shí)候表結(jié)構(gòu)改了,那么我們就需要改多個(gè)地方,不利于維護(hù)。

          解決問(wèn)題:

          當(dāng)我們的代碼程序出現(xiàn)重復(fù)代碼時(shí)怎么辦?將重復(fù)的代碼抽離出來(lái)成為獨(dú)立的一個(gè)類(lèi),然后在各個(gè)需要使用的地方進(jìn)行引用。對(duì)于SQL重復(fù)的問(wèn)題,我們也可以采用這種方式,通過(guò)將SQL片段模塊化,將重復(fù)的SQL片段獨(dú)立成一個(gè)SQL塊,然后在各個(gè)SQL語(yǔ)句引用重復(fù)的SQL塊,這樣需要修改時(shí)只需要修改一處即可。

          Part4Mybaits有待改進(jìn)之處MyBatis框架整體設(shè)計(jì)

          問(wèn)題描述:

          Mybaits所有的數(shù)據(jù)庫(kù)操作都是基于SQL語(yǔ)句,導(dǎo)致什么樣的數(shù)據(jù)庫(kù)操作都要寫(xiě)SQL語(yǔ)句。一個(gè)應(yīng)用系統(tǒng)要寫(xiě)的SQL語(yǔ)句實(shí)在太多了。

          改進(jìn)方法:

          我們對(duì)數(shù)據(jù)庫(kù)進(jìn)行的操作大部分都是對(duì)表數(shù)據(jù)的增刪改查,很多都是對(duì)單表的數(shù)據(jù)進(jìn)行操作,由這點(diǎn)我們可以想到一個(gè)問(wèn)題:?jiǎn)伪聿僮骺刹豢梢圆粚?xiě)SQL語(yǔ)句,通過(guò)JavaBean的默認(rèn)映射器生成對(duì)應(yīng)的SQL語(yǔ)句,比如:一個(gè)類(lèi)UserInfo對(duì)應(yīng)于USER_INFO表, userId屬性對(duì)應(yīng)于USER_ID字段。這樣我們就可以通過(guò)反射可以獲取到對(duì)應(yīng)的表結(jié)構(gòu)了,拼湊成對(duì)應(yīng)的SQL語(yǔ)句顯然不是問(wèn)題。

          Part5MyBatis框架整體設(shè)計(jì)

          MyBatis框架整體設(shè)計(jì)

          15.1 接口層-和數(shù)據(jù)庫(kù)交互的方式

          MyBatis和數(shù)據(jù)庫(kù)的交互有兩種方式:

          使用傳統(tǒng)的MyBatis提供的API;

          使用Mapper接口;

          5.1.1 使用傳統(tǒng)的MyBatis提供的API

          這是傳統(tǒng)的傳遞Statement Id 和查詢參數(shù)給 SqlSession 對(duì)象,使用 SqlSession對(duì)象完成和數(shù)據(jù)庫(kù)的交互;MyBatis提供了非常方便和簡(jiǎn)單的API,供用戶實(shí)現(xiàn)對(duì)數(shù)據(jù)庫(kù)的增刪改查數(shù)據(jù)操作,以及對(duì)數(shù)據(jù)庫(kù)連接信息和MyBatis 自身配置信息的維護(hù)操作。上述使用MyBatis 的方法,是創(chuàng)建一個(gè)和數(shù)據(jù)庫(kù)打交道的SqlSession對(duì)象,然后根據(jù)Statement Id 和參數(shù)來(lái)操作數(shù)據(jù)庫(kù),這種方式固然很簡(jiǎn)單和實(shí)用,但是它不符合面向?qū)ο笳Z(yǔ)言的概念和面向接口編程的編程習(xí)慣。由于面向接口的編程是面向?qū)ο蟮拇筅厔?shì),MyBatis 為了適應(yīng)這一趨勢(shì),增加了第二種使用MyBatis 支持接口(Interface)調(diào)用方式。

          5.1.2 使用Mapper接口

          MyBatis 將配置文件中的每一個(gè)節(jié)點(diǎn)抽象為一個(gè) Mapper 接口:

          這個(gè)接口中聲明的方法和節(jié)點(diǎn)中的<select|update|delete|insert> 節(jié)點(diǎn)項(xiàng)對(duì)應(yīng),即<select|update|delete|insert> 節(jié)點(diǎn)的id值為Mapper 接口中的方法名稱(chēng),parameterType 值表示Mapper 對(duì)應(yīng)方法的入?yún)㈩?lèi)型,而resultMap 值則對(duì)應(yīng)了Mapper 接口表示的返回值類(lèi)型或者返回結(jié)果集的元素類(lèi)型。

          根據(jù)MyBatis 的配置規(guī)范配置好后,通過(guò)SqlSession.getMapper(XXXMapper.class)方法,MyBatis 會(huì)根據(jù)相應(yīng)的接口聲明的方法信息,通過(guò)動(dòng)態(tài)代理機(jī)制生成一個(gè)Mapper 實(shí)例,我們使用Mapper接口的某一個(gè)方法時(shí),MyBatis會(huì)根據(jù)這個(gè)方法的方法名和參數(shù)類(lèi)型,確定Statement Id,底層還是通過(guò)SqlSession.select("statementId",parameterObject);或者SqlSession.update("statementId",parameterObject); 等等來(lái)實(shí)現(xiàn)對(duì)數(shù)據(jù)庫(kù)的操作,MyBatis引用Mapper 接口這種調(diào)用方式,純粹是為了滿足面向接口編程的需要。(其實(shí)還有一個(gè)原因是在于,面向接口的編程,使得用戶在接口上可以使用注解來(lái)配置SQL語(yǔ)句,這樣就可以脫離XML配置文件,實(shí)現(xiàn)“0配置”)。

          2數(shù)據(jù)處理層

          數(shù)據(jù)處理層可以說(shuō)是MyBatis的核心,從大的方面上講,它要完成兩個(gè)功能:

          通過(guò)傳入?yún)?shù)構(gòu)建動(dòng)態(tài)SQL語(yǔ)句;

          SQL語(yǔ)句的執(zhí)行以及封裝查詢結(jié)果集成List

          5.2.1 參數(shù)映射和動(dòng)態(tài)SQL語(yǔ)句生成

          動(dòng)態(tài)語(yǔ)句生成可以說(shuō)是MyBatis框架非常優(yōu)雅的一個(gè)設(shè)計(jì),MyBatis 通過(guò)傳入的參數(shù)值,使用 Ognl 來(lái)動(dòng)態(tài)地構(gòu)造SQL語(yǔ)句,使得MyBatis 有很強(qiáng)的靈活性和擴(kuò)展性。

          參數(shù)映射指的是對(duì)于java 數(shù)據(jù)類(lèi)型和jdbc數(shù)據(jù)類(lèi)型之間的轉(zhuǎn)換:這里有包括兩個(gè)過(guò)程:查詢階段,我們要將java類(lèi)型的數(shù)據(jù),轉(zhuǎn)換成jdbc類(lèi)型的數(shù)據(jù),通過(guò) preparedStatement.setXXX() 來(lái)設(shè)值;另一個(gè)就是對(duì)resultset查詢結(jié)果集的jdbcType 數(shù)據(jù)轉(zhuǎn)換成java 數(shù)據(jù)類(lèi)型。

          5.2.2 SQL語(yǔ)句的執(zhí)行以及封裝查詢結(jié)果集成List

          動(dòng)態(tài)SQL語(yǔ)句生成之后,MyBatis 將執(zhí)行SQL語(yǔ)句,并將可能返回的結(jié)果集轉(zhuǎn)換成List列表。MyBatis 在對(duì)結(jié)果集的處理中,支持結(jié)果集關(guān)系一對(duì)多和多對(duì)一的轉(zhuǎn)換,并且有兩種支持方式,一種為嵌套查詢語(yǔ)句的查詢,還有一種是嵌套結(jié)果集的查詢。

          3框架支撐層

          事務(wù)管理機(jī)制 事務(wù)管理機(jī)制對(duì)于ORM框架而言是不可缺少的一部分,事務(wù)管理機(jī)制的質(zhì)量也是考量一個(gè)ORM框架是否優(yōu)秀的一個(gè)標(biāo)準(zhǔn)。

          連接池管理機(jī)制

          由于創(chuàng)建一個(gè)數(shù)據(jù)庫(kù)連接所占用的資源比較大,對(duì)于數(shù)據(jù)吞吐量大和訪問(wèn)量非常大的應(yīng)用而言,連接池的設(shè)計(jì)就顯得非常重要。

          緩存機(jī)制

          為了提高數(shù)據(jù)利用率和減小服務(wù)器和數(shù)據(jù)庫(kù)的壓力,MyBatis 會(huì)對(duì)于一些查詢提供會(huì)話級(jí)別的數(shù)據(jù)緩存,會(huì)將對(duì)某一次查詢,放置到SqlSession 中,在允許的時(shí)間間隔內(nèi),對(duì)于完全相同的查詢,MyBatis會(huì)直接將緩存結(jié)果返回給用戶,而不用再到數(shù)據(jù)庫(kù)中查找。

          SQL語(yǔ)句的配置方式

          傳統(tǒng)的MyBatis 配置SQL語(yǔ)句方式就是使用XML文件進(jìn)行配置的,但是這種方式不能很好地支持面向接口編程的理念,為了支持面向接口的編程,MyBatis 引入了Mapper接口的概念,面向接口的引入,對(duì)使用注解來(lái)配置SQL語(yǔ)句成為可能,用戶只需要在接口上添加必要的注解即可,不用再去配置XML文件了,但是,目前的MyBatis 只是對(duì)注解配置SQL語(yǔ)句提供了有限的支持,某些高級(jí)功能還是要依賴(lài)XML配置文件配置SQL 語(yǔ)句。

          4引導(dǎo)層

          引導(dǎo)層是配置和啟動(dòng)MyBatis配置信息的方式。MyBatis 提供兩種方式來(lái)引導(dǎo)MyBatis :基于XML配置文件的方式和基于Java API 的方式。

          5主要構(gòu)件及其相互關(guān)系

          從MyBatis代碼實(shí)現(xiàn)的角度來(lái)看,MyBatis的主要的核心部件有以下幾個(gè):

          SqlSession:作為MyBatis工作的主要頂層API,表示和數(shù)據(jù)庫(kù)交互的會(huì)話,完成必要數(shù)據(jù)庫(kù)增刪改查功能;

          Executor:MyBatis執(zhí)行器,是MyBatis 調(diào)度的核心,負(fù)責(zé)SQL語(yǔ)句的生成和查詢緩存的維護(hù);

          StatementHandler:封裝了JDBC Statement操作,負(fù)責(zé)對(duì)JDBC statement 的操作,如設(shè)置參數(shù)、將Statement結(jié)果集轉(zhuǎn)換成List集合。

          ParameterHandler:負(fù)責(zé)對(duì)用戶傳遞的參數(shù)轉(zhuǎn)換成JDBC Statement 所需要的參數(shù);

          ResultSetHandler:負(fù)責(zé)將JDBC返回的ResultSet結(jié)果集對(duì)象轉(zhuǎn)換成List類(lèi)型的集合;

          TypeHandler:負(fù)責(zé)java數(shù)據(jù)類(lèi)型和jdbc數(shù)據(jù)類(lèi)型之間的映射和轉(zhuǎn)換;

          MappedStatement:MappedStatement維護(hù)了一條<select|update|delete|insert>節(jié)點(diǎn)的封裝;

          SqlSource:負(fù)責(zé)根據(jù)用戶傳遞的parameterObject,動(dòng)態(tài)地生成SQL語(yǔ)句,將信息封裝到BoundSql對(duì)象中,并返回;

          BoundSql:表示動(dòng)態(tài)生成的SQL語(yǔ)句以及相應(yīng)的參數(shù)信息;

          Configuration:MyBatis所有的配置信息都維持在Configuration對(duì)象之中;

          它們的關(guān)系如下圖所示:

          Part6SqlSession工作過(guò)程分析

          開(kāi)啟一個(gè)數(shù)據(jù)庫(kù)訪問(wèn)會(huì)話---創(chuàng)建SqlSession對(duì)象

          SqlSession sqlSession = factory.openSession(); 
          MyBatis封裝了對(duì)數(shù)據(jù)庫(kù)的訪問(wèn),把對(duì)數(shù)據(jù)庫(kù)的會(huì)話和事務(wù)控制放到了SqlSession對(duì)象中

          為SqlSession傳遞一個(gè)配置的Sql語(yǔ)句的Statement Id和參數(shù),然后返回結(jié)果:

          List<Employee> result = sqlSession.selectList("com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary",params);

          上述的"com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary",是配置在EmployeesMapper.xml 的Statement ID,params是傳遞的查詢參數(shù)。

          讓我們來(lái)看一下sqlSession.selectList()方法的定義:

          public <E> List<E> selectList(String statement, Object parameter) {  
              return this.selectList(statement, parameter, RowBounds.DEFAULT);  
          }  
          public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {  
              try {  
                  //1.根據(jù)Statement Id,在mybatis 配置對(duì)象Configuration中查找和配置文件相對(duì)應(yīng)的MappedStatement      
                  MappedStatement ms = configuration.getMappedStatement(statement);  
                  //2. 將查詢?nèi)蝿?wù)委托給MyBatis 的執(zhí)行器 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();  
              }  

          MyBatis在初始化的時(shí)候,會(huì)將MyBatis的配置信息全部加載到內(nèi)存中,使用org.apache.ibatis.session.Configuration實(shí)例來(lái)維護(hù)。使用者可以使用sqlSession.getConfiguration()方法來(lái)獲取。MyBatis的配置文件中配置信息的組織格式和內(nèi)存中對(duì)象的組織格式幾乎完全對(duì)應(yīng)的。

          上述例子中的:

          <select id="selectByMinSalary" resultMap="BaseResultMap" parameterType="java.util.Map" >  
             select   
                 EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY  
             from LOUIS.EMPLOYEES  
             <if test="min_salary != null">  
                 where SALARY < #{min_salary,jdbcType=DECIMAL}  
             </if>  
          </select>

          加載到內(nèi)存中會(huì)生成一個(gè)對(duì)應(yīng)的MappedStatement對(duì)象,然后會(huì)以key="com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary" ,value為MappedStatement對(duì)象的形式維護(hù)到Configuration的一個(gè)Map中。當(dāng)以后需要使用的時(shí)候,只需要通過(guò)Id值來(lái)獲取就可以了。

          從上述的代碼中我們可以看到SqlSession的職能是:SqlSession根據(jù)Statement ID, 在mybatis配置對(duì)象Configuration中獲取到對(duì)應(yīng)的MappedStatement對(duì)象,然后調(diào)用mybatis執(zhí)行器來(lái)執(zhí)行具體的操作。

          MyBatis執(zhí)行器Executor根據(jù)SqlSession傳遞的參數(shù)執(zhí)行query()方法(由于代碼過(guò)長(zhǎng),讀者只需閱讀我注釋的地方即可):

          /** 
             * BaseExecutor 類(lèi)部分代碼 
             * 
             */
            
          public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
                // 1. 根據(jù)具體傳入的參數(shù),動(dòng)態(tài)地生成需要執(zhí)行的SQL語(yǔ)句,用BoundSql對(duì)象表示    
                BoundSql boundSql = ms.getBoundSql(parameter);  
                // 2. 為當(dāng)前的查詢創(chuàng)建一個(gè)緩存Key  
                CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);  
                return query(ms, parameter, rowBounds, resultHandler, key, boundSql);  
          }  
           
          @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 {  
                         // 3.緩存中沒(méi)有值,直接從數(shù)據(jù)庫(kù)中讀取數(shù)據(jù)    
                         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 {  
                   
                    //4. 執(zhí)行查詢,返回List 結(jié)果,然后    將查詢的結(jié)果放入緩存之中  
                    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;  

          /** 
             * 
             * SimpleExecutor類(lèi)的doQuery()方法實(shí)現(xiàn) 
             * 
             */
            
          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();  
                    //5. 根據(jù)既有的參數(shù),創(chuàng)建StatementHandler對(duì)象來(lái)執(zhí)行查詢操作  
                    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);  
                    //6. 創(chuàng)建java.Sql.Statement對(duì)象,傳遞給StatementHandler對(duì)象  
                    stmt = prepareStatement(handler, ms.getStatementLog());  
                    //7. 調(diào)用StatementHandler.query()方法,返回List結(jié)果集  
                    return handler.<E>query(stmt, resultHandler);  
                 } finally {  
                     closeStatement(stmt);  
                 }  
          }

          上述的Executor.query()方法幾經(jīng)轉(zhuǎn)折,最后會(huì)創(chuàng)建一個(gè)StatementHandler對(duì)象,然后將必要的參數(shù)傳遞給StatementHandler,使用StatementHandler來(lái)完成對(duì)數(shù)據(jù)庫(kù)的查詢,最終返回List結(jié)果集。

          從上面的代碼中我們可以看出,Executor的功能和作用是:

          根據(jù)傳遞的參數(shù),完成SQL語(yǔ)句的動(dòng)態(tài)解析,生成BoundSql對(duì)象,供StatementHandler使用;

          為查詢創(chuàng)建緩存,以提高性能;

          創(chuàng)建JDBC的Statement連接對(duì)象,傳遞給StatementHandler對(duì)象,返回List查詢結(jié)果;

          StatementHandler對(duì)象負(fù)責(zé)設(shè)置Statement對(duì)象中的查詢參數(shù)、處理JDBC返回的resultSet,將resultSet加工為L(zhǎng)ist 集合返回:接著上面的Executor第六步,看一下:prepareStatement() 方法的實(shí)現(xiàn):

          /** 
             * 
             * SimpleExecutor類(lèi)的doQuery()方法實(shí)現(xiàn) 
             * 
             */
            
          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); 
                    // 1.準(zhǔn)備Statement對(duì)象,并設(shè)置Statement對(duì)象的參數(shù) 
                    stmt = prepareStatement(handler, ms.getStatementLog()); 
                    // 2. StatementHandler執(zhí)行query()方法,返回List結(jié)果 
                    return handler.<E>query(stmt, resultHandler); 
                } finally {
                    closeStatement(stmt); 
                } 
          }  
          private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
                Statement stmt;  
                Connection connection = getConnection(statementLog);  
                stmt = handler.prepare(connection);  
                //對(duì)創(chuàng)建的Statement對(duì)象設(shè)置參數(shù),即設(shè)置SQL 語(yǔ)句中 ? 設(shè)置為指定的參數(shù)  
                handler.parameterize(stmt);  
                return stmt;  
          }

          以上我們可以總結(jié)StatementHandler對(duì)象主要完成兩個(gè)工作:

          對(duì)于JDBC的PreparedStatement類(lèi)型的對(duì)象,創(chuàng)建的過(guò)程中,我們使用的是SQL語(yǔ)句字符串會(huì)包含 若干個(gè)? 占位符,我們其后再對(duì)占位符進(jìn)行設(shè)值。StatementHandler通過(guò)parameterize(statement)方法對(duì)Statement進(jìn)行設(shè)值;

          StatementHandler通過(guò)Listquery(Statement statement, ResultHandler resultHandler)方法來(lái)完成執(zhí)行Statement,和將Statement對(duì)象返回的resultSet封裝成List;

          StatementHandler 的parameterize(statement) 方法的實(shí)現(xiàn):

          /** 
             * StatementHandler 類(lèi)的parameterize(statement) 方法實(shí)現(xiàn)  
             */
            
          public void parameterize(Statement statement) throws SQLException {  
                //使用ParameterHandler對(duì)象來(lái)完成對(duì)Statement的設(shè)值    
                parameterHandler.setParameters((PreparedStatement) statement);  
          }  
          /** 
             *  
             * ParameterHandler類(lèi)的setParameters(PreparedStatement ps) 實(shí)現(xiàn) 
             * 對(duì)某一個(gè)Statement進(jìn)行設(shè)置參數(shù) 
             */
            
          public void setParameters(PreparedStatement ps) throws SQLException {  
                ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());  
                List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();  
                if (parameterMappings != null) {  
                    for (int i = 0; i < parameterMappings.size(); i++) {  
                        ParameterMapping parameterMapping = parameterMappings.get(i);  
                        if (parameterMapping.getMode() != ParameterMode.OUT) {  
                            Object value;  
                            String propertyName = parameterMapping.getProperty();  
                            if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params  
                                value = boundSql.getAdditionalParameter(propertyName);  
                            } else if (parameterObject == null) {  
                                value = null;  
                            } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {  
                                value = parameterObject;  
                            } else {  
                                MetaObject metaObject = configuration.newMetaObject(parameterObject);  
                                value = metaObject.getValue(propertyName);  
                            }  
                   
                            // 每一個(gè)Mapping都有一個(gè)TypeHandler,根據(jù)TypeHandler來(lái)對(duì)preparedStatement進(jìn)行設(shè)置參數(shù)  
                            TypeHandler typeHandler = parameterMapping.getTypeHandler();  
                            JdbcType jdbcType = parameterMapping.getJdbcType();  
                            if (value == null && jdbcType == null) jdbcType = configuration.getJdbcTypeForNull();  
                            // 設(shè)置參數(shù)  
                            typeHandler.setParameter(ps, i + 1, value, jdbcType);  
                        }  
                    }  
                }  
          }

          從上述的代碼可以看到,StatementHandler的parameterize(Statement) 方法調(diào)用了 ParameterHandler的setParameters(statement) 方法, ParameterHandler的setParameters(Statement)方法負(fù)責(zé) 根據(jù)我們輸入的參數(shù),對(duì)statement對(duì)象的 ? 占位符處進(jìn)行賦值。

          StatementHandler 的Listquery(Statement statement, ResultHandler resultHandler)方法的實(shí)現(xiàn):

           /** 
              * PreParedStatement類(lèi)的query方法實(shí)現(xiàn) 
              */
            
          public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {  
                //1.調(diào)用preparedStatemnt。execute()方法,然后將resultSet交給ResultSetHandler處理    
                PreparedStatement ps = (PreparedStatement) statement;  
                ps.execute();  
                //2. 使用ResultHandler來(lái)處理ResultSet  
                return resultSetHandler.<E> handleResultSets(ps);  
          }  

          從上述代碼我們可以看出,StatementHandler 的Listquery(Statement statement, ResultHandler resultHandler)方法的實(shí)現(xiàn),是調(diào)用了ResultSetHandler的handleResultSets(Statement) 方法。ResultSetHandler的handleResultSets(Statement) 方法會(huì)將Statement語(yǔ)句執(zhí)行后生成的resultSet 結(jié)果集轉(zhuǎn)換成List結(jié)果集:

          /**   
             * ResultSetHandler類(lèi)的handleResultSets()方法實(shí)現(xiàn) 
             *  
             */
            
          public List<Object> handleResultSets(Statement stmt) throws SQLException {  
                final List<Object> multipleResults = new ArrayList<Object>();  
           
                int resultSetCount = 0;  
                ResultSetWrapper rsw = getFirstResultSet(stmt);  
           
                List<ResultMap> resultMaps = mappedStatement.getResultMaps();  
                int resultMapCount = resultMaps.size();  
                validateResultMapsCount(rsw, resultMapCount);  
               
                while (rsw != null && resultMapCount > resultSetCount) {  
                    ResultMap resultMap = resultMaps.get(resultSetCount);  
                 
                    //將resultSet  
                    handleResultSet(rsw, resultMap, multipleResults, null);  
                    rsw = getNextResultSet(stmt);  
                    cleanUpAfterHandlingResultSet();  
                    resultSetCount++;  
                }
           
                String[] resultSets = mappedStatement.getResulSets();  
                if (resultSets != null) {  
                    while (rsw != null && resultSetCount < resultSets.length) {  
                        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);  
                        if (parentMapping != null) {  
                            String nestedResultMapId = parentMapping.getNestedResultMapId();  
                            ResultMap resultMap = configuration.getResultMap(nestedResultMapId);  
                            handleResultSet(rsw, resultMap, null, parentMapping);  
                        }  
                        rsw = getNextResultSet(stmt);  
                        cleanUpAfterHandlingResultSet();  
                        resultSetCount++;  
                    }  
                }  
           
                return collapseSingleResultList(multipleResults);  
          }  

          Part7MyBatis初始化機(jī)制

          7.1 MyBatis的初始化做了什么

          任何框架的初始化,無(wú)非是加載自己運(yùn)行時(shí)所需要的配置信息。MyBatis的配置信息,大概包含以下信息,其高層級(jí)結(jié)構(gòu)如下:

          MyBatis的上述配置信息會(huì)配置在XML配置文件中,那么,這些信息被加載進(jìn)入MyBatis內(nèi)部,MyBatis是怎樣維護(hù)的呢?

          MyBatis采用了一個(gè)非常直白和簡(jiǎn)單的方式---使用 org.apache.ibatis.session.Configuration對(duì)象作為一個(gè)所有配置信息的容器,Configuration對(duì)象的組織結(jié)構(gòu)和XML配置文件的組織結(jié)構(gòu)幾乎完全一樣(當(dāng)然,Configuration對(duì)象的功能并不限于此,它還負(fù)責(zé)創(chuàng)建一些MyBatis內(nèi)部使用的對(duì)象,如Executor等,這將在后續(xù)的文章中討論)。如下圖所示:

          MyBatis根據(jù)初始化好Configuration信息,這時(shí)候用戶就可以使用MyBatis進(jìn)行數(shù)據(jù)庫(kù)操作了。可以這么說(shuō),MyBatis初始化的過(guò)程,就是創(chuàng)建 Configuration對(duì)象的過(guò)程。

          MyBatis的初始化可以有兩種方式:

          基于XML配置文件:基于XML配置文件的方式是將MyBatis的所有配置信息放在XML文件中,MyBatis通過(guò)加載并XML配置文件,將配置文信息組裝成內(nèi)部的Configuration對(duì)象。

          基于Java API:這種方式不使用XML配置文件,需要MyBatis使用者在Java代碼中,手動(dòng)創(chuàng)建Configuration對(duì)象,然后將配置參數(shù)set 進(jìn)入Configuration對(duì)象中。

          接下來(lái)我們將通過(guò) 基于XML配置文件方式的MyBatis初始化,深入探討MyBatis是如何通過(guò)配置文件構(gòu)建Configuration對(duì)象,并使用它。

          7.2 基于XML配置文件創(chuàng)建Configuration對(duì)象

          現(xiàn)在就從使用MyBatis的簡(jiǎn)單例子入手,深入分析一下MyBatis是怎樣完成初始化的,都初始化了什么。看以下代碼:

          String resource = "mybatis-config.xml";  
          InputStream inputStream = Resources.getResourceAsStream(resource);  
          SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);  
          SqlSession sqlSession = sqlSessionFactory.openSession();  
          List list = sqlSession.selectList("com.foo.bean.BlogMapper.queryAllBlogInfo");

          有過(guò)MyBatis使用經(jīng)驗(yàn)的讀者會(huì)知道,上述語(yǔ)句的作用是執(zhí)行com.foo.bean.BlogMapper.queryAllBlogInfo 定義的SQL語(yǔ)句,返回一個(gè)List結(jié)果集。總的來(lái)說(shuō),上述代碼經(jīng)歷了mybatis初始化 -->創(chuàng)建SqlSession -->執(zhí)行SQL語(yǔ)句返回結(jié)果三個(gè)過(guò)程。

          上述代碼的功能是根據(jù)配置文件mybatis-config.xml 配置文件,創(chuàng)建SqlSessionFactory對(duì)象,然后產(chǎn)生SqlSession,執(zhí)行SQL語(yǔ)句。而mybatis的初始化就發(fā)生在第三句:SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 現(xiàn)在就讓我們看看第三句到底發(fā)生了什么。

          MyBatis初始化基本過(guò)程:

          SqlSessionFactoryBuilder根據(jù)傳入的數(shù)據(jù)流生成Configuration對(duì)象,然后根據(jù)Configuration對(duì)象創(chuàng)建默認(rèn)的SqlSessionFactory實(shí)例。

          初始化的基本過(guò)程如下序列圖所示:由上圖所示,mybatis初始化要經(jīng)過(guò)簡(jiǎn)單的以下幾步:

          調(diào)用SqlSessionFactoryBuilder對(duì)象的build(inputStream)方法;

          SqlSessionFactoryBuilder會(huì)根據(jù)輸入流inputStream等信息創(chuàng)建XMLConfigBuilder對(duì)象;

          SqlSessionFactoryBuilder調(diào)用XMLConfigBuilder對(duì)象的parse()方法;

          XMLConfigBuilder對(duì)象返回Configuration對(duì)象;

          SqlSessionFactoryBuilder根據(jù)Configuration對(duì)象創(chuàng)建一個(gè)DefaultSessionFactory對(duì)象;

          SqlSessionFactoryBuilder返回 DefaultSessionFactory對(duì)象給Client,供Client使用。

          SqlSessionFactoryBuilder相關(guān)的代碼如下所示:

          public SqlSessionFactory build(InputStream inputStream)  {  
                return build(inputStream, nullnull);  
          }  

          public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties)  {  
                try  {  
                    //2. 創(chuàng)建XMLConfigBuilder對(duì)象用來(lái)解析XML配置文件,生成Configuration對(duì)象  
                    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);  
                    //3. 將XML配置文件內(nèi)的信息解析成Java對(duì)象Configuration對(duì)象  
                    Configuration config = parser.parse();  
                    //4. 根據(jù)Configuration對(duì)象創(chuàng)建出SqlSessionFactory對(duì)象  
                    return build(config);  
                } 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.  
                    }  
                }
          }

          /* 從此處可以看出,MyBatis內(nèi)部通過(guò)Configuration對(duì)象來(lái)創(chuàng)建SqlSessionFactory,用戶也可以自己通過(guò)API構(gòu)造好Configuration對(duì)象,調(diào)用此方法創(chuàng)SqlSessionFactory  */
          public SqlSessionFactory build(Configuration config) {  
                return new DefaultSqlSessionFactory(config);  

          上述的初始化過(guò)程中,涉及到了以下幾個(gè)對(duì)象:

          SqlSessionFactoryBuilder :SqlSessionFactory的構(gòu)造器,用于創(chuàng)建SqlSessionFactory,采用了Builder設(shè)計(jì)模式

          Configuration :該對(duì)象是mybatis-config.xml文件中所有mybatis配置信息

          SqlSessionFactory:SqlSession工廠類(lèi),以工廠形式創(chuàng)建SqlSession對(duì)象,采用了Factory工廠設(shè)計(jì)模式

          XMLConfigBuilder :負(fù)責(zé)將mybatis-config.xml配置文件解析成Configuration對(duì)象,共SqlSessonFactoryBuilder使用,創(chuàng)建SqlSessionFactory

          創(chuàng)建Configuration對(duì)象的過(guò)程:接著上述的 MyBatis初始化基本過(guò)程討論,當(dāng)SqlSessionFactoryBuilder執(zhí)行build()方法,調(diào)用了XMLConfigBuilder的parse()方法,然后返回了Configuration對(duì)象。那么parse()方法是如何處理XML文件,生成Configuration對(duì)象的呢?(1)XMLConfigBuilder會(huì)將XML配置文件的信息轉(zhuǎn)換為Document對(duì)象,而XML配置定義文件DTD轉(zhuǎn)換成XMLMapperEntityResolver對(duì)象,然后將二者封裝到XpathParser對(duì)象中,XpathParser的作用是提供根據(jù)Xpath表達(dá)式獲取基本的DOM節(jié)點(diǎn)Node信息的操作。如下圖所示:(2)之后XMLConfigBuilder調(diào)用parse()方法:會(huì)從XPathParser中取出節(jié)點(diǎn)對(duì)應(yīng)的Node對(duì)象,然后解析此Node節(jié)點(diǎn)的子Node:properties, settings, typeAliases,typeHandlers, objectFactory, objectWrapperFactory, plugins, environments,databaseIdProvider, mappers:

          public Configuration parse() {  
               if (parsed) {  
                   throw new BuilderException("Each XMLConfigBuilder can only be used once.");  
               }  
               parsed = true;  
               //源碼中沒(méi)有這一句,只有parseConfiguration(parser.evalNode("/configuration"));  
               //為了讓讀者看得更明晰,源碼拆分為以下兩句  
               XNode configurationNode = parser.evalNode("/configuration");  
               parseConfiguration(configurationNode);  
               return configuration;
          }
          /** 
            * 解析 "/configuration"節(jié)點(diǎn)下的子節(jié)點(diǎn)信息,然后將解析的結(jié)果設(shè)置到Configuration對(duì)象中 
            */
            
          private void parseConfiguration(XNode root) {  
               try {  
                   //1.首先處理properties 節(jié)點(diǎn)     
                   propertiesElement(root.evalNode("properties")); //issue #117 read properties first  
                   //2.處理typeAliases  
                   typeAliasesElement(root.evalNode("typeAliases"));  
                   //3.處理插件  
                   pluginElement(root.evalNode("plugins"));  
                   //4.處理objectFactory  
                   objectFactoryElement(root.evalNode("objectFactory"));  
                   //5.objectWrapperFactory  
                   objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));  
                   //6.settings  
                   settingsElement(root.evalNode("settings"));  
                   //7.處理environments  
                   environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631  
                   //8.database  
                   databaseIdProviderElement(root.evalNode("databaseIdProvider"));  
                   //9.typeHandlers  
                   typeHandlerElement(root.evalNode("typeHandlers"));  
                   //10.mappers  
                   mapperElement(root.evalNode("mappers"));  
               } catch (Exception e) {  
                   throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);  
               }  
           }  

          注意:在上述代碼中,還有一個(gè)非常重要的地方,就是解析XML配置文件子節(jié)點(diǎn)的方法mapperElements(root.evalNode("mappers")), 它將解析我們配置的Mapper.xml配置文件,Mapper配置文件可以說(shuō)是MyBatis的核心,MyBatis的特性和理念都體現(xiàn)在此Mapper的配置和設(shè)計(jì)上。

          (3)然后將這些值解析出來(lái)設(shè)置到Configuration對(duì)象中:

          解析子節(jié)點(diǎn)的過(guò)程這里就不一一介紹了,用戶可以參照MyBatis源碼仔細(xì)揣摩,我們就看上述的environmentsElement(root.evalNode("environments")); 方法是如何將environments的信息解析出來(lái),設(shè)置到Configuration對(duì)象中的:

          /** 
            * 解析environments節(jié)點(diǎn),并將結(jié)果設(shè)置到Configuration對(duì)象中 
            * 注意:創(chuàng)建envronment時(shí),如果SqlSessionFactoryBuilder指定了特定的環(huán)境(即數(shù)據(jù)源); 
            *      則返回指定環(huán)境(數(shù)據(jù)源)的Environment對(duì)象,否則返回默認(rèn)的Environment對(duì)象; 
            *      這種方式實(shí)現(xiàn)了MyBatis可以連接多數(shù)據(jù)源 
            */
            
          private void environmentsElement(XNode context) throws Exception {  
              if (context != null)  
              {  
                   if (environment == null)  
                   {  
                       environment = context.getStringAttribute("default");  
                   }  
                   for (XNode child : context.getChildren())  
                   {  
                        String id = child.getStringAttribute("id");  
                        if (isSpecifiedEnvironment(id))  
                        {  
                            //1.創(chuàng)建事務(wù)工廠 TransactionFactory  
                            TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));  
                            DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));  
                            //2.創(chuàng)建數(shù)據(jù)源DataSource  
                            DataSource dataSource = dsFactory.getDataSource();  
                            //3. 構(gòu)造Environment對(duì)象  
                            Environment.Builder environmentBuilder = new Environment.Builder(id)  
                       .transactionFactory(txFactory)  
                       .dataSource(dataSource);  
                            //4. 將創(chuàng)建的Envronment對(duì)象設(shè)置到configuration 對(duì)象中  
                            configuration.setEnvironment(environmentBuilder.build());  
                       }  
                   }  
              }  
          }
          private boolean isSpecifiedEnvironment(String id)  
          {  
                if (environment == null)  
                {  
                     throw new BuilderException("No environment specified.");  
                }  
                else if (id == null)  
                {  
                     throw new BuilderException("Environment requires an id attribute.");  
                }  
                else if (environment.equals(id))  
                {  
                    return true;  
                }  
                return false;  
           }  

          (4)返回Configuration對(duì)象:

          將上述的MyBatis初始化基本過(guò)程的序列圖細(xì)化:

          7.3 基于Java API手動(dòng)加載XML配置文件創(chuàng)建Configuration對(duì)象,并使用SqlSessionFactory對(duì)象

          我們可以使用XMLConfigBuilder手動(dòng)解析XML配置文件來(lái)創(chuàng)建Configuration對(duì)象,代碼如下:

          String resource = "mybatis-config.xml";  
          InputStream inputStream = Resources.getResourceAsStream(resource);  
          // 手動(dòng)創(chuàng)建XMLConfigBuilder,并解析創(chuàng)建Configuration對(duì)象  
          XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, null,null);  
          Configuration configuration=parse();  
          // 使用Configuration對(duì)象創(chuàng)建SqlSessionFactory  
          SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);  
          // 使用MyBatis  
          SqlSession sqlSession = sqlSessionFactory.openSession();  
          List list = sqlSession.selectList("com.foo.bean.BlogMapper.queryAllBlogInfo");  
          7.4 涉及到的設(shè)計(jì)模式##
          初始化的過(guò)程涉及到創(chuàng)建各種對(duì)象,所以會(huì)使用一些創(chuàng)建型的設(shè)計(jì)模式。在初始化的過(guò)程中,Builder模式運(yùn)用的比較多。

          7.4.1 Builder模式應(yīng)用1:SqlSessionFactory的創(chuàng)建

          對(duì)于創(chuàng)建SqlSessionFactory時(shí),會(huì)根據(jù)情況提供不同的參數(shù),其參數(shù)組合可以有以下幾種:

          由于構(gòu)造時(shí)參數(shù)不定,可以為其創(chuàng)建一個(gè)構(gòu)造器Builder,將SqlSessionFactory的構(gòu)建過(guò)程和表示分開(kāi):

          7.4.2 Builder模式應(yīng)用2:數(shù)據(jù)庫(kù)連接環(huán)境Environment對(duì)象的創(chuàng)建

          在構(gòu)建Configuration對(duì)象的過(guò)程中,XMLConfigBuilder解析 mybatis XML配置文件節(jié)點(diǎn)節(jié)點(diǎn)時(shí),會(huì)有以下相應(yīng)的代碼:

          private void environmentsElement(XNode context) throws Exception {  
              if (context != null) {  
                  if (environment == null) {  
                      environment = context.getStringAttribute("default");  
                  }  
                  for (XNode child : context.getChildren()) {  
                      String id = child.getStringAttribute("id");  
                      //是和默認(rèn)的環(huán)境相同時(shí),解析之  
                      if (isSpecifiedEnvironment(id)) {  
                          TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));  
                          DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));  
                          DataSource dataSource = dsFactory.getDataSource();  
            
                          //使用了Environment內(nèi)置的構(gòu)造器Builder,傳遞id 事務(wù)工廠和數(shù)據(jù)源  
                          Environment.Builder environmentBuilder = new Environment.Builder(id)  
                          .transactionFactory(txFactory)  
                          .dataSource(dataSource);  
                          configuration.setEnvironment(environmentBuilder.build());  
                      }  
                  }  
              }  
          }  

          在Environment內(nèi)部,定義了靜態(tài)內(nèi)部Builder類(lèi):

          public final class Environment {  
              private final String id;  
              private final TransactionFactory transactionFactory;  
              private final DataSource dataSource;  
            
              public Environment(String id, TransactionFactory transactionFactory, DataSource dataSource) {  
                  if (id == null) {  
                      throw new IllegalArgumentException("Parameter 'id' must not be null");  
                  }  
                  if (transactionFactory == null) {  
                      throw new IllegalArgumentException("Parameter 'transactionFactory' must not be null");  
                  }  
                  this.id = id;  
                  if (dataSource == null) {  
                      throw new IllegalArgumentException("Parameter 'dataSource' must not be null");  
                  }  
                  this.transactionFactory = transactionFactory;  
                  this.dataSource = dataSource;  
              }  
            
              public static class Builder {  
                  private String id;  
                  private TransactionFactory transactionFactory;  
                  private DataSource dataSource;  
            
                  public Builder(String id) {  
                      this.id = id;  
                  }  
            
                  public Builder transactionFactory(TransactionFactory transactionFactory) {  
                      this.transactionFactory = transactionFactory;  
                      return this;  
                  }  
            
                  public Builder dataSource(DataSource dataSource) {  
                      this.dataSource = dataSource;  
                      return this;  
                  }  
            
                  public String id() {  
                      return this.id;  
                  }  
            
                  public Environment build() {  
                      return new Environment(this.id, this.transactionFactory, this.dataSource);  
                  }  
              }  
            
              public String getId() {  
                  return this.id;  
              }  
            
              public TransactionFactory getTransactionFactory() {  
                  return this.transactionFactory;  
              }  
            
              public DataSource getDataSource() {  
                  return this.dataSource;  
              }
          }
          送書(shū)活動(dòng)
          首先,感謝北京大學(xué)出版社 為 "Java學(xué)習(xí)之道" 提供的書(shū)籍贊助,非常感謝!后續(xù)公眾號(hào)頭條推文,1周至少會(huì)有1-2次的文末送書(shū)活動(dòng),大家記得看完文章后,多多參與送書(shū)哈,混臉熟也能中獎(jiǎng)

          《Java核心技術(shù)及面試指南》

          一本書(shū)講透Java語(yǔ)言核心概念、編程思想、開(kāi)發(fā)實(shí)戰(zhàn)及面試技巧 爆賣(mài)7w冊(cè)的漫畫(huà)算法書(shū)進(jìn)階版,小倉(cāng)鼠帶你輕松學(xué)算法,拿大廠offer!


          可點(diǎn)擊下方鏈接直接購(gòu)買(mǎi)
          ?? 免費(fèi)獲取方法:

          6月2日前公眾號(hào)后臺(tái)回復(fù) 【 java學(xué)習(xí) 即可參與活動(dòng)!!!

          掃碼回復(fù)「java學(xué)習(xí)」抽獎(jiǎng)品

          沒(méi)加小編微信的建議先加一下小編微信,方便中獎(jiǎng)之后安排發(fā)貨和領(lǐng)

          瀏覽 22
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  亚洲第一页中文字幕 | 91人妻天天操天天干 | 蜜桃视频欧美一区二区 | 日逼大全 | 免费黄色电影网址日韩 |