一次Mybaits查詢的原始碼分析

2023-06-18 06:01:49

很好奇Mybaits是怎麼將xml和mapper對應起來的,用一段比較簡單的demo去debug追蹤一下原始碼看看

先用xml設定的方式,看懂了再去看註解的方式是怎麼實現的

獲取Mapper

Mybaits是如何從xml中載入到mapper的

<?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>
    <typeAliases>
        <package name="com.github.yeecode.mybatisdemo"/>
    </typeAliases>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql://127.0.0.1:3306/test?serverTimezone=UTC"/>
                    <property name="username" value="root"/>
                    <property name="password" value="123456"/>
                </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="com/github/yeecode/mybatisdemo/UserMapper.xml"/>
    </mappers>
</configuration>

在xml中有mapper標籤,應該是從這裡載入到設定

範例程式碼

  public static void main(String[] args) {
        // 第一階段:MyBatis的初始化階段
        String resource = "mybatis-config.xml";
        // 得到組態檔的輸入流
        InputStream inputStream = null;
        try {
            inputStream = Resources.getResourceAsStream(resource);
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 得到SqlSessionFactory
        SqlSessionFactory sqlSessionFactory =
                new SqlSessionFactoryBuilder().build(inputStream);

        // 第二階段:資料讀寫階段
        try (SqlSession session = sqlSessionFactory.openSession()) {
            // 找到介面對應的實現
            UserMapper userMapper = session.getMapper(UserMapper.class);
            // 組建查詢引數
            User userParam = new User();
            userParam.setSchoolName("Sunny School");
            // 呼叫介面展開資料庫操作
            List<User> userList =  userMapper.queryUserBySchoolName(userParam);
            // 列印查詢結果
            for (User user : userList) {
                System.out.println("name : " + user.getName() + " ;  email : " + user.getEmail());
            }
        }
    }

UserMapper userMapper = session.getMapper(UserMapper.class);此處開始debug,看看是怎麼獲取到mapper的

一路點進來發現是從一個Map中去獲取mapper物件

private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

那麼是從什麼時候填充knownMappers的呢

這個物件的方法不只有獲取mapper,還有新增mapper,找一個新增方法繼續斷底點

找到是找到了,但是不知道什麼時候呼叫的,可以通過IDEA的呼叫棧

最後發現了,是載入xml的時候,去解析mapper標籤裡的值,然後再通過類載入器去載入資源,最後載入到knownMappers中,還有去解析xml中的sql的過程

這樣子xml就和mapper就對應起來了,雖然知道了mapper和xml的對應關係,但是不知道怎麼通過呼叫mapper裡的方法,去找到對應的sql

在對 List<User> userList = userMapper.queryUserBySchoolName(userParam);debug時,沒有進入到mapper方法中,而是會進入到一個代理類中

剛剛在getMapper()中給UserMapper建立了代理,那麼大概知道是mapper和xml是怎麼關聯的了,當呼叫mapper時,會被MapperProxy代理,去執行查詢方法時,通過上邊的knownMappers
獲取到mapper對應的xml,這樣代理類就知道要呼叫的方法和對應的sql的哪裡

最終時通過mapperMethod.execute(sqlSession, args);去執行查詢的,點進去一看發現對各種sql型別做了處理

select的查詢原來通過返回值來選擇不同的處理

很好奇這些屬性是怎麼判斷的,找到對應的類繼續斷點

原來是在execute之前去賦值,而且這個方法會把呼叫方法對應的xml中的方法找到

通過獲取到方法的返回值,然後再去做對比,我這個方法返回的是list,就命中了returnMany,在繼續斷點,找到了真正執行的方法

這裡就已經將關聯的xml資訊帶過來了


繼續看會看到快取相關的程式碼,如果命中了快取就直接返回了,我這裡沒有就繼續開了一個執行緒往下執行,delegate是一個Executor

最後的查詢到了這裡,就是呼叫mysql的包了,在statement中,已經把sql、引數和連線設定什麼的都封裝好了

查詢完後把結果返回到statement,但是返回的內容很多,查詢結果記錄在這裡

查詢總結:

  • 在進行資料庫查詢前,先查詢快取;沒有名中,則資料庫查詢之後的結果也放入快取中
  • SQL 語句的執行經過了層層轉化,依次經過了 MappedStatement 物件、Statement物件和 PreparedStatement物件,最後才交給mysql執行
  • 最終資料庫查詢得到的結果交給 ResultHandler物件處理

返回結果

將結果對映到實體類上這段程式碼有點繞,呼叫鏈很長

首先是這裡先建立輸出的實體類,就是resultMap裡定義的物件

建立好實體後,把實體傳輸給下一個方法,填充實體

將實體欄位和結果集裡的欄位對應起來,然後根據欄位去獲取對應的值,然後把值設定到實體裡,通過迴圈遍歷全部欄位

這樣走一圈回來,一個物件就對映好了,再經過迴圈,就把全部的物件都拿到了,最後再將這些物件封裝到multipleResults集合裡,這個集合就是返回值了

對映總結:

  • 獲取並建立實體類
  • 將實體類的欄位和結果集的欄位一一對應,然後再填充實體的值
  • 最後返回實體集合

總結

以上就是Mybaits讀取xml,然後查詢的過程了,整個過程還是很複雜的,很多層封裝和跳轉,但是大大的提高了我們開發的效率

然後再把總結髮一下

獲取設定總結:

  • 得到組態檔然後轉換成輸入流
  • 將輸入流傳給SqlSessionFactoryBuilder建立SqlSessionFactory
  • 掃描xml檔案並載入,然後將xml和mapper的對應關係填充好

查詢總結:

  • 在進行資料庫查詢前,先查詢快取;沒有名中,則資料庫查詢之後的結果也放入快取中
  • SQL 語句的執行經過了層層轉化,依次經過了 MappedStatement 物件、Statement物件和 PreparedStatement物件,最後才交給mysql執行
  • 最終資料庫查詢得到的結果交給 ResultHandler物件處理

對映總結:

  • 獲取並建立實體類
  • 將實體類的欄位和結果集的欄位一一對應,然後再填充實體的值
  • 最後返回實體集合