天天看點

JavaEE 企業級分布式進階架構師(一)MyBatis架構學習筆記(3)源碼篇

MyBatis學習筆記

  • 源碼篇
    • 接口和對象介紹
      • SqlSessionFactoryBuilder
      • XMLConfigBuilder
      • XMLMapperBuilder
      • Configuration
      • SqlSource接口
      • SqlSessionFactory接口
      • SqlSession接口
      • Executor接口
        • BaseExecutor
        • CachingExecutor
      • StatementHandler接口
        • 定義
        • 繼承結構
        • prepare方法
        • parameterize方法
        • RoutingStatementHandler
      • ParameterHandler接口
      • ResultSetHandler接口
    • 源碼閱讀
      • 加載全局配置檔案(建立Configuration對象)流程
      • 加載映射檔案流程
      • SQL解析流程
      • 擷取Mapper代理對象流程
      • SqlSession執行主流程-執行SQL查詢流程
      • 設定PreparedStatement的參數流程
      • 結果集映射流程
      • 待分析的問題

源碼篇

  • 源碼閱讀的方法:找主線,找入口,記筆記(類名#方法名(資料成員變量)),參考其他人的源碼閱讀經驗。
  • 源碼閱讀的目的:通過閱讀源碼,提升對設計模式的了解,提升程式設計能力;通過閱讀源碼,可以找到問題的根源,用來解決問題;應付面試。

接口和對象介紹

SqlSessionFactoryBuilder

JavaEE 企業級分布式進階架構師(一)MyBatis架構學習筆記(3)源碼篇

XMLConfigBuilder

  • 專門用來解析全局配置檔案的解析器

XMLMapperBuilder

  • 專門用來解析映射檔案的解析器

Configuration

  • Mybatis架構支援開發人員通過配置檔案與其進行交流,在配置檔案中配置的資訊,架構運作時,會被XMLConfigBuilder解析并存儲在一個Configuration對象中。
  • Configuration對象會被作為參數傳給DefaultSqlSessionFactory,而DefaultSqlSessionFactory根據Configuration對象資訊為Client建立對應特征的SqlSession對象。

SqlSource接口

JavaEE 企業級分布式進階架構師(一)MyBatis架構學習筆記(3)源碼篇
  • DynamicSqlSource:主要是封裝動态SQL标簽解析之後的SQL語句和帶有${}的SQL語句
  • RawSqlSource:主要封裝帶有#{}的SQL語句
  • StaticSqlSource:是BoundSql中要存儲SQL語句的一個載體,上面兩個SqlSource的SQL語句,最終都會存儲到該SqlSource實作類中。

SqlSessionFactory接口

  • 預設實作類是DefaultSqlSessionFactory類

SqlSession接口

  • 預設實作類是DefaultSqlSession類

Executor接口

  • 定義:
public interface Executor {
  ResultHandler NO_RESULT_HANDLER = null;
  int update(MappedStatement ms, Object parameter) throws SQLException;
  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
  <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;
  List<BatchResult> flushStatements() throws SQLException;
  void commit(boolean required) throws SQLException;
  void rollback(boolean required) throws SQLException;
  CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);
  boolean isCached(MappedStatement ms, CacheKey key);
  void clearLocalCache();
  void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);
  Transaction getTransaction();
  void close(boolean forceRollback);
  boolean isClosed();
  void setExecutorWrapper(Executor executor);
}
           
  • Mybatis中所有的Mapper語句的執行都是通過Executor進行的,Executor是Mybatis的一個核心接口。
  • 從其定義的接口方法我們可以看出,對應的增删改語句是通過Executor接口的update方法進行的,查詢是通過query方法進行的。
  • Executor是跟SqlSession綁定在一起的,每一個SqlSession都擁有一個新的Executor對象,由Configuration建立,代碼如下:
public Executor newExecutor(Transaction transaction) {
  return newExecutor(transaction, defaultExecutorType);
}
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
  executorType = executorType == null ? defaultExecutorType : executorType;
  executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
  Executor executor;
  if (ExecutorType.BATCH == executorType) {
    executor = new BatchExecutor(this, transaction);
  } else if (ExecutorType.REUSE == executorType) {
    executor = new ReuseExecutor(this, transaction);
  } else {
    executor = new SimpleExecutor(this, transaction);
  }
  if (cacheEnabled) {
    executor = new CachingExecutor(executor);
  }
  executor = (Executor) interceptorChain.pluginAll(executor);
  return executor;
}
           
JavaEE 企業級分布式進階架構師(一)MyBatis架構學習筆記(3)源碼篇
  • Executor分成兩大類:
    • BaseExecutor:抽像類,在建立時會根據傳過來的ExecutorType建立不同的類:SimpleExecutor、ReuseExecutor、BatchExecutor。
    • CachingExecutor:先從緩存中擷取查詢結果,存在就傳回,不存在,再委托給Executor delegate去資料庫取,delegate可以是 BaseExecutor 中的任一一種。

BaseExecutor

  • SimpleExecutor:每執行一次update或select,就開啟一個Statement對象,用完立刻關閉Statement對象。(可以是Statement或PrepareStatement對象)
  • ReuseExecutor:執行update或select,以sql作為key查找Statement對象,存在就使用,不存在就建立,用完後,不關閉Statement對象,而是放置于Map<String, Statement>内,供下一次使用(可以是Statement或PrepareStatement對象)。
  • 代碼如下:
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
    Statement stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.update(stmt);
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    BoundSql boundSql = handler.getBoundSql();
    String sql = boundSql.getSql();
    if (hasStatementFor(sql)) {
        stmt = getStatement(sql);
        applyTransactionTimeout(stmt);
    } else {
        Connection connection = getConnection(statementLog);
        stmt = handler.prepare(connection, transaction.getTimeout());
        putStatement(sql, stmt);
    }
    handler.parameterize(stmt);
    return stmt;
}
private boolean hasStatementFor(String sql) {
    try {
        return statementMap.keySet().contains(sql) && !statementMap.get(sql).getConnection().isClosed();
    } catch (SQLException e) {
        return false;
    }
}
private Statement getStatement(String s) {
    return statementMap.get(s);
}
private void putStatement(String sql, Statement stmt) {
    statementMap.put(sql, stmt);
}
//...
           
  • BatchExecutor:執行update(沒有select,JDBC批處理不支援select),将所有sql都添加到批進行中(addBatch()),等待統一執行(executeBatch()),它緩存了多個Statement對象,每個Statement對象都是addBatch()完畢後,等待逐一執行executeBatch()批處理的;BatchExecutor相當于維護了多個桶,每個桶裡都裝了很多屬于自己的SQL,就像蘋果籃裡裝了很多蘋果,番茄籃裡裝了很多番茄。最後,再統一倒進倉庫。(可以是Statement或PrepareStatement對象)。

CachingExecutor

  • 代碼如下:
public CachingExecutor(Executor delegate) {
    this.delegate = delegate;
    delegate.setExecutorWrapper(this);
}
//...
@Override
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
    flushCacheIfRequired(ms);
    return delegate.update(ms, parameterObject);
}
@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);
}   
...
           

StatementHandler接口

  • 首先約定文中講的四大對象是指:Executor、StatementHandler、ParameterHandler 和 ResultHandler 接口對象。而StatementHandler 毫無疑問是四大對象中最重要的一個,它的任務就是和資料庫對話。在它這裡會使用 ParameterHandler 和ResultHandler 對象為我們綁定SQL參數群組裝最後的結果傳回。

定義

public interface StatementHandler {
  Statement prepare(Connection connection, Integer transactionTimeout)
      throws SQLException;
  void parameterize(Statement statement)
      throws SQLException;
  void batch(Statement statement)
      throws SQLException;
  int update(Statement statement)
      throws SQLException;
  <E> List<E> query(Statement statement, ResultHandler resultHandler)
      throws SQLException;
  <E> Cursor<E> queryCursor(Statement statement)
      throws SQLException;
  BoundSql getBoundSql();
  ParameterHandler getParameterHandler();
}
           
  • 這裡有幾個重要的方法:prepare、parameterize、query、update,他們的作用是不一樣的。

繼承結構

JavaEE 企業級分布式進階架構師(一)MyBatis架構學習筆記(3)源碼篇
  • StatementHandler:頂層接口。
  • BaseStatementHandler : 實作頂層接口的抽象類,實作了部分接口,并定義了一個抽象方法。
  • SimpleStatementHandler:對應JDBC中常用的Statement接口,用于簡單SQL的處理。
  • PreparedStatementHandler:對應JDBC中的PreparedStatement,預編譯SQL的接口。
  • CallableStatementHandler:對應JDBC中CallableStatement,用于執行存儲過程相關的接口。
  • RoutingStatementHandler:這個接口是以上三個接口的路由,沒有實際操作,隻是負責上面三個StatementHandler的建立及調用。
在MyBatis中,Configuration對象會采用new RoutingStatementHandler()來生成StatementHandler對象,換句話說我們真正使用的是RoutingStatementHandler對象,然後它會根據Executor的類型去建立對應具體的statementHandler對象(SimpleStatementHandler,PreparedStatementHandler和CallableStatementHandler)。然後利用具體statementHandler的方法完成所需要的功能。那麼這個具體的statementHandler是儲存在RoutingStatementHandler對象的delegate屬性的,是以當我們攔截statementHandler的時候就要常常通路它了。

prepare方法

  • BaseStatementHandler的三個子類都繼承 prepare() 方法,并沒有重寫該方法。該方法中調用了一個抽象方法 instantiateStatement()。根據一個 Connection 傳回一個Statement 對象;三個子類都實作了該方法,分别傳回了 Statement,PrepareStaement 和 CallableStatement 對象。
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
  ErrorContext.instance().sql(boundSql.getSql());
  Statement statement = null;
  try {
    statement = instantiateStatement(connection);
    setStatementTimeout(statement, transactionTimeout);
    setFetchSize(statement);
    return statement;
  } catch (SQLException e) {
    closeStatement(statement);
    throw e;
  } catch (Exception e) {
    closeStatement(statement);
    throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
  }
}
protected abstract Statement instantiateStatement(Connection connection) throws SQLException;
           
  • 顯然我們通過源碼更加關注抽象方法instantiateStatement是做了什麼事情。它依舊是一個抽象方法,那麼它就有其實作類。
  • 那就是之前說的那幾個具體的StatementHandler對象,讓我們看看PreparedStatementHandler:
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
  // 擷取帶有占位符的SQL語句
  String sql = boundSql.getSql();
  if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
    // 處理帶有主鍵傳回的SQL
    String[] keyColumnNames = mappedStatement.getKeyColumns();
    if (keyColumnNames == null) {
      return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
    } else {
      return connection.prepareStatement(sql, keyColumnNames);
    }
  } else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
    return connection.prepareStatement(sql);
  } else {
    return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
  }
}
           

這個方法非常簡單,我們可以看到它主要是根據上下文來預編譯SQL,這時我們還沒有設定參數。設定參數的任務是交由statement接口的parameterize方法來實作的。

parameterize方法

  • 進行參數設定:
@Override
public void parameterize(Statement statement) throws SQLException {
// 通過ParameterHandler處理參數
  parameterHandler.setParameters((PreparedStatement) statement);
}
           
  • 抽象類的三個子類都實作了 update()、batch()、query(),在對應的方法中将傳入的 Statement 對象轉型為 具體的 Statement 類型進行相應的操作。
  • 以PreparedStatementHandler的實作為例:
@Override
public int update(Statement statement) throws SQLException {
  PreparedStatement ps = (PreparedStatement) statement;
  ps.execute();
  int rows = ps.getUpdateCount();
  Object parameterObject = boundSql.getParameterObject();
  KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
  keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
  return rows;
}
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  PreparedStatement ps = (PreparedStatement) statement;
  // 執行PreparedStatement,也就是執行SQL語句
  ps.execute();
  // 處理結果集
  return resultSetHandler.handleResultSets(ps);
}
           
  • 我們可以看到如果是進行update的,它将會執行生成主鍵的操作(插入資料要自動生成主鍵的時候),然後就傳回影響行數。
  • 如果是進行query的就更加簡單了,它就是執行SQL語句,然後将結果使用resultHandler的handleResultSets去完成我們的結果組裝。

RoutingStatementHandler

  • 該類采用了代理模式(靜态代理),直接實作了 StatmentHandler 接口,實作了接口的全部方法。該類持有一個 StatmentHandler 的執行個體,并在建立該類時根據傳入的 MappedStatement 的 type 建立不同的 Statement 執行個體,然後通過這個具體的執行個體去實作相應的操作。
public class RoutingStatementHandler implements StatementHandler {
  private final StatementHandler delegate;
  public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }
  }
  @Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    return delegate.prepare(connection, transactionTimeout);
  }
  @Override
  public void parameterize(Statement statement) throws SQLException {
    delegate.parameterize(statement);
  }
  @Override
  public void batch(Statement statement) throws SQLException {
    delegate.batch(statement);
  }
  // 其他方法同理
  ...
}
           

ParameterHandler接口

  • 定義
public interface ParameterHandler {
  Object getParameterObject(); // 擷取參數對象
  void setParameters(PreparedStatement ps)
      throws SQLException; // 設定參數對象
}
           
  • 繼承結構:隻有一個預設的實作類
    JavaEE 企業級分布式進階架構師(一)MyBatis架構學習筆記(3)源碼篇
  • 預設實作類:DefaultParameterHandler
public class DefaultParameterHandler implements ParameterHandler {

  private final TypeHandlerRegistry typeHandlerRegistry;

  private final MappedStatement mappedStatement;
  private final Object parameterObject;
  private final BoundSql boundSql;
  private final Configuration configuration;

  public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    this.mappedStatement = mappedStatement;
    this.configuration = mappedStatement.getConfiguration();
    this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
    this.parameterObject = parameterObject;
    this.boundSql = boundSql;
  }

  @Override
  public Object getParameterObject() {
    return parameterObject;
  }

  @Override
  public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    // 擷取要設定的參數映射資訊
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        // 隻處理入參
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          // 擷取屬性名稱
          String propertyName = parameterMapping.getProperty();
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            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);
          }
          // 擷取每個參數的類型處理器,去設定入參和擷取傳回值
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          // 擷取每個參數的JdbcType
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
    	    // 給PreparedStatement設定參數
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          } catch (SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }
}
           
  • 它的 setParameters() 隻在兩處地方被調用,分别為 PreparedStatementHandler 和 CallableStatementHandler 的 parameterize()。

ResultSetHandler接口

  • 定義:
public interface ResultSetHandler {
  // 将Statement執行後産生的結果集(可能有多個結果集)映射為結果清單
  <E> List<E> handleResultSets(Statement stmt) throws SQLException;
  <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;
  // 處理存儲過程執行後的輸出參數
  void handleOutputParameters(CallableStatement cs) throws SQLException;
}
           
  • 繼承結構
    JavaEE 企業級分布式進階架構師(一)MyBatis架構學習筆記(3)源碼篇
  • 預設實作類:ResultSetHandler的具體實作類是DefaultResultSetHandler,其實作的步驟就是将Statement執行後的結果集,按照Mapper檔案中配置的ResultType或ResultMap來封裝成對應的對象,最後将封裝的對象傳回 。

源碼閱讀

  • Mybatis整體架構
    JavaEE 企業級分布式進階架構師(一)MyBatis架構學習筆記(3)源碼篇
  • MyBatis源碼包對應的架構圖
    JavaEE 企業級分布式進階架構師(一)MyBatis架構學習筆記(3)源碼篇

加載全局配置檔案(建立Configuration對象)流程

  • 找入口:SqlSessionFactoryBuilder#build()
/**
 * 主線一:加載全局配置檔案流程
 */
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
  try {
    // XMLConfigBuilder 用來解析XML配置檔案
    // 使用建構者模式
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
    // parser.parse():使用XPath解析XML配置檔案,将配置檔案資訊封裝為Configuration對象
    // 傳回 DefaultSqlSessionFactory 對象,改對象擁有Configuration對象(封裝了配置檔案資訊的)
    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.
    }
  }
}
           
  • 記筆記
SqlSessionFactoryBuilder#build(...):用于建構SqlSessionFactory對象
	|--XMLConfigBuilder#構造函數(...):用來解析全局檔案的解析器
		|--XPathParser#構造函數(...):用來使用XPath文法解析XML的解析器
			|--XPathParser#createDocument(...):解析全局配置檔案,封裝為Document對象(封裝一些子節點,使用XPath文法解析擷取)
		|--Configuration#構造函數(...):建立Configuration對象,同時初始化内置類的别名
	|--XMLConfigBuilder#parse(...):全局配置檔案的解析器
		|--XPathParser#parse(XPath文法):XPath解析器,專門用來通過XPath文法解析XML傳回XNode節點
		|--XMLConfigBuilder#parseConfiguration(XNode):從全局配置檔案根節點開始解析,加載的資訊設定到Configuration對象中
	|--SqlSessionFactoryBuilder#build(...):建立SqlSessionFactory接口的預設實作類DefaultSqlSessionFactoryBuilder
           
  • 總結:
    • SqlSessionFactoryBuilder建立SqlSessionFactory時,需要傳入一個Configuration對象
    • XMLConfigBuilder對象會去執行個體化Configuration
    • XMLConfigBuilder對象會去初始化Configuration對象
      • 通過XPathParser去解析全局配置檔案,形成Document對象
      • 通過XPathParser去擷取指定節點的XNode對象
      • 解析XNode對象的資訊,然後封裝到Configuration對象中
  • 主要涉及到的類:
SqlSessionFactoryBuilder
XMLConfigBuilder
XPathParser
Configuration
           

加載映射檔案流程

  • 找入口:XMLConfigBuilder#mapperElement()
/**
 * 主線二:解析<mappers>标簽
 */
private void mapperElement(XNode parent) throws Exception {
  if (parent != null) {
    // 擷取<mappers>标簽的子标簽
    for (XNode child : parent.getChildren()) {
      // <package>子标簽
  	if ("package".equals(child.getName())) {
        // 擷取mapper接口和mapper映射檔案對應的package包名
  	  String mapperPackage = child.getStringAttribute("name");
        // 将包下所有的mapper接口以及它的代理對象存儲到一個Map集合中
  	  // key為mapper接口類型,value為代理對象工廠
  	  configuration.addMappers(mapperPackage);
      } else {// <mapper>子标簽
        // 擷取<mapper>子标簽的resource屬性
        String resource = child.getStringAttribute("resource");
        // 擷取<mapper>子标簽的url屬性
        String url = child.getStringAttribute("url");
        // 擷取<mapper>子标簽的class屬性
        String mapperClass = child.getStringAttribute("class");
        // 按照 resource --> url --> class 的優先級去解析<mapper>子标簽,他們是互斥的
        if (resource != null && url == null && mapperClass == null) {
          ErrorContext.instance().resource(resource);
          InputStream inputStream = Resources.getResourceAsStream(resource);
          // XMLMapperBuilder專門用來解析mapper映射檔案
          XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
          // XMLMapperBuilder調用parse方法解析mapper映射檔案
          mapperParser.parse();
        } else if (resource == null && url != null && mapperClass == null) {
          ErrorContext.instance().resource(url);
          InputStream inputStream = Resources.getUrlAsStream(url);
          XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
          // XMLMapperBuilder調用parse方法解析mapper映射檔案
          mapperParser.parse();
        } else if (resource == null && url == null && mapperClass != null) {
          Class<?> mapperInterface = Resources.classForName(mapperClass);
          // 将指定mapper接口以及它的代理對象存儲到一個Map集合中
          // key為mapper接口類型,value為代理對象工廠
          configuration.addMapper(mapperInterface);
        } else {
          throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
        }
      }
    }
  }
}
           
  • 記筆記
XMLConfigBuilder#mapperElement(...):解析全局配置檔案的<mappers>标簽
	|--XMLMapperBuilder#構造函數(...):專門用來解析映射檔案
		|--XPathParser#構造函數(...):
			|--XPathParser#createDocument(...):解析映射檔案,封裝為Document對象
			|--MapperBuilderAssistant#構造函數(...):用于建構MappedStatement對象
	|--XMLMapperBuilder#parse(...):解析方法
		|--XMLMapperBuilder#configurationElement(...):專門用來解析mapper映射檔案
			|--XMLMapperBuilder#buildStatementFromContext(...):用來建立MappedStatement對象的
				|--|--XMLMapperBuilder#buildStatementFromContext(...):
					|--XMLStatementBuilder#構造函數(...):專門用來解析MappedStatement
					|--XMLStatementBuilder#parseStatementNode(...):
						|--MapperBuilderAssistant#addMappedStatement(...):建立MappedStatement
							|--MappedStatement.Builder#構造函數(...)
							|--MappedStatement#build(...):建立MappedStatement對象,并存儲到Configuration對象中
           
  • 主要涉及到的類:
XMLConfigBuilder
XMLMapperBuilder
XPathParser
MapperBuilderAssistant
XMLStatementBuilder
MappedStatement.Builder
MappedStatement
           

SQL解析流程

  • 找入口:XMLLanguageDriver#createSqlSource
/**
 * 主線三:SQL解析
 */
@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
  // 初始化了動态SQL标簽處理器
  XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
  // 解析動态SQL
  return builder.parseScriptNode();
}
           
  • 記筆記
XMLLanguageDriver#createSqlSource(...):擷取解析之後的SQL資訊,以及參數資訊
	|--XMLScriptBuilder#構造函數(...):初始化動态SQL中的節點處理器集合
	|--XMLScriptBuilder#parseScriptNode(...):
		|--XMLScriptBuilder#parseDynamicTags(...):解析動态SQL标簽
|--SqlSource#getBoundSql(...)
	|--SqlSourceBuilder#parse(...):将帶有#{}的SQL語句進行解析,然後封裝到StaticSqlSource中
           
  • 主要涉及到的類:
XMLLanguageDriver
XMLScriptBuilder
SqlSource
SqlSourceBuilder
           

擷取Mapper代理對象流程

  • 找入口:DefaultSqlSession#getMapper(…)
/**
 * 主線四:擷取Mapper代理對象
 */
@Override
public <T> T getMapper(Class<T> type) {
  // 從Configuration對象中,根據Mapper接口,擷取Mapper代理對象
  return configuration.<T>getMapper(type, this);
}
           
  • 記筆記
DefaultSqlSession#getMapper(...):擷取Mapper代理對象
	|--Configuration#getMapper(...):擷取Mapper代理對象
		|--MapperRegistry#getMapper(...):通過代理對象工廠,擷取代理對象
		 |--MapperProxyFactory#newInstance(...):調用JDK的動态代理方式,建立Mapper代理
           
  • 主要涉及到的類:
DefaultSqlSession
Configuration
MapperRegistry
MapperProxyFactory
           

SqlSession執行主流程-執行SQL查詢流程

  • 找入口:DefaultSqlSession#selectList(…)
/**
 * 主線五:SqlSession執行
 */
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  try {
    // 根據傳入的statementId,擷取MappedStatement對象
    MappedStatement ms = configuration.getMappedStatement(statement);
    // 調用執行器的查詢方法
    // RowBounds是用來邏輯分頁(按照條件将資料從資料庫查詢到記憶體中,在記憶體中進行分頁)
    // wrapCollection(parameter)是用來裝飾集合或者數組參數
    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();
  }
}
           
  • 記筆記
DefaultSqlSession#selectList(...)
	|--CachingExecutor#query(...)
		|--BaseExecutor#query(...)
			|--BaseExecutor#queryFromDatabase(...)
				|--SimpleExecutor#doQuery(...)
					|--Configuration#newStatementHandler(...):建立StatementHandler用來執行MappedStatement對象
						|--RoutingStatementHandler#構造函數(...):根據路由規則,設定不同的StatementHandler
					|--SimpleExecutor#prepareStatement(...):主要設定PreparedStatement的參數
						|--SimpleExecutor#getConnection(...):擷取資料庫連接配接
						|--PreparedStatementHandler#prepare(...):建立PreparedStatement對象
						|--PreparedStatementHandler#parameterize:設定PreparedStatement的參數
					|--PreparedStatementHandler#query(...):主要用來執行SQL語句,以及處理結果集
						|--PreparedStatement#execute():調用JDBC的API執行Statement
							|--DefaultResultSetHandler#handleResultSets:處理結果集
						
           
  • 主要涉及到的類:
DefaultSqlSession
CachingExecutor
BaseExecutor
SimpleExecutor
Configuration
RoutingStatementHandler
PreparedStatementHandler
PreparedStatement
DefaultResultSetHandler
           

設定PreparedStatement的參數流程

  • 找入口:PreparedStatementHandler#parameterize(…)
/**
 * 主線六:PreparedStatement 進行參數設定
 */
@Override
public void parameterize(Statement statement) throws SQLException {
// 通過ParameterHandler處理參數
  parameterHandler.setParameters((PreparedStatement) statement);
}
           
  • 記筆記
PreparedStatementHandler#parameterize(...):設定PreparedStatement的參數
	|--DefaultParameterHandler#setParameters(...):設定參數
		|--BaseTypeHandler#setParameter(...):
			|--XxxTypeHandler#setNonNullParameter(...):調用PreparedStatement的setXxx方法
           
  • 主要涉及到的類:
PreparedStatementHandler
DefaultParameterHandler
BaseTypeHandler
XxxTypeHandler
           

結果集映射流程

  • 找入口:DefaultResultSetHandler#handleResultSets(…)
/**
 * 主線七:結果集映射
 */
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
  ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

  // <select>标簽的resultMap屬性,可以指定多個值,多個值之間用逗号(,)分割
  final List<Object> multipleResults = new ArrayList<>();

  int resultSetCount = 0;
  // 這裡是擷取第一個結果集,将傳統JDBC的ResultSet包裝成一個包含結果列元資訊的ResultSetWrapper對象
  ResultSetWrapper rsw = getFirstResultSet(stmt);

  // 這裡是擷取所有要映射的ResultMap(按照逗号分割出來的)
  List<ResultMap> resultMaps = mappedStatement.getResultMaps();
  // 要映射的ResultMap的數量
  int resultMapCount = resultMaps.size();
  validateResultMapsCount(rsw, resultMapCount);
  // 循環處理每個ResultMap,從第一個開始處理
  while (rsw != null && resultMapCount > resultSetCount) {
    // 得到結果映射資訊
    ResultMap resultMap = resultMaps.get(resultSetCount);
    // 處理結果集
    // 從rsw結果集參數中擷取查詢結果,再根據resultMap映射資訊,将查詢結果映射到multipleResults中
    handleResultSet(rsw, resultMap, multipleResults, null);
    
    rsw = getNextResultSet(stmt);
    cleanUpAfterHandlingResultSet();
    resultSetCount++;
  }

  // 對應<select>标簽的resultSets屬性,一般不使用該屬性
  String[] resultSets = mappedStatement.getResultSets();
  if (resultSets != null) {
    while (rsw != null && resultSetCount < resultSets.length) {
      ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
      if (parentMapping != null) {
        String nestedResultMapId = parentMapping.getNestedResultMapId();
        ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
        handleResultSet(rsw, resultMap, null, parentMapping);
      }
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }
  }

  // 如果隻有一個結果集合,則直接從多結果集中取出第一個
  return collapseSingleResultList(multipleResults);
}
           
  • 記筆記
DefaultResultSetHandler#handleResultSets(...)
	|--DefaultResultSetHandler#handleResultSet(...)
		|--DefaultResultSetHandler#handleRowValues(...)
			|--DefaultResultSetHandler#handleRowValuesForNestedResultMap(...)
				|--DefaultResultSetHandler#getRowValue(...)
					|--DefaultResultSetHandler#createResultObject(...):建立映射結果對象
					|--DefaultResultSetHandler#applyAutomaticMappings(...)
					|--DefaultResultSetHandler#applyPropertyMappings(...)
           
  • 主要涉及到的類:
DefaultResultSetHandler
           
  • resultType最終會封裝成resultMap

待分析的問題

  • 延遲加載的流程
  • 嵌套結果集映射流程

繼續閱讀