In layman's language to create a process Mybatis source of analytical --SqlSource

Foreword

In a previous article in layman's language Mybatis source code parsing - mapping file loading process , the last when it comes to creating SqlSource and create MappedStatement objects, finally had to terminate due to space reasons, so they had to write an article for the said one such SqlSource What is the process to create, in the first article of this series is completed, have asked one or two readers know, there is that is not able to understand, I might write a blog is not illustrated, so next blog post ready to organize a complete implementation of a flow chart of Mybatis to the reader with a clear understanding of what Mybatis execution of such a process.

This article is about to begin, please fasten your seat belts!

A dynamic SQL processor label

Before re-started, the code first look at the createSqlSource method implementation, the code is as follows:

@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
	// 初始化了动态SQL标签处理器
	XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
	// 解析动态SQL
	return builder.parseScriptNode();
}

Note that this method is XMLLanguageDriver.java this class, since we saw the entrance of the place, look at Mybatis of SqlSource is created as the above code first built XMLScriptBuilder this subject, I feel the need to look at this constructors do, because I feel this constructor extraordinary. Ado, directly on the code:

	public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) {
		super(configuration);
		this.context = context;
		this.parameterType = parameterType;
		// 初始化动态SQL中的节点处理器集合
		initNodeHandlerMap();
	}

	private void initNodeHandlerMap() {
		nodeHandlerMap.put("trim", new TrimHandler());
		nodeHandlerMap.put("where", new WhereHandler());
		nodeHandlerMap.put("set", new SetHandler());
		nodeHandlerMap.put("foreach", new ForEachHandler());
		nodeHandlerMap.put("if", new IfHandler());
		nodeHandlerMap.put("choose", new ChooseHandler());
		nodeHandlerMap.put("when", new IfHandler());
		nodeHandlerMap.put("otherwise", new OtherwiseHandler());
		nodeHandlerMap.put("bind", new BindHandler());
	}

This is extraordinary? Some people may be skeptical, but you look initNodeHandlerMap method saved so many objects that are basically mapper file some where, foreach, if these and other label objects. So I think nodeHandlerMap certainly not an ordinary map collection. Then we take a simple look nodeHandlerMap:

private final Map<String, NodeHandler> nodeHandlerMap = new HashMap<>();

private interface NodeHandler {
	void handleNode(XNode nodeToHandle, List<SqlNode> targetContents);
}

private class BindHandler implements NodeHandler {
	public BindHandler() {
		// Prevent Synthetic Access
	}

	@Override
	public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
		final String name = nodeToHandle.getStringAttribute("name");
		final String expression = nodeToHandle.getStringAttribute("value");
		final VarDeclSqlNode node = new VarDeclSqlNode(name, expression);
		targetContents.add(node);
	}
}

private class WhereHandler implements NodeHandler {
	public WhereHandler() {
		// Prevent Synthetic Access
	}

	@Override
	public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
                // 混合SQL节点
		MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
		WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode);
		targetContents.add(where);
	}
}

As can be seen from the above code, nodeHandlerMap it stored so that NodeHandler internal interface object, then the process initNodeHandlerMap objects must be stored to achieve the NodeHandler. So here are two related implementation class code, the code for WhereHandler implemented in the parseDynamicTags method, then we will be explained, because those object implementation, most call this method. We still continue to look back builder.parseScriptNode ().

Second, the dynamic SQL parsing

Call builder.parseScriptNode () or in createSqlSource method, in that case, it would look at this parseScriptNode methods in the end what has been done:

public SqlSource parseScriptNode() {
	// 获取解析过的SQL信息(解析了动态SQL标签和${})
	// 注意:此时SQL语句中还有#{}没有处理
	MixedSqlNode rootSqlNode = parseDynamicTags(context);
	SqlSource sqlSource = null;
	// 如果包含${}和动态SQL语句,就是dynamic的
	if (isDynamic) {
		sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
	} else {
		// 否则是RawSqlSource的(带有#{}的SQL语句)
		sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
	}
	return sqlSource;
}

The method is very simple, it is first of all to get a MixedSqlNode objects through parseDynamicTags method, then began to determine whether this is dynamic SQL, the standard is to determine if it contains $ {} and dynamic SQL statements, then that is the dynamic SQL, otherwise RawSqlSource. Some students may think this isDynamic is how come, it will be announced this problem and so do not worry.

We first look at the parseDynamicTags in the end what has been done, then look at each construction method DynamicSqlSource and RawSqlSource objects.

protected MixedSqlNode parseDynamicTags(XNode node) {
	List<SqlNode> contents = new ArrayList<>();
	//获取<select>\<insert>等4个标签的子节点,子节点包括元素节点和文本节点
	NodeList children = node.getNode().getChildNodes();
	for (int i = 0; i < children.getLength(); i++) {
		XNode child = node.newXNode(children.item(i));
		// 处理文本节点
		if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE
				|| child.getNode().getNodeType() == Node.TEXT_NODE) {
			String data = child.getStringBody("");
			// 将文本内容封装到SqlNode中
			TextSqlNode textSqlNode = new TextSqlNode(data);
			// ${}是dynamic的
			if (textSqlNode.isDynamic()) {
				contents.add(textSqlNode);
				isDynamic = true;
			} else {
				// 除了${}都是static的,包括下面的动态SQL标签
				contents.add(new StaticTextSqlNode(data));
			}
			
			//处理元素节点
		} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
			String nodeName = child.getNode().getNodeName();
			// 动态SQL标签处理器
			NodeHandler handler = nodeHandlerMap.get(nodeName);
			if (handler == null) {
				throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
			}
			handler.handleNode(child, contents);
			// 动态SQL标签是dynamic的
			isDynamic = true;
		}
	}
	return new MixedSqlNode(contents);
}

The above code logic is relatively simple, after obtaining the sub-element of those tags, and then subjected to traverse, at a convenient time to determine the element node is a text node or, where we can see isDynamic operation is set to true . Finally, get out of here all the data into a collection, and then put this set into MixedSqlNode this object, and then return.

I just said to look, DynamicSqlSource and RawSqlSource class, in fact code for both the class constructor is simple:

public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
	this.configuration = configuration;
	this.rootSqlNode = rootSqlNode;
}

public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
	this(configuration, getSql(configuration, rootSqlNode), parameterType);
}

Hey ...... this RawSqlSource constructor does not seem very simple ah, which called this, but also called getSql method, the way it would look:

public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
	// 解析SQL语句
	SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
	// 获取入参类型
	Class<?> clazz = parameterType == null ? Object.class : parameterType;
	// 开始解析
	sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<String, Object>());
}

private static String getSql(Configuration configuration, SqlNode rootSqlNode) {
	DynamicContext context = new DynamicContext(configuration, null);
	rootSqlNode.apply(context);
	return context.getSql();
}

Seemingly discovered the New World, in the method of this transfer, the first created SqlSourceBuilder object, and then get parameterType, finally resolved, this resolve we wait for the next look, take a look at what has been done in getSql, seemingly did not do anything, DynamicContext is to create an object, and finally to get through dynamic SQL context.

public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
	ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType,
			additionalParameters);
	// 创建分词解析器
	GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
	// 解析#{}
	String sql = parser.parse(originalSql);
	// 将解析之后的SQL信息,封装到StaticSqlSource对象中
	return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}

There seems to have been to code very core, first create a word Handler parameter mapping, and then create a word parser, and finally by the word parser to parse # {} Finally, the SQL information after parsing package to StaticSqlSource object.

Speaking here, some people may think that's why the DynamicSql It's that simple is over, I can only say to not worry, wonderful yet. Here seems to have dynamic SQL resolve seemingly ended, but we have seemingly ignored the ParameterMappingTokenHandler in handleToken (String content) and buildParameterMapping method, because I have not posted here parse method code parser.parse (originalSql), for the way it is in handleToken this method call, and then call buildParameterMapping in handleToken in. This reader can see for themselves.

Third, create an object MappedStatement

Finally, a look at the front did not say, it is to create MappedStatement object. Entrance and createSqlSource method of this method in the same method, we directly look at how to create the following code:

  public MappedStatement addMappedStatement(
      String id,
      SqlSource sqlSource,
      StatementType statementType,
      SqlCommandType sqlCommandType,
      Integer fetchSize,
      Integer timeout,
      String parameterMap,
      Class<?> parameterType,
      String resultMap,
      Class<?> resultType,
      ResultSetType resultSetType,
      boolean flushCache,
      boolean useCache,
      boolean resultOrdered,
      KeyGenerator keyGenerator,
      String keyProperty,
      String keyColumn,
      String databaseId,
      LanguageDriver lang,
      String resultSets) {

    if (unresolvedCacheRef) {
      throw new IncompleteElementException("Cache-ref not yet resolved");
    }

    id = applyCurrentNamespace(id, false);
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

    //利用构建者模式,去创建MappedStatement.Builder,用于创建MappedStatement对象
    MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
        .resource(resource)
        .fetchSize(fetchSize)
        .timeout(timeout)
        .statementType(statementType)
        .keyGenerator(keyGenerator)
        .keyProperty(keyProperty)
        .keyColumn(keyColumn)
        .databaseId(databaseId)
        .lang(lang)
        .resultOrdered(resultOrdered)
        .resultSets(resultSets)
        .resultMaps(getStatementResultMaps(resultMap, resultType, id))
        .resultSetType(resultSetType)
        .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
        .useCache(valueOrDefault(useCache, isSelect))
        .cache(currentCache);

    ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
    if (statementParameterMap != null) {
      statementBuilder.parameterMap(statementParameterMap);
    }

    // 通过MappedStatement.Builder,构建一个MappedStatement
    MappedStatement statement = statementBuilder.build();
    // 将MappedStatement对象存储到Configuration中的Map集合中,key为statement的id,value为MappedStatement对象
    configuration.addMappedStatement(statement);
    return statement;
  }

Looks very long piece of code, but is in fact bluffing, this code is the core point of first use model builders, to create MappedStatement.Builder objects, and finally through MappedStatement.Builder, build a MappedStatement, others are secondary role only.

As for the code MappedStatement.Builder (......) everyone can see for yourself, the code is very simple, for the code statementBuilder.build () is also very simple, here is not posted the code, you can see for yourself interested.

In fact, here it is over this article, the author due to limited experience, may miss a lot of details, you can leave a message to the author, thank you! Then finally to a summary!

to sum up

 

  • Related classes and interfaces:
    • XMLLanguageDriver
    • XMLScriptBuilder
    • SqlSource
    • SqlSourceBuilder
    • music Trade
    • ……

 

  • # CreateSqlSource XMLLanguageDriver : Creating SqlSource
    • XMLScriptBuilder # constructor: initialize dynamic SQL in the set of node processor
    • XMLScriptBuilder#parseScriptNode:
      • # ParseDynamicTags XMLScriptBuilder : parse select \ insert \ update \ delete labels in the SQL statement will eventually resolves to SqlNode package to MixedSqlNode in the List collection
      • DynamicSqlSource # constructor: If SQL contains $ {} and dynamic SQL statements, then SqlNode encapsulated DynamicSqlSource in
      • RawSqlSource # constructor: If SQL contains # {} , then SqlNode encapsulated RawSqlSource in
        • ParameterMappingTokenHandler # constructor
        • GenericTokenParser # constructor: designated to be analyzed openToken and closeToken , and specifies the processor
        • The parse # GenericTokenParser : parsing SQL statements, handling openToken and closeToken in content
          • # HandleToken PieiaruameterMappingTokenHandler : Processing Token ( # {} / $ {} )
            • # BuildParameterMapping ParameterMappingTokenHandler : Creating ParameterMapping objects
        • StaticSqlSource # constructor: after parsing the SQL information encapsulated StaticSqlSource
  • MapperBuilderAssistant # addMappedStatement: Create a MappedStatement objects (next article parsed)
    • MappedStatement.Builder # constructor
    • MappedStatement # build: Create MappedStatement object and is stored in the Configuration object
Published 41 original articles · won praise 8 · views 4252

Guess you like

Origin blog.csdn.net/zfy163520/article/details/103152985