selectById的流程

  上一篇,我們講過DAO層接口,是怎麼被mybatis自動實現的。DAO層接口,爲什麼能操作數據庫

這篇,我們繼續下去,講一講DAO層接口中selectById的流程。

 

一、準備工作

  和上一篇文章中,用到的代碼幾乎沒有區別,只是Main類有所調整

public class Main {

    public static SqlSession getSqlSession() throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        return sqlSessionFactory.openSession();
    }

    public static void main(String[] args) throws IOException {
        TestDAO testDAO = getSqlSession().getMapper(TestDAO.class);
        Test test = testDAO.selectById(1);

        testDAO.testDefaultMethod();

        //類文件是緩存在java虛擬機中,我們將類文件打印到文件中,便於查看
//        generateProxyFile("F:/TestDAOProxy.class");
    }

    private static void generateProxyFile(String path){
        byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", new Class<?>[]{TestDAO.class});

        try(FileOutputStream fos = new FileOutputStream(path)) {
            fos.write(classFile);
            fos.flush();
            System.out.println("代理類class文件寫入成功");
        } catch (Exception e) {
            System.out.println("寫文件錯誤");
        }
    }
}

 

二、開始debug

1、起始位置

   從main方法中的Test test = testDAO.selectById(1);這一行開始debug。探究selectById的流程

2、UML圖

  這裏先給出一個UML圖,照着這個UML進行講解。當然這裏UML圖太繁瑣了,根本記不住,我們會在最後給出一個簡化的UML圖,方便理解記憶

 

3、TestDAO的selectById方法,實際上是調用了它的代理類MapperProxy的invoke方法

那麼我們就來理一理這個invoke方法:

  1)Object.class.equals(method.getDeclaringClass()) 這句話的意思是:判斷method這個方法,是不是Object類中的方法,比如沒有在子類中被重寫的toString()、hashCode()、equals()方法。

 2)isDefaultMethod(method) 的代碼如下:

/**
   * Backport of java.lang.reflect.Method#isDefault()
   */
  private boolean isDefaultMethod(Method method) {
    return (method.getModifiers()
        & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC
        && method.getDeclaringClass().isInterface();
  }

   我們可以看到這個判斷語句,前半段的意思是:這個方法被public修飾,但是不被abstract、static修飾。後半段的意思是:這個方法是接口中的方法。

  又要是接口中的方法,又不能被abstract、static修飾,接口中真的有這種方法嗎?

確實是有的,JDK8以後,接口可以有方法體

  

 

4、很明顯selectById不是接口默認方法,那麼我們只能走final MapperMethod mapperMethod = cachedMapperMethod(method);這一步了。

從methodCache中get出一個MapperMethod。這個methodCache又是哪來的?

我們回溯一下,發現methodCache,是從MapperProxy中的構造方法來的。那麼看看誰調用了這個構造方法。

好了,我們知道methodCache是自己new出來的。

 

5、很明顯這個時候methodCache是空的,那麼就要自己new 一個MapperMethod

其中,SqlCommand如下:

 

6、MapperMethod已經被創建了,我們回到MapperProxy的invoke方法,發現下一步是調用MapperMethod的execute方法

  其中Object param = method.convertArgsToSqlCommandParam(args)是獲取參數的值,也就是我們selectById(1)傳進來的1。

 

7、繼續下一步:result = sqlSession.selectOne(command.getName(), param);

defaultSqlSession的selectOne,實際上是調用了selectList。那麼我們繼續跟蹤selectList

 

8、接下來調用了CachingExecutor類的query方法。那麼這個CachingExecutor是什麼時候被創建的?回溯一下,發現是在DefaultSqlSessionFactory的openSession方法中,被創建的。也就是我們代碼中的這一句。

我們已經知道了CachingExecutor是什麼時候創建的,那麼繼續探索它的query方法

獲取BoundSql和CacheKey之後,繼續query

又調用了SimpleExecutor的query方法,這裏用到了裝飾器模式(同宗同源)。

 

9、來到BaseExecutor的queryFromDatabase方法,終於調用了doQuery方法

 

10、在doQuery方法中,我們來看一下StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);

這句話,生成了3個handler,分別是StatementHandler、ParameterHandler、ResultSetHandler。後兩個是作爲StatementHandler的實例變量存在的。

  這三個類的用法,可以參考官網,關於 插件 (plugins)的描述。

 順道說一下,Executor、StatementHandler、ParameterHandler、ResultSetHandler這四個插件的生成順序是 

Executor --> ParameterHandler  --> ResultSetHandler  --> StatementHandler。

 

11、生成3個handler之後,就來到了stmt = prepareStatement(handler, ms.getStatementLog());

  這個方法的作用類似於我們使用JDBC時,獲取連接,創建PreparedStatement,做一些前期準備。sql語句中的佔位符也被傳進來的參數替代了。

 

12、執行handler.<E>query(stmt, resultHandler)

前面兩句,是執行sql語句。

resultSetHandler.<E> handleResultSets(ps)這句是處理返回結果,將ResultSet轉換成我們需要的實體對象。

 

三、簡化版的UML

這個步驟也太冗長了,我麼簡化一下,方便記憶

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章