[Mybatis source code analysis] Parse statement tag_Select|Update|Insert|Delete

I. Introduction

Before elaborating on parsing statement labels, we must first know where the content of our statement labels is finally encapsulated in Configuration? (Everyone should know that Mybatis uses XMLConfigBuilder to parse xml and then encapsulates it into a Configuration object and passes it to SqlSessionFactory for further execution).

The parsed content of the statement label is encapsulated into the mappedStatements Map object in Configuration, which is the following attributes:

protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>(
      "Mapped Statements collection")
          .conflictMessageProducer((savedValue, targetValue) -> ". please check " + savedValue.getResource() + " and "
              + targetValue.getResource());

Its key corresponds to the tag id, and MappedStatement is an encapsulation instance class for the content of the statement tag (it is a final class, which is ultimately built through the internal Builder class build, and no structure is provided to the outside).You can understand that a Select|Update|Insert|Delete statement label maps to a MappedStatement, just like a ResultMap field corresponds to a ResultMapping.. The internal properties are as follows:

  private String resource;// 源自于哪个mapper文件
  private Configuration configuration;// 配置文件
  private String id;// id
  private Integer fetchSize;// 每次从服务器获取的数量
  private Integer timeout;// 最长访问数据库的时间
  private StatementType statementType;// STATEMENT、PREPARED、CALLABLE
  private ResultSetType resultSetType;
  private SqlSource sqlSource;// 对SQL的包装
  private Cache cache;// 缓存
  private ParameterMap parameterMap;
  private List<ResultMap> resultMaps;// 结果映射
  private boolean flushCacheRequired;// 是否需要缓存刷新
  private boolean useCache;// 是否使用二级缓存
  private boolean resultOrdered;
  private SqlCommandType sqlCommandType;// UNKNOWN、INSERT、UPDATE、SELECT
  private KeyGenerator keyGenerator;// 主键生成器
  private String[] keyProperties;
  private String[] keyColumns;
  private boolean hasNestedResultMaps;// 是否存在嵌套查询结果集
  private String databaseId;
  private Log statementLog;
  private LanguageDriver lang;// 语言驱动,永磊解析动态或静态SQL
  private String[] resultSets;// resultset
  private boolean dirtySelect;

Many attributes are well known to everyone, such as SqlSource and LanguageDriver. There is also source code analysis of them in my previous blog. The latter is to obtain the former, and the former is to obtain the encapsulation of the sql to be executed (boundsql ). [Mybatis source code analysis] The underlying principle of dynamic tags, DynamicSqlSource source code analysis

The process is roughly as shown below:
Insert image description here

Let’s stop here first and start the real source code analysis.

2. Source code analysis of statement tags

Since this is a blog, I will not analyze the source code from beginning to end, but only analyze the core part. Before proceeding, I will first make an overview of the constructors used when Mybatis parses xml, and understand the overall understanding. Source code analysis is more helpful (I got it after watching the source code myself. If there are any problems, you can point them out in the comment area). (Although the picture is intuitive, it feels so cool when I think of it. I don’t know how many walnuts I would have to eat to design it.)

Please add image description

The core content of parsing statement tags is in theXMLStatementBuilder.parseStatementNode method. After parsing the tag content, it is mapped into a MapperStatement through the MapperBuilderAssistant object and then encapsulated into Configuration. of mappedStatements.

The core source code is as follows - with deletions (you can take a rough look at the details or main parts, there are pictures below to explain in detail):

public void parseStatementNode() {
    
    
    String id = context.getStringAttribute("id");

    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
    // 这是对 sql 片段的引用,在下面对 sql 标签进行了源码分析
    // 这里是通过 include 标签对 sql 标签内容的引用
    // 可以说是替换内容吧
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);
		
	// 这里的话可以自定义 LanguageDriver 对象然后去使用然后获取对应的 SqlSource对象
	// 这里的话默认是 XMLLanguageDriver
    String lang = context.getStringAttribute("lang");
    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;
    }

    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);

	// 去得到最后执行语句准备使用的语句类型,默认是 PreparedStatement
    StatementType statementType = StatementType
        .valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));

    String parameterMap = context.getStringAttribute("parameterMap");
    
    // 获取返回值类型
    String resultType = context.getStringAttribute("resultType");
    Class<?> resultTypeClass = resolveClass(resultType);
    String resultMap = context.getStringAttribute("resultMap");
    if (resultTypeClass == null && resultMap == null) {
    
    
      resultTypeClass = MapperAnnotationBuilder.getMethodReturnType(builderAssistant.getCurrentNamespace(), id);
    }
   
   // 通过助手去构建 MappedStatement,
   // 并且封装进 configuration 中的 mappedStatements中
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap,
        parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered,
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets, dirtySelect);
  }

Test xml:

    <sql id="person_test">
        id,name,age,sex
    </sql>

    <select id="hhh" resultType="com.ncpowernode.mybatis.bean.Person">
        select <include refid="person_test"/>
        from t_person
    </select>

Below I think several main attribute analysis during the construction process will be specifically explained:

If you do not configure the lang attribute, it will be parsed through XMLLanguageDriver by default.

Insert image description hereAccording to the test statements, I did not use dynamic SQL, so the SQLSource parsing result should be a RawSQLSource object.

Insert image description here

If the corresponding StatementType statement type is not specified, PreparedStatement is used by default.

Insert image description here

If it is a Select statement label, you need to specify the resultType attribute or resultMap (if neither is specified, the class specified by namespace will be used as the return object):

Insert image description here

Then use the Mapper assistant to construct the MappedStatement object and map it.

Insert image description here
That is, it is built through MappedStatement.Builder and then encapsulated into configuration.

Insert image description here
For encapsulation, just put (fully qualified id, MappedStatement object) directly

Insert image description here

3. Parsing of sql tags

The test code is as follows:

	<sql id="person_test">
        id,name,age,sex
    </sql>

    <select id="hhh" resultType="com.ncpowernode.mybatis.bean.Person">
        select <include refid="person_test"/>
        from t_person
    </select>    
    @Test
    public void testSqlTag(){
    
    
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = builder.build(Thread.currentThread().getContextClassLoader().getResourceAsStream("mybatis-config.xml"));
        SqlSession sqlSession = sqlSessionFactory.openSession();
        PersonMapper mapper = sqlSession.getMapper(PersonMapper.class);
        List<Person> xx = mapper.hhh();
    }

The core parsing source code is as follows (the essence is to encapsulate the content of sql fragments into the sqlFragments map, and then use it in statement tags later)

  private void sqlElement(List<XNode> list, String requiredDatabaseId) {
    
    
    for (XNode context : list) {
    
    
      String databaseId = context.getStringAttribute("databaseId");
      String id = context.getStringAttribute("id");
      // 合并成全id,namespace.id 这种形式,全限定id
      id = builderAssistant.applyCurrentNamespace(id, false);
      if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
    
    
        sqlFragments.put(id, context);
      }
    }
  }

4. Summary

  • In fact, through these source code analysis, we also know: why the default is to use XMLLanguageDriver to obtain SQLSource; why the default is to use PreparedStatement to manipulate the final SQL; In the query Select statement, no specification Are resultType and resultMap attributes OK? OK, but the default is to return the class specified by your namespace.
  • We can specify the corresponding LanguageDriver implementation class to obtain SqlSource by configuring the lang attribute of the tag. Of course, I think the default one is great, and you shouldn’t need to write this yourself.
  • In the mapper file, the sql tag is used, and you can use the include tag to use the content in the sql tag.

The two pictures I drew below feel quite vivid:
Insert image description here

Please add image description

A little digression here:When parsing dynamic tags in XMLScriptBuilder, ${} is also parsed into dynamic sql, and the corresponding dynamic SQLNode is a>TextSqlNode, the internal apply method is parsed through GenericTokenParser and then encapsulated into DynamicContext. When parsing #{}, it is not counted as dynamic sql. This is because whether it is RawSqlSource or DynamicSqlSource, #{} will be processed through the SqlSourceBuilder.parse method. (I had a vague understanding of it in my last blog, so I’ll clarify it again after making it clear here)

Guess you like

Origin blog.csdn.net/qq_63691275/article/details/132406086