MyBatis源碼的學習(7)---MappedStatement的的創建

SqlSessionFactoryBuilder--->XMLConfigBuilder---> XMLMapperBuilder---(橋樑)MapperBuilderAssistant--->XMLStatementBuilder(parseStatementNode方法)--(橋樑)MapperBuilderAssistant(addMappedStatement方法)-->MappedStatement.Builder--->MappedStatement--->confuguration.addMappedStatement()

 

sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);


public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }




private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      loadCustomLogImpl(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      //這一步,根據setting解析的結果,設置我們的一些參數,如果沒有設置則給默認值。
      //我們的配置如下:
        /**
           <settings>
             <setting name="cacheEnabled" value="false"/>
             <setting name="logImpl" value="STDOUT_LOGGING" />
           </settings>
        */
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      //這一步,解析Mapper.xml
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

/**
<mappers>
    <mapper resource="userMapper.xml"/>    
</mappers>

*/
private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {//子節點 是<package>這樣的
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {//我們在這個分支 ,先獲取 resource,url,class這三個屬性的值
        //我們的resource值爲:userMapper.xml
        //這三個屬性值,只能三選一。
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
         //我們在這個分支
            ErrorContext.instance().resource(resource);
            //讀取我們的userMapper.xml文件,轉爲輸入流
            InputStream inputStream = Resources.getResourceAsStream(resource);
            //創建XMLMapper的Builder對象
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            //生成我們的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());
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }


下面分析這一句,看看如何創建一個Mapper對象的,就是一個mapper.xml文件的抽象。 

   mapperParser.parse();

/**
    下面的方法來自XMLMapperBuilder類
*/

public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }
/**
   負責解析 <mapper></mapper>這一對標籤

<mapper namespace="learn.UserMapper">
    <cache/>    
    <select resultType="learn.User" parameterType="String" id="selectUser">
    select * from u_user where usercode = #{id}
  </select>
    <update parameterType="String" id="updateName">
    update u_user set username = #{name} where usercode= #{id}
  </update>
    <insert parameterType="learn.User" id="insert">
    insert into u_user(usercode,username,createtime,usertype,mobile) values (#{userCode},#{userName}
    ,#{createTime},#{userType},#{mobile});
  </insert>
    <insert parameterType="learn.User" id="batchInsert">
        <foreach item="user" separator="," collection="list">
      (#{user.userCode},#{user.userName}
      ,#{user.createTime},#{user.userType},#{user.mobile})
    </foreach>
    </insert>
</mapper>
*/
private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      cacheRefElement(context.evalNode("cache-ref"));
      //這裏是負責生成二級緩存的,如果有 </cache>標籤就生成一個緩存
      cacheElement(context.evalNode("cache"));
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      sqlElement(context.evalNodes("/mapper/sql"));
      //這是重頭戲,就是負責生成增刪改查四種類型的sql的抽象的 statement對象
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }
/**
爲namespace,生成二級緩存
*/
private void cacheElement(XNode context) {
    if (context != null) {
      String type = context.getStringAttribute("type", "PERPETUAL");
      Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
      String eviction = context.getStringAttribute("eviction", "LRU");
      Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
      Long flushInterval = context.getLongAttribute("flushInterval");
      Integer size = context.getIntAttribute("size");
      boolean readWrite = !context.getBooleanAttribute("readOnly", false);
      boolean blocking = context.getBooleanAttribute("blocking", false);
      Properties props = context.getChildrenAsProperties();
      //爲當前namespace創建一個緩存,雖然我們的總設置是不使用緩存
      builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
    }
  }
/** MapperBuilderAssistant類中
生成一個緩存對象,然後放入到configuration對象中*/
public Cache useNewCache(Class<? extends Cache> typeClass,
      Class<? extends Cache> evictionClass,
      Long flushInterval,
      Integer size,
      boolean readWrite,
      boolean blocking,
      Properties props) {
    //這兒,將生成的緩存和當前的這個namespace綁定,就是當前的這個userMapper.xml文件抽象後
    Cache cache = new CacheBuilder(currentNamespace)
        .implementation(valueOrDefault(typeClass, PerpetualCache.class))
        .addDecorator(valueOrDefault(evictionClass, LruCache.class))
        .clearInterval(flushInterval)
        .size(size)
        .readWrite(readWrite)
        .blocking(blocking)
        .properties(props)
        .build();
    configuration.addCache(cache);
    currentCache = cache;
    return cache;
  }

下面分析這一句,看看如何創建當前這個mapper.xml中的所有的Statement對象的,就是增刪改查sql語句的抽象。 

buildStatementFromContext(context.evalNodes("select|insert|update|delete"));

//mybatis-3\src\main\java\org\apache\ibatis\builder\xml\XMLMapperBuilder.java
  private void buildStatementFromContext(List<XNode> list) {
    if (configuration.getDatabaseId() != null) {
      buildStatementFromContext(list, configuration.getDatabaseId());
    }
    buildStatementFromContext(list, null);
  }

/**
這是遍歷的其中一條
<select resultType="learn.User" parameterType="String" id="selectUser">
    select * from u_user where usercode = #{id}
  </select>

*/
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {//這裏有遍歷,負責生成這個mapper下的所有sql
      //從這裏能夠看出,新建的每一個sql的抽象statement對象都是公用了namespace中的這四個對象
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {//由Builder負責構建一條sql語句
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }
下面分析,如何創建某一條sql的抽象對象statement

statementParser.parseStatementNode();

/**
  XMLStatementBuilder類中
 context 就是:
 <select resultType="learn.User" parameterType="String" id="selectUser">
    select * from u_user where usercode = #{id}
  </select>


*/
public void parseStatementNode(){
    //id就是 selectUser
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");
    //用於判斷,這個sql是否和我們的數據庫一致。比如我們的是mysql,而這條語句配了屬性爲oracle,
    //那麼就不執行
    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }
    //節點名稱,我們的是 select
    String nodeName = context.getNode().getNodeName();
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // Include Fragments before parsing
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);

    String lang = context.getStringAttribute("lang");
   //用於解析sql
    LanguageDriver langDriver = getLanguageDriver(lang);

    // Parse selectKey after includes and remove them.
    processSelectKeyNodes(id, parameterTypeClass, langDriver);

    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    KeyGenerator keyGenerator;
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    if (configuration.hasKeyGenerator(keyStatementId)) {
      keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
          ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }
    //生成我們的動態sql,sql的解析工作
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    Integer fetchSize = context.getIntAttribute("fetchSize");
    Integer timeout = context.getIntAttribute("timeout");
    String parameterMap = context.getStringAttribute("parameterMap");
    String resultType = context.getStringAttribute("resultType");
    Class<?> resultTypeClass = resolveClass(resultType);
    String resultMap = context.getStringAttribute("resultMap");
    String resultSetType = context.getStringAttribute("resultSetType");
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    if (resultSetTypeEnum == null) {
      resultSetTypeEnum = configuration.getDefaultResultSetType();
    }
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    String resultSets = context.getStringAttribute("resultSets");

    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered,
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }

生成一個基礎的非動態的sqlSource:RawSqlSource

//XMLScriptBuilder 類中
public SqlSource parseScriptNode() {
    //用於判斷是否選擇哪種sqlSorce 如果只有'#{}',也沒有動態標籤時,用RawSqlSource
    //'${}'這種的使用DynamicSqlSource
   // select * from u_user where usercode = ${id}
MixedSqlNode rootSqlNode = parseDynamicTags(context);
    SqlSource sqlSource;
    if (isDynamic) { 
    /**
對於動態的sqlSource,類似下面這樣的方法getBoundSql()的時候,我們纔會解析
  @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);
  }
*/
      sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
     //非動態的sqlSource,直接在這個方法裏將‘#{}’處理爲佔位符‘?’
      sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
  }

/**
DynamicSqlSource解析含有${},或動態標籤的sql語句,而RawSqlSource解析只含有#{}的sql語句

*/

//含有${} 的這種只是創建了對象,沒做任何處理。只有我們在調用getBoundSql方法時,會進行解析
//將'${}'替換爲真實的參數值
//parseDynamicTags(context)方法中,只是判斷了是否時動態sqlSource,沒有對'${}'做替換賦值操作
public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
    this.configuration = configuration;
    this.rootSqlNode = rootSqlNode;
  }

public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> clazz = parameterType == null ? Object.class : parameterType;
    //會將 #{} 替換爲 ?
    sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());
  }


這倆個類的getBoundSql的代碼如下:
DynamicSqlSource類中的 getBoundSql
 public BoundSql getBoundSql(Object parameterObject) {
    DynamicContext context = new DynamicContext(configuration, parameterObject);
    rootSqlNode.apply(context);
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    context.getBindings().forEach(boundSql::setAdditionalParameter);
    return boundSql;
  }

RawSqlSource類中的

  @Override
  public BoundSql getBoundSql(Object parameterObject) {
    return sqlSource.getBoundSql(parameterObject);
  }




 public boolean isDynamic() {
    DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
    GenericTokenParser parser = createParser(checker);
    parser.parse(text);
    return checker.isDynamic();
  }

  private GenericTokenParser createParser(TokenHandler handler) {
    return new GenericTokenParser("${", "}", handler);
  }

上面是用於解析sql中的“${}”這樣的入參形式。

GenericTokenParser類用於解析“${id}”,“#{id}”這樣的入參,
DynamicCheckerTokenParser 這個是真實的
處理器,它只是進行了判斷是否存在'${}'。
而對‘${}’的賦值操作時用的處理器BindingTokenParser
處理“#{}”的處理器是ParameterMappingTokenHandler(這是一個內部類)

DynamicSqlSource解析含有${}的sql語句,而RawSqlSource解析只含有#{}的sql語句

在我們的getBoundSql的時候,DynamicSqlSource涵蓋的操作比RawSqlSource多了一步,便是優先處理${}字符,動態標籤,然後其本身解析‘#{}’。

另外,類似我們平時使用的 <where>,<foreach>,<trim>這些動態標籤的,最終的解析器也是DynamicSqlSource。具體的判斷邏輯在parseDynamicTags方法中。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章