15分鐘--幫你分析mybatis源碼重點!

本文主旨

方便你快速分析mybatis源碼,梳理其流程,掌握主要知識點.

官方簡介:

MyBatis 是一款優(yōu)秀的持久層框架,它支持定制化 SQL、存儲過程以及高級映射。MyBatis 避免了幾乎所有的 JDBC 代碼和手動設(shè)置參數(shù)以及獲取結(jié)果集。MyBatis 可以使用簡單的 XML 或注解來配置和映射原生類型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 對象)為數(shù)據(jù)庫中的記錄。

本文介紹

mybatis相信大家都用的多了,本文主要介紹mybatis的整體設(shè)計思想、核心源碼實現(xiàn),并從中體悟面向?qū)ο笏季S的設(shè)計理念.-----哈哈,正所謂,技近乎藝,藝近乎道!

架構(gòu)介紹

mybatis結(jié)構(gòu)

其結(jié)構(gòu)可以分為三層

  1. 配置初始化:負(fù)責(zé)mybatis的初始化,環(huán)境、數(shù)據(jù)、插件配置組裝工作
  2. 調(diào)用會話: 一次查詢的調(diào)用過程
  3. 映射轉(zhuǎn)換: java對象、數(shù)據(jù)庫結(jié)果間相互映射處理

源碼流程梳理

層級如此,我們再來看看調(diào)用時序:

mybati流程圖
  1. Configuration作為mybatis的頂層配置類,管理一切mybatis的環(huán)境、緩存信息

2.當(dāng)你調(diào)用getMapper方法獲取對應(yīng)mapper類時(或通過spring注入mapper類時)其實最后調(diào)用了Configuration里的getMapper方法, 看下getMapper對應(yīng)代碼:

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

其實調(diào)用的是對應(yīng)的緩存類MapperRegistry管理Mapper代理對象

  1. 打開MapperRegistry類,熟悉的代理模式方法
 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);
    }
  }

此處獲取對應(yīng)的MapperProxy代理類,看下此類核心invoker方法:

 @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
    //聲明對應(yīng)代理內(nèi)部方法直接調(diào)用,如object的方法
      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);
//最后實際執(zhí)行方法,還是回到了SqlSession接口類上
    return mapperMethod.execute(sqlSession, args);
  }

  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }
  1. 看完MapperProxy代理類,我們知道m(xù)ybatis最終返回給用戶使用的是MapperProxy類,但實際干活的還是SqlSession對象,但SqlSession實際是會話級對象,即其scope作用域為線程級別,那此處這個代理類中的成員變量是怎么回事呢?

  2. 答案是此處的SqlSession實現(xiàn)類為SqlSessionTemplate類,其是SqlSession默認(rèn)實現(xiàn)的模版方法類,屬于application級別,看下其構(gòu)造方法

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
    notNull(executorType, "Property 'executorType' is required");

    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
  //此處生成對應(yīng)的sqlSession代理執(zhí)行類,每個線程將在SqlSessionInterceptor創(chuàng)建單獨的SqlSession
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class },
        new SqlSessionInterceptor());
  }
  1. 原來又是一層動態(tài)代理啊!繼續(xù)看其invoker方法,實際分為下面三部
    1. 獲取實際會話對象,此時注意同一個事務(wù)中可以獲取相同的SqlSession
    2. 調(diào)用實際會話對象相應(yīng)方法
    3. 關(guān)閉資源
private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//獲取線程級別SqlSession會話對象,同一個事務(wù)中可以獲取相同的SqlSession,進(jìn)行統(tǒng)一事務(wù)和緩存、延遲加載等處理
      SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);
      try {
//調(diào)用實際sqlSession會話對象
        Object result = method.invoke(sqlSession, args);
        ............
  //關(guān)閉資源
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        
      }
    }
  }
  1. 經(jīng)過多層代理,終于找到了我們會話級別的SqlSession,當(dāng)然還有一些細(xì)節(jié)流程,如事務(wù)處理、 TransactionSynchronizationManager(便于用戶定義事務(wù)提交后處理流程) 處理器管理等等

8.下面SqlSession的實現(xiàn)類,默認(rèn)為DefaultSqlSession,在mybatis中認(rèn)為,任何查詢返回結(jié)果都概括為一個list,單個結(jié)果只是特殊的list,所以selectList方法為其核心查詢方法

 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();
    }
  }

9.首先MappedStatement為定義的方法對應(yīng)的sql信息,映射信息等等數(shù)據(jù),executor又是一種重要概念,代表了mybatis中最終的執(zhí)行器.

  1. 繼續(xù),BaseExecutor明細(xì)的模版方法,執(zhí)行query,跟代碼注釋梳理下核心邏輯:
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
//ErrorContext存儲了對應(yīng)的myatis調(diào)用棧信息,便于排查錯誤
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
//查詢堆棧,
      queryStack++;
// 查看一級緩存是否存在緩存結(jié)果
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
//沒有數(shù)據(jù)從數(shù)據(jù)庫獲取數(shù)據(jù)
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
    //對于延時加載的處理
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      c.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

此處,我們看到了熟悉的mybatis一級緩存概念,對于關(guān)聯(lián)查詢等操作,一級緩存還是很有用的,懶加載此處也有實現(xiàn),在結(jié)果集映射時也會操作deferredLoads此對象,進(jìn)行懶加載操作.
順便此處說下二級緩存.mybatis的二級緩存通過CachingExecutor實現(xiàn),其是對BaseExecutor的包裝類,當(dāng)緩存中沒有時,一樣會調(diào)用BaseExecutor相應(yīng)方法進(jìn)行從數(shù)據(jù)庫中獲取數(shù)據(jù).

  1. 下面核心落到了queryFromDatabase方法中,
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
//通過占位符方式,標(biāo)注是否有緩存數(shù)據(jù)存入
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
//調(diào)用實際查詢方法
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);
    }
//一級緩存數(shù)據(jù)
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }
  1. 下面進(jìn)入SimpleExecutor類的doQuery方法
 public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
//獲取數(shù)據(jù)庫鏈接
      Configuration configuration = ms.getConfiguration();
//包裝 Statement對象,此處一樣用委派模式,進(jìn)行包裝不同的處理類,默認(rèn)實現(xiàn)為PreparedStatementHandler,并且對于mybatis的plugins也是在此處進(jìn)行封裝
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//處理參數(shù)驗證,TypeHandler等轉(zhuǎn)換在此前置處理
      stmt = prepareStatement(handler, ms.getStatementLog());
//最后通過此PreparedStatementHandler執(zhí)行query方法,并通過resultHandler進(jìn)行結(jié)果映射
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }
  1. 下面就簡單了
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
//熟悉的jdbc操作,獲取db數(shù)據(jù)
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
//結(jié)果集映射處理
    return resultSetHandler.<E> handleResultSets(ps);
  }
  1. 在resultSetHandler中,會通過反射創(chuàng)建對應(yīng)的對象實體,賦值到結(jié)果集中,如果有懶加載需要,會添加deferredLoad對列,等待用的時候加載對應(yīng)屬性.

總結(jié)

好了,mybatis主要遠(yuǎn)嘛流程梳理完了.

  1. mybatis多處用到了動態(tài)代理模式,方便使用者,sql和接口定義分離模式

  2. 其中對于SqlSession的代理Template封裝很巧妙,充分學(xué)習(xí)了模版類的神奇之處

  3. ErrorContext實現(xiàn)對于異常排查問題,方便使用

  4. 代理無處不在,handler方便擴(kuò)展,mybatis主旨在于參數(shù)映射,半封裝處理,面向?qū)ο笏季S將各模塊組裝到一起.

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容