1. 程式人生 > >Mybatis 整體流程詳解、部分原始碼解讀以及運用到了哪些設計模式

Mybatis 整體流程詳解、部分原始碼解讀以及運用到了哪些設計模式

MyBatis主要的類

  • Configuration        MyBatis所有的配置資訊都維持在Configuration物件之中。
  • 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|update|delete|insert>節點的封裝,
  • SqlSource              負責根據使用者傳遞的parameterObject,動態地生成SQL語句,將資訊封裝到BoundSql物件中,並返回
  • BoundSql              表示動態生成的SQL語句以及相應的引數資訊

設計模式的應用

  • Builder模式,例如SqlSessionFactoryBuilder、XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder、CacheBuilder;

  • 工廠模式,例如SqlSessionFactory、ObjectFactory、MapperProxyFactory;

  • 單例模式,例如ErrorContext和LogFactory;

  • 代理模式,Mybatis實現的核心,比如MapperProxy、ConnectionLogger,用的jdk的動態代理;還有executor.loader包使用了cglib或者javassist達到延遲載入的效果;

  • 組合模式,例如SqlNode和各個子類ChooseSqlNode等;

  • 模板方法模式,例如BaseExecutor和SimpleExecutor,還有BaseTypeHandler和所有的子類例如IntegerTypeHandler;

  • 介面卡模式,例如Log的Mybatis介面和它對jdbc、log4j等各種日誌框架的適配實現;

  • 裝飾者模式,例如Cache包中的cache.decorators子包中等各個裝飾者的實現;

  • 迭代器模式,例如迭代器模式PropertyTokenizer;

原始碼解讀


public class TestMain {

    public static void main(String[] args) throws IOException {
       String resouce = "conf.xml";
        InputStream is = Resources.getResourceAsStream(resouce);

        // 構建sqlSession工廠
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
        // 獲取sqlSession
        SqlSession session = sqlSessionFactory.openSession();

        User user;

        try {
            /**
             * 第一種方式: 直接執行已對映的 SQL 語句
             */
            String statement = "com.hht.dao.UserDao.getById";
            user = session.selectOne(statement, 1);
            System.out.println(user);
        }
        finally {
            session.close();
        }

        /**
         * 第二種方式: 執行更清晰和型別安全的程式碼
         */
        UserDao userDao = session.getMapper(UserDao.class);
        user = userDao.getById(1);
        System.out.println(user);
    }

}

selectOne 原始碼,返回結果超過1報錯

  @Override
  public <T> T selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    List<T> list = this.<T>selectList(statement, parameter);
    if (list.size() == 1) {
      return list.get(0);
    } else if (list.size() > 1) {
      throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
      return null;
    }
  }

selectOne()會呼叫selectList()

// DefaultSqlSession類
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
        MappedStatement ms = configuration.getMappedStatement(statement);
        // CURD操作是交給Excetor去處理的
        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();
    }
}

executor.query 解讀,使用CachingExecutor

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

getBoundSql 封裝sql

public BoundSql getBoundSql(Object parameterObject) {
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings == null || parameterMappings.isEmpty()) {
      boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
    }

    // check for nested result maps in parameter mappings (issue #30)
    for (ParameterMapping pm : boundSql.getParameterMappings()) {
      String rmId = pm.getResultMapId();
      if (rmId != null) {
        ResultMap rm = configuration.getResultMap(rmId);
        if (rm != null) {
          hasNestedResultMaps |= rm.hasNestedResultMaps();
        }
      }
    }

封裝快取key

 @Override
  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    CacheKey cacheKey = new CacheKey();
    cacheKey.update(ms.getId());
    cacheKey.update(rowBounds.getOffset());
    cacheKey.update(rowBounds.getLimit());
    cacheKey.update(boundSql.getSql());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
    // mimic DefaultParameterHandler logic
    for (ParameterMapping parameterMapping : parameterMappings) {
      if (parameterMapping.getMode() != ParameterMode.OUT) {
        Object value;
        String propertyName = parameterMapping.getProperty();
        if (boundSql.hasAdditionalParameter(propertyName)) {
          value = boundSql.getAdditionalParameter(propertyName);
        } else if (parameterObject == null) {
          value = null;
        } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          value = parameterObject;
        } else {
          MetaObject metaObject = configuration.newMetaObject(parameterObject);
          value = metaObject.getValue(propertyName);
        }
        cacheKey.update(value);
      }
    }
    if (configuration.getEnvironment() != null) {
      // issue #176
      cacheKey.update(configuration.getEnvironment().getId());
    }
    return cacheKey;
  }

呼叫下面的方法,這裡根據快取key先從快取獲取,條件是useCache為true,屬於二級快取,預設關閉

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    Cache cache = ms.getCache();
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

接下來呼叫BaseExecutor.java 這裡 localCache.getObject(key) 屬於一級快取,預設開啟

  @SuppressWarnings("unchecked")
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    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++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

關於快取:

  • 當為select語句時:

flushCache預設為false,表示任何時候語句被呼叫,都不會去清空本地快取和二級快取。

useCache預設為true,表示會將本條語句的結果進行二級快取。

  • 當為insert、update、delete語句時:

flushCache預設為true,表示任何時候語句被呼叫,都會導致本地快取和二