1. 程式人生 > >MyBatis原始碼閱讀——通過debug解析MyBatis執行流程

MyBatis原始碼閱讀——通過debug解析MyBatis執行流程

前言

最近在閱讀MyBatis框架的原始碼。發現它其實是一個非常值得閱讀的框架。它靈活得運用了常見的設計模式去設計。值得我們去學習。我還是比較喜歡以debug閱讀MyBatis的原始碼。下面,就一起來看看吧。
首先,我們先寫一個demo,以供除錯使用

public class Demo1SessionFactory {
    public static void main(String[] args) throws IOException {
        String resource = "mybatis/conf/mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        //從 XML 中構建 SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession session = sqlSessionFactory.openSession();
        try {
            BlogMapper mapper = session.getMapper(BlogMapper.class);
            Blog blog = mapper.selectBlog(1L);
            System.out.println(blog);
            blog = mapper.selectBlog(1L);
            System.out.println(blog);
        } finally {
            session.close();
        }
    }
}

從demo分析上層流程

在demo中,我們先從最上層分析一下流程。這裡寫圖片描述
我們首先是建立SqlSessionFactory,這其中需要使用配置資訊。然後從SqlSessionFactory獲取SqlSession,再從SqlSession中獲取Mapper。這是我們最上層的流程。那這這幾步操作中,內部操作了什麼呢?我們需要debug去分析。在分析之前,我們需要先了解一下MyBatis的架構和使用的設計模式,這樣能幫助我們更輕鬆的去了解它的原始碼。

作用域(Scope)和生命週期

  • SqlSessionFactory
    SqlSessionFactory 一旦被建立就應該在應用的執行期間一直存在,沒有任何理由對它進行清除或重建。使用 SqlSessionFactory 的最佳實踐是在應用執行期間不要重複建立多次,多次重建 SqlSessionFactory 被視為一種程式碼“壞味道(bad smell)”。因此 SqlSessionFactory 的最佳作用域是應用作用域。有很多方法可以做到,最簡單的就是使用單例模式或者靜態單例模式。

  • SqlSession
    每個執行緒都應該有它自己的 SqlSession 例項。SqlSession 的例項不是執行緒安全的,因此是不能被共享的,所以它的最佳的作用域是請求或方法作用域。絕對不能將 SqlSession 例項的引用放在一個類的靜態域,甚至一個類的例項變數也不行。也絕不能將 SqlSession 例項的引用放在任何型別的管理作用域中,比如 Servlet 架構中的 HttpSession。如果你現在正在使用一種 Web 框架,要考慮 SqlSession 放在一個和 HTTP 請求物件相似的作用域中。換句話說,每次收到的 HTTP 請求,就可以開啟一個 SqlSession,返回一個響應,就關閉它。這個關閉操作是很重要的,你應該把這個關閉操作放到 finally 塊中以確保每次都能執行關閉。下面的示例就是一個確保 SqlSession 關閉的標準模式:

其實我感覺這個跟Servlet中的生命週期類似,SqlSessionFactory當做上下文(Application),SqlSession當做一個會話(session),這樣就好理解了。一個SqlSession就是與資料庫進行一次互動。

從作用域和生命週期的分析,我們可以分析出,它的核心是SqlSession。
檢視org.apache.ibatis.session.defaults.DefaultSqlSessionFactory原始碼,檢視SqlSession的獲取實現過程

  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType);
      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();
    }
  }

再看SqlSession類的結構:
SqlSession 主要由Configuration和Executor構成,其中有增刪改查的各種方法。
這裡寫圖片描述
由此我們可以推理出我們用MyBatis進行操作的主要操作入口都在這裡。那麼,接下來就涉及到我們是如何獲取到具體的Mapper

具體Mapper的獲取過程

檢視 org.apache.ibatis.session.SqlSession ->getMapper() 找到實現

@Override
public <T> T getMapper(Class<T> type) {
  return configuration.<T>getMapper(type, this);
}

繼續深入,找到org.apache.ibatis.binding.MapperRegistry ->getMapper()

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

可以分析出這是通過動態代理獲取的,而它在獲取的時候需要兩個引數Class type, SqlSession sqlSession。SqlSession是作為被代理的物件。type作為key去Map中拿MapperProxyFactory來產生代理類需要的引數。

Mapper的操作流程

現在,我們已經知道具體的Mapper的來歷了,那麼就可以去了解一下它是如何使用的。因為它是SqlSession進行了動態代理類,所以,我們debug SqlSession,進行一次查詢操作。就如下圖所示進入除錯。
這裡寫圖片描述
debug下去,你會看到,底層是通過Executor執行的
這裡寫圖片描述
然後繼續深入,可以看到sql的組裝過程:
這裡寫圖片描述

Executor是MyBatis執行器,是MyBatis 排程的核心,負責SQL語句的生成和查詢快取的維護;
接下來就是類似傳統JDBC的操作了
這裡寫圖片描述

MyBatis核心構件

名稱 作用
SqlSession 作為MyBatis工作的主要頂層API,表示和資料庫互動的會話,完成必要資料庫增刪改查功能
Executor MyBatis執行器,是MyBatis 排程的核心,負責SQL語句的生成和查詢快取的維護
StatementHandler 封裝了JDBC Statement操作,負責對JDBC statement 的操作,如設定引數、將Statement結果集轉換成List集合
ParameterHandler 負責對使用者傳遞的引數轉換成JDBC Statement 所需要的引數
ResultSetHandler 負責將JDBC返回的ResultSet結果集物件轉換成List型別的集合
TypeHandler 負責java資料型別和jdbc資料型別之間的對映和轉換
MappedStatement MappedStatement維護了一條select
SqlSource 負責根據使用者傳遞的parameterObject,動態地生成SQL語句,將資訊封裝到BoundSql物件中,並返回
BoundSql 表示動態生成的SQL語句以及相應的引數資訊
Configuration MyBatis所有的配置資訊都維持在Configuration物件之中