深刻理解 MyBatis 啓動流程

環境簡介與入口

記錄一下嘗試閱讀Mybatis源碼的過程,這篇筆記是我一邊讀,一遍記錄下來的,雖然內容也很少,對Mybatis總體的架構體系也沒有摸的很清楚,起碼也能把這個過程整理下來,這也是我比較喜歡的一種學習方式吧java

單獨Mybatis框架搭建的環境,沒有和其餘框架整合程序員

入口點的源碼以下:sql

@Test
public void test01() {

 try {
    this.resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
    // 2. 建立SqlSessionFactory工廠
    this.factory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    // 3. 建立sqlSession
    // todo 怎麼理解這個sqlSession? 首先它是線程級別的,線程不安全, 其次它裏面封裝了大量的CRUD的方法
    this.sqlSession = factory.openSession();

    IUserDao mapper = this.sqlSession.getMapper(IUserDao.class);
    List<User> all = mapper.findAll();
    for (User user : all) {
        System.out.println(user);
    }
    // 事務性的操做,自動提交
    this.sqlSession.commit();
    // 6, 釋放資源
    this.sqlSession.close();
    this.resourceAsStream.close();
  } catch (IOException e) {
   e.printStackTrace();
  }
}

構建SqlSessionFactory

首先跟進這個,看看如何構建SqlSessionFactory對象數據庫

this.factory = new SqlSessionFactoryBuilder().build(resourceAsStream);

這個SqlSessionFactoryBuilder類的存在很簡單,取名也叫他構建器,Mybatis的官網是這樣解釋它的,這個類能夠被實例化(由於它有且僅有一個默認的無參構造),使用它的目的就是用來建立多個SqlSessionFactory實例,最好不要讓他一直存在,進而保證全部用來解析xml的資源能夠被釋放緩存

因此跳過對這個構建器的關注,轉而看的build()方法安全

首先會來到這個方法,直接能夠看到存在一個XML配置解析器,這其實並不意外,畢竟如今是MyBatis是孤軍一人,就算咱們使用的是註解開發模式,不也得存在主配置文件不是?session

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      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.
      }
    }
  }

接着看看parser.parse(),它裏面會解析主配置文件中的信息,解析哪些信息呢? 源碼以下: 很清楚的看到,涵蓋mybatis配置文件中的全部的標籤mybatis

private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      ...
      typeHandlerElement(root.evalNode("typeHandlers"));
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

第一個問題: 解析的配置信息存在哪裏呢? 其實存放在一個叫Configuration的封裝類中, 這個上面的解析器是XMLConfigBuilder 這個封裝類的保存者是它的父類BaseBuilder架構

繼續跟進build()方法,源碼以下:app

public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

小結: 至此也就完成了DefaultSqlSessionFactory()的構建,回想下,這個構建器真的太無私了,犧牲了本身,不只僅建立了默認的SqlSessionFactory,還將配置文件的信息給了他

打開SqlSession

建立完成SqlSession工廠的建立, 咱們繼續跟進this.sqlSession = factory.openSession(); , 從工廠中獲取一個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呢? 註釋是這麼說的,他是MyBatis在java中主要幹活的接口,經過這個接口,你能夠執行命令(它裏面定義了大量的 諸如selectList相似的方法),獲取mapper,合併事務

The primary Java interface for working with MyBatis.
Through this interface you can execute commands, get mappers and manage transactions.

經過上面的我貼出來的函數,你們能夠看到,經過事務工廠實例化了一個事物Transaction,那麼問題來了,這個Transaction又是什麼呢? 註釋是這麼解釋的: Transaction 包裝了一個數據庫的鏈接,處理這個鏈接的生命週期,包含: 它的建立,準備 提交/回滾 和 關閉

緊接着建立執行器:configuration.newExecutor(tx, execType),默認建立的是CachingExecutor它維護了一個SimpleExecutor, 這個執行器的特色是 在每次執行完成後都會關閉 statement 對象

關於mybatis的執行器,其實挺多事的,打算專門寫一篇筆記

繼續看new DefaultSqlSession()咱們得知,這個sqlSession的默認實現類是DefaultSqlSession

第三個參數autocommit爲false, 這也是爲何咱們若是不手動提交事務時,雖然測試會經過,可是事務不會被持久化的緣由

小結: 當前函數是 openSession(), 若是說它是打開一個session,那跟沒說是同樣的,經過源碼咱們也看到了,這一步實際上是Mybatis將 數據庫鏈接,事務,執行器進行了一下封裝而後返回給程序員

獲取Mapper -- maperProxy

咱們交給mybatis的mapper是一個接口,看看Mybatis是如何實例化咱們的結果,返回給咱們一個代理對象的

跟進源碼:IUserDao mapper = this.sqlSession.getMapper(IUserDao.class); ,通過一個空方法,咱們進入Configuration類中的函數以下:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }

mapperRegistry中獲取mapper,他是Configuration屬性以下: 能夠看到這個mapperRegistry甚至包含了Configuration,甚至還多了個 knownMappers

那麼問題來了,這個knownMappers是幹啥呢? 我直接說,這個map就是在上面解析xml配置文件時,存放程序員在<mappers>標籤下配置的<maper>

protected final MapperRegistry mapperRegistry = new MapperRegistry(this);

// 詳情:
public class MapperRegistry {
    private final Configuration config;
    private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

繼續跟進這個getMapper()以下: 而且咱們在配置文件中是這樣配置的

<mappers>
    <mapper class="com.changwu.dao.IUserDao"/>
</mappers>
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);
    }
  }

因此不難想象,咱們確定能獲取到結果,經過上面的代碼咱們能看到,獲取到的對象被強轉成了MapperProxyFactory類型,它的主要成員以下: 說白了,這個 map代理工廠是個輔助對象,它是對程序員提供的mapper結果的描述,同時內置使用jdk動態代理的邏輯爲mapper建立代理對象

public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();
  ...
    protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
    }

說到了爲mapper建立動態代理,就不得不去看看是哪一個類充當了動態代理的須要的InvoketionHandler -- 這個類是mybatis中的MapperProxy, 沒錯它實現了InvocationHandler接口,重寫了invoke()邏輯,源碼以下:

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
  if (Object.class.equals(method.getDeclaringClass())) {
    return method.invoke(this, args);
  } else if (isDefaultMethod(method)) {
    return invokeDefaultMethod(proxy, method, args);
  }
} catch (Throwable t) {
  throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}

小結: 至此當前模塊的獲取mapper對象已經完結了,咱們明明白白的看到了MyBatis爲咱們的mapper使用jdk的動態代理建立出來代理對象, 這也是爲何咱們免去了本身寫實現類的粗活

執行Map -- maperProxy

上一個模塊咱們知道了Mybatis爲咱們建立出來了mapper接口的代理對象,那當咱們獲取到這個代理對象以後執行它的mapper.findAll();實際上觸發的是代理對象的invoke()方法

因此說,接着看上面的MapperProxyinvoke()方法:

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
  if (Object.class.equals(method.getDeclaringClass())) {
    return method.invoke(this, args);
  } else if (isDefaultMethod(method)) {
    return invokeDefaultMethod(proxy, method, args);
  }
} catch (Throwable t) {
  throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}

上面的方法咱們關注兩個地方,第一個地方就是final MapperMethod mapperMethod = cachedMapperMethod(method);,見名知意: 緩存MapperMethod

第一個問題: 這個MapperMethod是什麼? 它實際上是Mybatis爲sql命令+方法全限定名設計的封裝類

*/
public class MapperMethod {

  private final SqlCommand command;
  private final MethodSignature method;

說白了,就是想爲MapperProxy保存一份map格式的信息,key=方法全名 value=MapperMethod(command,method),存放在MapperProxy的屬性methodCache中

comand -> "name=com.changwu.dao.IUserDao,fingAll"

method -> "result= public abstract java.util.List.com.changwu.dao.IUserDao.findAll()"

爲何要給這個MapperProxy保存這裏呢? 很簡單,它是mapper的代理對象啊,程序員使用的就是他,在這裏留一份method的副本,再用的話多方便?

完成緩存後,繼續看它如何執行方法:,跟進mapperMethod.execute(sqlSession, args)

由於我使用的是@Select("select * from user"),因此必定進入下面的result = executeForMany(sqlSession, args);

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      
      ..
        case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        ...
}

因此咱們繼續關注這個 executeForMany(sqlSession, args);方法,看他的第一個參數是sqlSession,也就是咱們的DefaultSqlSession,他裏面存在兩大重要對象: 1是configuration 配置對象, 2是Executor 執行器對象

繼續跟進:

private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;  
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.selectList(command.getName(), param, rowBounds);
    } else {
      result = sqlSession.selectList(command.getName(), param);
    }

咱們在繼續跟進這個selectList方法以前,先看看這個command.getName()是啥? 其實咱們上面的代碼追蹤中有記錄: 就是name=com.changwu.dao.IUserDao,fingAll

繼續跟進去到下面的方法:

這個方法就比較有趣了,首先來講,下面的入參都是什麼

statement = "com.changwu.dao.IUserDao.findAll" parameter=null

第二個問題:MappedStatement是什麼? 它是一個對象,維護了不少不少的配置信息,可是咱們關心它裏面的兩條信息,這其實能夠理解成一種方法與sql之間的映射,以下圖

mappedStatement

@Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

前面閱讀時,咱們知道Mybatis爲咱們建立的默認的執行器 Executor是CachingExecutor,以下圖

CachingExecutor

繼續跟進,主要作了下面三件事, 獲取到綁定的sql,而後調用SimpleExecutor緩存key,而後繼續執行query()方法

@Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

接着調用SimpleExecutorquery()方法,而後咱們關注SimpleExecutordoQuery()方法,源碼以下

@Override
  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);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

咱們注意到.在SimpleExcutor中建立的了一個 XXXStatementHandler這樣一個處理器, 因此咱們的只管感受就是,sql真正執行者其實並非Executor,而是Executor會爲每一條sql的執行從新new 一個 StatementHandler ,由這個handler去具體的執行sql

關於這個StatementHandler究竟是是何方神聖? 暫時瞭解它是Mybatis定義的一個規範接口,定義了以下功能便可

public interface StatementHandler {
  // sql預編譯, 構建statement對象 
  Statement prepare(Connection connection, Integer transactionTimeout)
      throws SQLException;
  // 對prepare方法構建的預編譯的sql進行參數的設置
  void parameterize(Statement statement)
      throws SQLException;
  // 批量處理器
  void batch(Statement statement)
      throws SQLException;
  // create update delete
  int update(Statement statement)
      throws SQLException;
  // select
  <E> List<E> query(Statement statement, ResultHandler resultHandler)
      throws SQLException;
  
  <E> Cursor<E> queryCursor(Statement statement)
      throws SQLException;
  // 獲取sql的封裝對象 
  BoundSql getBoundSql();
  // 獲取參數處理對象
  ParameterHandler getParameterHandler();

}

瞭解了這個StatementHandler是什麼,下一個問題就是當前咱們建立的默認的statement是誰呢? routingStatementHandler以下圖

https://img2018.cnblogs.com/blog/1496926/201910/1496926-20191025220026439-543525632.png

建立preparedStatement

立刻馬就發生了一件悄無聲息的大事!!!根據現有的sql等信息,構建 PreparedStatement,咱們關注這個prepareStatement(handler, ms.getStatementLog());方法,經過調試咱們得知,prepareStatement()RoutingStatementHandler的抽象方法,被PreparedStatementHandler重寫了,因此咱們去看它如何重寫的,以下:

@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
  statement = instantiateStatement(connection);

咱們關注這個instantiateStatement()方法, 而且進入它的connection.prepareStatement(sql);方法,以下圖:

preparedStatement

純潔的微笑... 見到了原生JDK, jdbc的親人...

建立完事這個 preparedStatement,下一步總該執行了吧...繞這麼多圈...

咱們回到上面代碼中的return handler.query(stmt, resultHandler);準備執行,此時第一個參數就是咱們的剛建立出來的PreparedStatement, 回想一下,上面建立的這個默認的statement中的表明是PreparedStatementHandler,因此,咱們進入到這個StatementHandler的實現類RountingStatementHandler中,看他的query()方法

@Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    return delegate.query(statement, resultHandler);
  }

調用RountingStatementHandler中維護的表明的StatementHandler也就是PreparedStatementHandlerquery()方法,順勢跟進去,最終會經過反射執行jdbc操做,如圖, 我圈出來的對象就是咱們上面建立出來的preparedStatement

執行preparedStatement

提交事務

跟進conmit()方法,分紅兩步

  • 清空緩存
  • 提交事務

清空緩存是在CachingExecutor中調用了SimpleExecutor簡單執行器的方法commit(required)

@Override
  public void commit(boolean required) throws SQLException {
    delegate.commit(required);
    tcm.commit();
  }

接在SimpleExecutor的父類BaseExecutor中完成

@Override
  public void commit(boolean required) throws SQLException {
    if (closed) {
      throw new ExecutorException("Cannot commit, transaction is already closed");
    }
    clearLocalCache();
    flushStatements();
    if (required) {
      transaction.commit();
    }
  }

提交事務的操做在tcm.commit();中完成

本文到這裏也就行將結束了,若是您以爲挺好玩的,歡迎點贊支持,有錯誤的話,也歡迎批評指出

相關文章
相關標籤/搜索