1. 程式人生 > >深入剖析 mybatis 原理(一)

深入剖析 mybatis 原理(一)

# 前言

在java程式設計師的世界裡,最熟悉的開源軟體除了 Spring,Tomcat,還有誰呢?當然是 Mybatis 了,今天樓主是來和大家一起分析他的原理的。

1. 回憶JDBC

首先,樓主想和大家一起回憶學習JDBC的那段時光:

package cn.think.in.java.jdbc;

public class JdbcDemo {

  private Connection getConnection() {
    Connection connection = null;
    try {
      Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver"
); String url = "jdbc:sqlserver://192.168.0.251:1433;DatabaseName=test"; String user = "sa"; String password = "$434343%"; connection = DriverManager.getConnection(url, user, password); } catch (Exception e) { e.printStackTrace(); } return connection; } public
UserInfo getRole(Long id) throws SQLException { Connection connection = getConnection(); PreparedStatement ps = null; ResultSet rs = null; try { ps = connection.prepareStatement("select * from user_info where id = ?"); ps.setLong(1, id); rs = ps.executeQuery(); while
(rs.next()) { Long roleId = rs.getLong("id"); String userName = rs.getString("username"); String realname = rs.getString("realname"); UserInfo userInfo = new UserInfo(); userInfo.id = roleId.intValue(); userInfo.username = userName; userInfo.realname = realname; return userInfo; } } catch (Exception e) { e.printStackTrace(); } finally { connection.close(); ps.close(); rs.close(); } return null; } public static void main(String[] args) throws SQLException { JdbcDemo jdbcDemo = new JdbcDemo(); UserInfo userInfo = jdbcDemo.getRole(1L); System.out.println(userInfo); } }

看著這麼多 try catch finally 是不是覺得很親切呢?只是現如今,我們再也不會這麼寫程式碼了,都是在Spring和Mybatis 中整合了,一個 userinfoMapper.selectOne(id) 方法就搞定了上面的這麼多程式碼,這都是我們今天的主角 Mybatis 的功勞,而他主要做的事情,就是封裝了上面的除SQL語句之外的重複程式碼,為什麼說是重複程式碼呢?因為這些程式碼,細想一下,都是不變的。

那麼,Mybatis 做了哪些事情呢?

實際上,Mybatis 只做了兩件事情:
1. 根據 JDBC 規範 建立與資料庫的連線。
2. 通過反射打通Java物件和資料庫引數和返回值之間相互轉化的關係。

2. 從 Mybatis 的一個 Demo 案例開始

此次樓主從 github 上 clone 了mybatis 的原始碼,過程比Spring原始碼順利,主要注意一點:在 IDEA 編輯器中(Eclipse 樓主不知道),需要排除 src/test/java/org/apache/ibatis/submitted 包,防止編譯錯誤。

樓主在原始碼中寫了一個Demo,給大家看一下目錄結構:

圖片中的紅框部分是樓主自己新增的,然後看看程式碼:

JavaBean程式碼

Mapper 介面程式碼

Main 測試類程式碼

再看看 mybatis-config.xml 配置檔案:

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

  <properties><!--定義屬性值-->
    <property name="driver" value="com.microsoft.sqlserver.jdbc.SQLServerDriver"/>
    <property name="url" value="jdbc:sqlserver://192.168.0.122:1433;DatabaseName=test"/>
    <property name="username" value="sa"/>
    <property name="password" value="434343"/>
  </properties>

  <settings>
    <setting name="cacheEnabled" value="true"/>
  </settings>

  <!-- 類型別名 -->
  <typeAliases>
    <typeAlias alias="userInfo" type="org.apache.ibatis.mybatis.UserInfo"/>
  </typeAliases>

  <!--環境-->
  <environments default="development">
    <environment id="development"><!--採用jdbc 的事務管理模式-->
      <transactionManager type="JDBC">
        <property name="..." value="..."/>
      </transactionManager>
      <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>

  <!--對映器  告訴 MyBatis 到哪裡去找到這些語句-->
  <mappers>
    <mapper resource="UserInfoMapper.xml"/>
  </mappers>

</configuration

UserInfoMapper.xml 配置檔案

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >

<mapper namespace="org.apache.ibatis.mybatis.UserInfoMapper">

  <select id="selectById" parameterType="int" resultType="org.apache.ibatis.mybatis.UserInfo">
    SELECT * FROM user_info  WHERE  id = #{id}
  </select>
</mapper>

好了,我們的測試程式碼就這麼多,執行一下測試類:

結果正確,列印了2次,因為我們使用了兩種不同的方式來執行SQL。

那麼,我們就從這個簡單的例子來看看 Mybatis 是如何執行的。

3. 深入原始碼之前的理論知識

再深入原始碼之前,樓主想先來一波理論知識,避免因進入原始碼的汪洋大海導致迷失方向。

首先, Mybatis 的執行可以分為2個部分,第一部分是讀取配置檔案建立 Configuration 物件, 用以建立 SqlSessionFactroy, 第二部分是 SQLSession 的執行過程.

我們再來看看我們的測試程式碼:

Main 測試類程式碼

這是一個和我們平時使用不同的方式, 但如果細心觀察,會發現, 實際上在 Spring 和 Mybatis 整合的框架中也是這麼使用的, 只是 Spring 的 IOC 機制幫助我們遮蔽了建立物件的過程而已. 如果我們忘記建立物件的過程, 這段程式碼就是我們平時使用的程式碼.

那麼,我們就來看看這段程式碼, 首先建立了一個流, 用於讀取配置檔案, 然後使用流作為引數, 使用 SqlSessionaFactoryBuilder 建立了一個 SqlSessionFactory 物件,然後使用該物件獲取一個 SqlSession, 呼叫 SqlSession 的 selectOne 方法 獲取了返回值,或者 呼叫了 SqlSession 的 getMapper 方法獲取了一個代理物件, 呼叫代理物件的 selectById 方法 獲取返回值.

在這裡, 樓主覺得有必要講講這幾個類的生命週期:
1. SqlSessionaFactoryBuilder 該類主要用於建立 SqlSessionFactory, 並給與一個流物件, 該類使用了建立者模式, 如果是手動建立該類(這種方式很少了,除非像樓主這種測試程式碼), 那麼建議在建立完畢之後立即銷燬.

  1. SqlSessionFactory 該類的作用了建立 SqlSession, 從名字上我們也能看出, 該類使用了工廠模式, 每次應用程式訪問資料庫, 我們就要通過 SqlSessionFactory 建立 SqlSession, 所以SqlSessionFactory 和整個 Mybatis 的生命週期是相同的. 這也告訴我們不同建立多個同一個資料的 SqlSessionFactory, 如果建立多個, 會消耗盡資料庫的連線資源, 導致伺服器夯機. 應當使用單例模式. 避免過多的連線被消耗, 也方便管理.

  2. SqlSession 那麼是什麼 SqlSession 呢? SqlSession 相當於一個會話, 就像 HTTP 請求中的會話一樣, 每次訪問資料庫都需要這樣一個會話, 大家可能會想起了 JDBC 中的 Connection, 很類似,但還是有區別的, 何況現在幾乎所有的連線都是使用的連線池技術, 用完後直接歸還而不會像 Session 一樣銷燬. 注意:他是一個執行緒不安全的物件, 在設計多執行緒的時候我們需要特別的當心, 操作資料庫需要注意其隔離級別, 資料庫鎖等高階特性, 此外, 每次建立的 SqlSession 都必須及時關閉它, 它長期存在就會使資料庫連線池的活動資源減少,對系統性能的影響很大, 我們一般在 finally 塊中將其關閉. 還有, SqlSession 存活於一個應用的請求和操作,可以執行多條 Sql, 保證事務的一致性.

  3. Mapper 對映器, 正如我們編寫的那樣, Mapper 是一個介面, 沒有任何實現類, 他的作用是傳送 SQL, 然後返回我們需要的結果. 或者執行 SQL 從而更改資料庫的資料, 因此它應該在 SqlSession 的事務方法之內, 在 Spring 管理的 Bean 中, Mapper 是單例的。

大家應該還看見了另一種方式, 就是上面的我們不常見到的方式,其實, 這個方法更貼近Mybatis底層原理,只是該方法還是不夠面向物件, 使用字串當key的方式也不易於IDE 檢查錯誤。我們常用的還是getMapper方法。

4. 開始深入原始碼

我們一行一行看。

首先根據maven的classes目錄下的配置檔案並建立流,然後建立 SqlSessionFactoryBuilder 物件,該類結構如下:

可以看到該類只有一個方法並且被過載了9次,而且沒有任何屬性,可見該類唯一的功能就是通過配置檔案建立 SqlSessionFactory。那我們就緊跟來看看他的build方法:

該方法,預設環境為null, 屬性也為null,呼叫了自己的另一個過載build方法,我們看看該方法。

  /**
   * 構建SqlSession 工廠
   *
   * @param inputStream xml 配置檔案
   * @param environment 預設null
   * @param properties 預設null
   * @return 工廠
   */
  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      // 建立XML解析器
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      // 建立 session 工廠
      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.
      }
    }
  }

可以看到該方法只有2個步驟,第一,根據給定的引數建立一個 XMLConfigBuilder XML配置物件,第二,呼叫過載的 build 方法。並將上一行返回的 Configuration 物件作為引數。我們首先看看建立 XMLConfigBuilder 的過程。

首先還是呼叫了自己的構造方法,引數是 XPathParser 物件, 環境(預設是null),Properties (預設是null),然後呼叫了父類的構造方法並傳入 Configuration 物件,注意,Configuration 的構造器做了很多的工作,或者說他的預設構造器做了很多的工作。我們看看他的預設構造器:

該構造器主要是註冊別名,並放入到一個HashMap中,這些別名在解析XML配置檔案的時候會用到。如果平時注意mybatis配置檔案的話,這些別名應該都非常的熟悉了。

我們回到 XMLConfigBuilderd 的構造方法中,也就是他的父類 BaseBuilder 構造方法,該方法如下:

主要是一些賦值過程,主要將剛剛建立的 Configuration 物件和他的屬性賦值到 XMLConfigBuilder 物件中。

我們回到 SqlSessionFactoryBuilder 的 build 方法中,此時已經建立了 XMLConfigBuilder 物件,並呼叫該物件的 parse 方法,我們看看該方法實現:

首先判斷了最多隻能解析一次,然後呼叫 XPathParser 的 evalNode 方法,該方法返回了 XNode 物件 ,而XNode 物件就和我們平時使用的 Dom4j 的 node 物件差不多,我們就不深究了,總之是解析XML 配置檔案,載入 DOM 樹,返回 DOM 節點物件。然後呼叫 parseConfiguration 方法,我們看看該方法:

該方法的作用是解析剛剛的DOM節點,可以看到我們熟悉的一些標籤,比如:properties,settings,objectWrapperFactory,mappers。我們重點看看最後一行 mapperElement 方法,其餘的方法,大家如果又興趣自己也可以看看,mapperElement 方法如下:

該方法迴圈了 mapper 元素,如果有 “package” 標籤,則獲取value值,並新增進對映器集合Map中,該Map如何儲存呢,找到包所有class,並將Class物件作為key,MapperProxyFactory 物件作為 value 儲存, MapperProxyFactory 類中有2個屬性,一個是 Class mapperInterface ,也就是介面的類名,一個 Map

5. SqlSession 建立過程

我們接下來要看看 SqlSession 的建立過程和執行過程,首先呼叫了 sqlSessionFactory.openSession() 方法。該方法預設實現類是 DefaultSqlSessionFactory ,我們看看該方法如何被重寫的。

呼叫了自身的 openSessionFromDataSource 方法,注意,引數中 configuration 獲取了預設的執行器 “SIMPLE”,自動提交我們沒有配置,預設是false,我們進入到 openSessionFromDataSource 方法檢視:

該方法以下幾個步驟:
1. 獲取配置檔案中的環境,也就是我們配置的 標籤,並根據環境獲取事務工廠,事務工廠會建立一個事務物件,而 configurationye 則會根據事務物件和執行器型別建立一個執行器。最後返回一個預設的 DefaultSqlSession 物件。 可以說,這段程式碼,就是根據配置檔案建立 SqlSession 的核心地帶。我們一步步看程式碼,首先從配置檔案中取出剛剛解析的環境物件。

然後根據環境物件獲取事務工廠,如果配置檔案中沒有配置,則建立一個 ManagedTransactionFactory 物件直接返回。否則呼叫環境物件的 getTransactionFactory 方法,該方法和我們配置的一樣返回了一個 JdbcTransactionFactory,而實際上,TransactionFactory 只有2個實現類,一個是 ManagedTransactionFactory ,一個是 JdbcTransactionFactory。

我們回到 openSessionFromDataSource 方法,獲取了 JdbcTransactionFactory 後,呼叫 JdbcTransactionFactory 的 newTransaction 方法建立一個事務物件,引數是資料來源,level 是null, 自動提交還是false。newTransaction 建立了一個 JdbcTransaction 物件,我們看看該類的構造:

可以看到,該類都是有關連線和事務的方法,比如commit,openConnection,rollback,和JDBC 的connection 功能很相似。而我們剛剛看到的level是什麼呢?在原始碼中我們看到了答案:

就是 “事務的隔離級別”。並且該事務物件還包含了JDBC 的Connection 物件和 DataSource 資料來源物件,好親切啊,可見這個事務物件就是JDBC的事務的封裝。

繼續回到 openSessionFromDataSource 方,法此時已經建立好事務物件。接下來將事務物件執行器作為引數執行 configuration 的 newExecutor 方法來獲取一個 執行器類。我們看看該方法實現:

首先,該方法判斷給定的執行型別是否為null,如果為null,則使用預設的執行器, 也就是 ExecutorType.SIMPLE,然後根據執行的型別來建立不同的執行器,預設是 SimpleExecutor 執行器,這裡樓主需要解釋以下執行器:

Mybatis有三種基本的Executor執行器,SimpleExecutor、ReuseExecutor、BatchExecutor。

  1. SimpleExecutor:每執行一次update或select,就開啟一個Statement物件,用完立刻關閉Statement物件。

  2. ReuseExecutor:執行update或select,以sql作為key查詢Statement物件,存在就使用,不存在就建立,用完後,不關閉Statement物件,而是放置於Map

6. SqlSession 執行過程

我們建立了一個map,並放入了引數,重點看紅框部分,我們鑽進去看看。selectOne 方法:

該方法實際上還是呼叫了selectList方法,最後取得了List中的第一個,如果返回值長度大於1,則丟擲異常。啊,原來,經常出現的異常就是這麼來的啊,終於知道你是怎麼回事了。我們也看的出來,重點再 selectList 方法中,我們進入看看:

該方法攜帶了3個引數,SQL 宣告的key,引數Map,預設分頁物件(不分頁),注意,mybatis 分頁是假分頁,即一次返回所有到記憶體中,再進行提取,如果資料過多,可能引起OOM。我們繼續向下走:

該方法首先根據 key或者說 id 從 configuration 中取出 SQL 宣告物件, 那麼是如何取出的呢?我們知道,我們的SQL語句再XML中編輯的時候,都有一個key,加上我們全限定類名,就成了一個唯一的id,我們進入到該方法檢視:

該方法呼叫了自身的 getMappedStatement 方法,預設需要驗證SQL語句是否正確,也就是 buildAllStatements 方法,最後從繼承了 HashMap 的StrictMap 中取出 value,這個StrictMap 有個注意的地方,他基本擴充套件了HashMap 的方法,我們重點看看他的get方法:

如何擴充套件呢?如果返回值是null,則丟擲異常,JDK中HashMap 可是不丟擲異常的,如果 value是 Ambiguity 型別,也丟擲異常,說明 key 值不夠清晰。

那麼 buildAllStatements 方法做了什麼呢?

注意看註釋(大意):解析快取中所有未處理的語句節點。當所有的對映器都被新增時,建議呼叫這個方法,因為它提供了快速失敗語句驗證。意思是如果連結串列中任何一個不為空,則丟擲異常,是一種快速失敗的機制。那麼這些是什麼時候新增進連結串列的呢?答案是catch的時候,看程式碼:

這個時候會將錯誤的語句新增進該連結串列中。

我們回到 selectList 方法,此時已經返回了 MappedStatement 物件,這個時候該執行器出場了,呼叫執行器的query方法,攜帶對映宣告,包裝過的引數物件,分頁物件。那麼如何包裝引數物件呢?我們看看 wrapCollection 方法:

該方法首先判斷是否是集合型別,如果是,則建立一個自定義Map,key是collection,value是集合,如果不是,並且還是陣列,則key為array,都不滿足則直接返回該物件。那麼我們該進入 query 一探究竟:

進入 CachingExecutor 的query 方法,首先根據引數獲取 BoundSql 物件,最終會呼叫 StaticSqlSource 的 getBoundSql 方法,該方法會構造一個 BoundSql 物件,構造過程是什麼樣子的呢?

會有5個屬性被賦值,sql語句,引數,

引數是我們剛剛傳遞的,那麼SQL 是怎麼來的呢,答案是在 XMLConfigBuilder 的 parseConfiguration 方法中,通過層層呼叫,最終執行 StaticSqlSource 的構造方法,將mapper 檔案中的Sql解析到該類中,最後會將XML 中的 #{id} 構造成一個ParameterMapping 物件,格式入下:

並將配置物件賦值給該類。

回到 BoundSql 的構造器,首先賦值SQL, 引數對映物件陣列,引數物件,預設的額外引數,還有一個元資料引數。

回到我們的 getBoundSql 方法:

我們已經有了引數繫結物件,該物件中有SQL語句,引數。繼續向下執行,從該物件獲取引數對映集合,如果為空,則再次建立一個 BoundSql 物件。接著迴圈引數,先獲取 resultMap id,如果有,則從配置對下中獲取resultMap 物件,如果不為null,則修改 hasNestedResultMaps 為 true。最後返回 BoundSql 物件。

我們回到 CachingExecutor 的 query 方法, 我們已經有了sql繫結物件, 接下來建立一個快取key,根據sql繫結物件,方法宣告物件,引數物件,分頁物件,注意:mybatis 一級快取預設為true,二級快取預設false。建立快取的過程很簡單,就是將所有的引數的key或者id構造該 CacheKey 物件,使該物件唯一。最後執行query方法:

該方法步驟:
1. 獲取快取,如果沒u偶,則執行代理執行器的query方法,如果有,且需要清空了,則清空快取(也就是Map)。
2. 如果該方法宣告使用快取並且結果處理器為null,則校驗引數,如果方法宣告使儲存過程,且所有引數有任意一個不是輸入型別,則丟擲異常。意思是當為儲存過程時,確保不能有輸出引數。
3. 呼叫 TransactionalCacheManager 事務快取處理器執行 getObject 方法,如果返回值時null,則呼叫代理執行器的query方法,最後新增進事務快取處理器。

我們重點關注代理執行器的query方法,也就是我們 SimpleExecutor 執行器。該方法如下:

  1. 首先判斷執行器狀態是否關閉。
  2. 判斷是否需要清除快取。
  3. 判斷結果處理器是否為null,如果不是null,則返回null,如果不是,則從本地快取中取出。
  4. 如果返回的list不是null,則處理快取和引數。否則呼叫queryFromDatabase 方法從資料庫查詢。
  5. 如果需要延遲載入,則開始載入,最後清空載入佇列。
  6. 如果配置檔案中的快取範圍是聲明範圍,則清空本地快取。
  7. 最後返回list。

可以看出,我們重點要關注的是 queryFromDatabase 方法,其餘的方法都是和快取相關,但如果沒有從資料庫取出來,快取也沒什麼用。進入該方法檢視:

我們關注紅框部分。

該方法建立了一個宣告處理器,然後呼叫了 prepareStatement 方法,最後呼叫了宣告處理器的query方法,注意,這個宣告處理器有必要說一下:

mybatis 的SqlSession 有4大物件:
1. Executor代表執行器,由它排程StatementHandler、ParameterHandler、ResultSetHandler等來執行對應的SQL。其中StatementHandler是最重要的。
2. StatementHandler的作用是使用資料庫的Statement(PreparedStatement)執行操作,它是四大物件的核心,起到承上啟下的作用,許多重要的外掛都是通過攔截它來實現的。
3. ParamentHandler是用來處理SQL引數的。
4. ResultSetHandler是進行資料集的封裝返回處理的,它相當複雜,好在我們不常用它。

好,我們繼續檢視 configuration 是如何建立 StatementHandler 物件的。我們看看他的 newStatementHandler 方法:

首先根據方法宣告型別建立一個宣告處理器,有最簡單的,有預編譯的,有儲存過程的,在我們這個方法中,建立了一個預編譯的方法宣告物件,這個物件的構造器對 configuration 等很多引數進行的賦值。我們還是看看吧:

我們看到了剛剛提到了parameterHandler和resultSetHandler。

回到 newStatementHandler 方法,需要執行下面的攔截器鏈的pluginAll方法,由於我們這裡沒有配置攔截器,該方法也就結束了。攔截器就是實現了Interceptor介面的類,國內著名的分頁外掛pagehelper就是這個原理,在mybais 原始碼裡,有一個外掛使用的例子,我們可以隨便看看:

執行了Plugin 的靜態 wrap 方法,包裝目標類(也就是方法宣告處理器),該靜態方法如下:

這裡就是動態代理的知識了,獲取目標類的介面,最後執行攔截器的invoke方法。有機會和大家再一起探討如何編寫攔截器外掛。這裡由於篇幅原因就不展開了。

我們回到 newStatementHandler 方法,此時,如果我們有攔截器,返回的應該是被層層包裝的代理類,但今天我們沒有。返回了一個普通的方法宣告器。

執行 prepareStatement 方法,攜帶方法宣告器,日誌物件。

第一行,獲取聯結器。

從事務管理器中獲取聯結器(該方法中還需要設定是否自動提交,隔離級別)。如果我們的事務日誌是debug級別,則建立一個日誌代理物件,代理Connection。

回到 prepareStatement 方法,看第二行,開始讓預編譯處理器預編譯sql(也就是讓connection預編譯),我看看看是如何執行的。注意,我們沒有配置timeout。因此返回null。

進入 RoutingStatementHandler 的 prepare 方法,呼叫了代理類的 PreparedStatementHandler 的prepare方法,該方法實現入下:

該方法以下幾個步驟:
1. 例項化SQL,也就是呼叫connection 啟動 prepareStatement 方法。我們熟悉的JDBC方法。
2. 設定超時時間。
3. 設定fetchSize ,作用是,執行查詢時,一次從伺服器端拿多少行的資料到本地jdbc客戶端這裡來。
4. 最後返回對映宣告處理器。

我們主要看看第一步:

有沒有很親切,我們看到我們在剛開始回憶JDBC程式設計的 connection.prepareStatement 程式碼,由此證明mybatis 就是封裝了 JDBC。首先判斷是否含有返回主鍵的功能,如果有,則看 keyColumnNames 是否存在,如果不存在,取第一個列為主鍵。最後執行else 語句,開始預編譯。注意:此connection 已經被動態代理封裝過了,因此會呼叫 invoke 方法列印日誌。最後返回宣告處理器物件。

我們回到 SimpleExecutor 的 prepareStatement 方法, 執行第三行 handler.parameterize(stmt),該方法其實也是委託了 PreparedStatementHandler 來執行,而 PreparedStatementHandler 則委託了 DefaultParameterHandler 執行 setParameters 方法,我們看看該方法:

首先獲取引數對映集合,然後從配置物件建立一個元資料物件,最後從元資料物件取出引數值。再從引數對映物件中取出型別處理器,最後將型別處理器和引數處理器關聯。我們看看最後一行程式碼:

還是JDBC。而這個下標的順序則就是引數對映的陣列下標。

終於,在準備了那麼多之後,我們回到 doQuery 方法,有了預編譯好的宣告處理器,接下來就是執行了。當然還是呼叫了PreparedStatementHandler 的query方法。

可以看到,直接執行JDBC 的 execute 方法,注意,該物件也被日誌物件代理了,做列印日誌工作,和清除工作。如果方法名稱是 “executeQuery” 則返回 ResultSet 並代理該物件。 否則直接執行。我們繼續看看DefaultResultSetHandler 的 handleResultSets 是如何執行的:

首先呼叫 getFirstResultSet 方法獲取包裝過的 ResultSet ,然後從對映器中獲取 resultMap 和resultSet,如果不為null,則呼叫 handleResultSet 方法,將返回值和resultMaps處理新增進multipleResults list中 ,然後做一些清除工作。最後呼叫 collapseSingleResultList 方法,該方法內容如下:

如果返回值長度等於1,返回第一個值,否則返回本身。

至此,終於返回了一個List。不容易啊!!!!最後在返回值的時候執行關閉 Statement 等操作。我們還需要關注一下 SqlSession 的 close 方法,該方法是事務最後是否生效的關鍵,當然真正的執行者是executor,在
CachingExecutor 的close 方法中:

該方法決定了到底是commit 還是rollback,最後執行代理執行器的 close 方法,也就是 SimpleExecutor 的close方法,該方法內容入下:

首先執行rollback方法,該方法內部主要是清除快取,校驗是否清除 Statements。然後執行 transaction.close()方法,重置事務(重置事務的autoCommit 屬性為true),最後呼叫 connection.close() 方法,和我們JDBC 一樣,關閉連線,但實際上,該connection 被代理了,被 PooledConnection 連線池代理了,在該代理的invoke方法中,會將該connection從連線池集合刪除,在建立一個新的連線放在集合中。最後回到 SimpleExecurtor 的 close 方法中,在執行完事務的close 方法後,在finally塊中將所有應用置為null,等待GC回收。清除工作也就完畢了。

到這裡 SqlSession的執行就基本結束了。

最後返回到我們的main方法,列印輸出。

我們再看看這行程式碼,這麼一行簡單的程式碼裡面 mybatis 為我們封裝了無數的呼叫。可不簡單。

UserInfo userInfo1 = sqlSession.selectOne(“org.apache.ibatis.mybatis.UserInfoMapper.selectById”, parameter);

7. 總結

今天我們從一個小demo開始 debug mybatis 原始碼,從如何載入配置檔案,到如何建立SqlSedssionFactory,再到如何建立 SqlSession,再到 SqlSession 是如何執行的,我們知道了他們的生命週期。其中建立SqlSessionFactory 和 SqlSession 是比較簡單的,執行SQL並封裝返回值是比較複雜的,因為還需要配置事務,日誌,外掛等工作。

還記得我們剛開始說的嗎?mybatis 做的什麼工作?

  1. 根據 JDBC 規範 建立與資料庫的連線。
    1. 通過反射打通Java物件和資料庫引數和返回值之間相互轉化的關係。

還有Mybatis 的執行過程?

  1. 讀取配置檔案建立 Configuration 物件, 用以建立 SqlSessionFactroy.
  2. SQLSession 的執行過程.

我們也知道了其實在mybatis 層層封裝下,真正做事情的是 StatementHandler,他下面的各個實現類分別代表著不同的SQL宣告,我們看看他有哪些屬性就知道了:

該類可以說囊括了所有執行SQL的必備屬性:配置,物件工廠,型別處理器,結果集處理器,引數處理器,SQL執行器,對映器(儲存這個SQL 所有相關屬性的地方,比放入SQL語句,引數,返回值型別,配置,id,宣告型別等等), 分頁物件, 繫結SQL與引數物件。有了這些東西,還有什麼SQL執行不了的呢?

當然,StatementHandler 只是 SqlSession 4 大物件的其中之一,還有Executor 執行器,他負責排程 StatementHandler,ParameterHandler,ResultHandler 等來執行對應的SQL,而 StatementHandler 的作用是使用資料庫的 Statement(PreparedStatement ) 執行操作,他是4大物件的核心,起到承上啟下的作用。ParameterHandler 就是封裝了對引數的處理,ResultHandler 封裝了對結果級別的處理。

到這裡,我們這篇文章就結束了,當然,大家肯定還想知道 getMapper 的原理是怎麼回事,其實我們開始說過,getMapper 更加的面向物件,但也是對上面的程式碼的封裝。篇幅有限,我們將在下篇文章中詳細解析。

good luck!!!!

相關推薦

深入剖析 mybatis 原理

# 前言 在java程式設計師的世界裡,最熟悉的開源軟體除了 Spring,Tomcat,還有誰呢?當然是 Mybatis 了,今天樓主是來和大家一起分析他的原理的。 1. 回憶JDBC 首先,樓主想和大家一起回憶學習JDBC的那段時光: pac

Mybatis ----- 剖析Mybatis原理 【摘抄】

摘自下面公眾號,記錄方便學習 # 前言 在java程式設計師的世界裡,最熟悉的開源軟體除了 Spring,Tomcat,還有誰呢?當然是 Mybatis 了,今天樓主是來和大家一起分析他的原理的。 1. 回憶JDBC 首先,樓主想和大家一起回憶學習JDBC的那

深入剖析 mybatis 原理

# 前言 在上篇文章中我們分析了 sqlSession.selectOne(“org.apache.ibatis.mybatis.UserInfoMapper.selectById”, parameter) 程式碼的執行過程,我們說,這種方式雖然更接近 myb

步步深入Java底層原理

接觸Java也快半年時間了,但是對Java的底層原理還不是非常清楚,最近在看《Java虛擬機器(第二版)》,深有啟發,順便把Java知識梳理一下,以便更好的進行接下來的學習。 Java技術體系劃分(

步步深入Java底層原理

Java物件的建立: 在建立物件時通常只是一個new關鍵字,但是Java虛擬機器首先會檢查引數是否能在常量池中定位到一個類的符號引用,並檢查這個類是否已經被載入,解析和初始化過,如果沒有,那麼必須先執行類的載入。 類的載入、連線、初始化三個

步步深入Java底層原理

ArrayList的實現原理: 1. ArrayList概述:   ArrayList是List介面的可變陣列的實現。實現了所有可選列表操作,並允許包括 null 在內的所有元素。除了實現 List

java併發機制的底層實現原理:volatile深入分析

     java程式碼最終會被類載入器載入到JVM中,然後轉化為彙編指令在CPU上執行。java中所使用的併發機制依賴於JVM的實現和CPU的指令。 1.volatile的應用 volatile是一個輕量級的synchronize,它保證了共享變數的可見性,確保了所有執

剖析SSH核心原理 .

在我前面的文章中,也試圖總結過SSH,見http://blog.csdn.net/shan9liang/article/details/8803989,隨著知識的積累,總感覺以前說得比較籠統,思路不夠清晰,所以打算寫幾篇文章再稍微深入地剖析一下SSH,只能說是稍微,因為這三

Java併發4深入分析java執行緒池框架及實現原理

先說說我個人對執行緒池的理解:執行緒池顧名思義是一個裝有很多執行緒的池子,這個池子維護著從執行緒建立到銷燬的怎個生命週期以及執行緒的分配,使用者只需要把任務提交給這個執行緒池而不用去關心執行緒池如何建立執行緒,執行緒池會自己給這些任務分配執行緒資源來完成任務。 java的E

[轉]畢設- 深入HBase架構解析

mil https 文件存儲 back 高效 索引 asa dia 隨機 深入HBase架構解析(一) 前記 公司內部使用的是MapR版本的Hadoop生態系統,因而從MapR的官網看到了這篇文文章:An In-Depth Look at the HBase Ar

socket 由淺入深系列------ 原理

ora internal 概念 pro tcp協議 如何 控制 是的 depends 來自:網絡整理 個人覺得寫一個網絡應用程序沒有是一件非常easy的事。其實,我們剛開始的時候總覺得的原則: 建立------》連接套接字-------》接受一個連接----》發送數據

solr搜索之入門及原理

solr solr入門 1 solr簡介solr官方文檔:http://wiki.apache.org/solr/DataImportHandler 下載地址:http://www.apache.org/dyn/closer.cgi/lucene/solr/2 solr入門我們使

android深入之設計模式托付模式

-h listen back != new 聚合 string static data- (一)托付模式簡單介紹 托付模式是主要的設計模式之中的一個。托付。即是讓還有一個對象幫你做事情。 更多的模式,如狀態模式、策略模式、訪問者模式本質上是在更特殊的場合採用了托

mybatis總結

tran default led sqlserver insert cto src 結果集 obj MyBatis 是支持普通 SQL查詢,存儲過程和高級映射的優秀持久層框架。MyBatis 消除了幾乎所有的JDBC代碼和參數的手工設置以及結果集的檢索。MyBatis 使用

mybatis學習----入門

配置 ati 開源 bold 文件中 手動 arch mage git 一.Mybatis介紹 MyBatis 本是apache的一個開源項目iBatis, 2010年這個項目由apache software foundation 遷移到了google code,並且改名為

MyBatis學習簡介及入門案例

結果集 提交 ace 支持 nag 實例 exce 空間 cti 1.什麽是MyBatis?   MyBatis是一個支持普通SQL查詢,存儲過程,和高級映射的優秀持久層框架。MyBatis去掉了幾乎所有的JDBC代碼和參數的手工設置以及對結果集的檢索封裝。MyBatis可

MyBatis框架

通過 ons sql dstat ride 開源 alt div feed MyBatis介紹:   MyBatis 本是apache的一個開源項目iBatis, 2010年這個項目由apache software foundation 遷移到了google code,並且

Mybatis總結mybatis的搭建

ack pen devel column 3.3 exception ive pri tac mybatis:它抽象了大量的jdbc代碼,並提供了一個簡單易用的API和數據庫交互。 mybatis的優勢:它消除了大量jdbc冗余的代碼、它可以接受SQL語句。 mybati

QR二維碼原理

info 分別是 最大 mask 多字節字符 包含 多字節 版本 錯誤 一、什麽是QR碼 QR碼屬於矩陣式二維碼中的一個種類,由DENSO(日本電裝)公司開發,由JIS和ISO將其標準化。QR碼的樣子其實在很多場合已經能夠被看到了,我這還是貼個圖展示一下: 這個圖如果被正確

Day1 Mybatis初識

定義 gen 細節 manager mapping out 開發效率 導入jar 工廠 框架 將重復的,繁瑣的代碼實現封裝,讓程序員將更多的精力放在業務的理解和分析上。 框架的作用 提高開發效率 隱藏細節 三大框架SSH --> SSM 1)