由于上篇博客,介绍了主配置文件的加载过程,至于mapper
的文件的解析,也是讲了但是不够详细,这篇博客我决定重写梳理一遍,首先我们先看我们运行代码的以及对应的mapper
文件,具体的内容如下:
public class Test {
public static void main(String[] args) throws Exception {
String resource = "mybatis.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//xml解析完成
//其实我们mybatis初始化方法 除了XML意外 其实也可以0xml完成
//new SqlSessionFactoryBuilder().b
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
Configuration configuration = sqlSessionFactory.getConfiguration();
//使用者可以随时使用或者销毁缓存
//默认sqlsession不会自动提交
//从SqlSession对象打开开始 缓存就已经存在
SqlSession sqlSession = sqlSessionFactory.openSession();
sqlSession.close();
}
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<!-- 使用jdbc事务管理 -->
<transactionManager type="JDBC" />
<!-- 数据库连接池 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver" />
<property name="url"
value="jdbc:mysql://localhost:3306/bookstore?characterEncoding=utf-8" />
<property name="username" value="root" />
<property name="password" value="5201314ysX@" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/DemoMapper.xml"></mapper>
</mappers>
</configuration>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.DemoMapper">
<select id="selectAll" parameterType="String" resultType="Map">
select * from test
<where>
1=1
<if test="param2 != null and param2 != ''">
and age = #{param2}
</if>
and id =${param2}
</where>
</select>
</mapper>
public interface DemoMapper {
List<Map<String,Object>> selectAll(String name,String age);
}
上面的mapper
文件基本上涵盖了所有应该有的情况了,废话不多说,我们直接上代码,具体的代码如下:
public class XMLMapperBuilder extends BaseBuilder {
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
//绑定Namespace里面的Class对象
bindMapperForNamespace();
}
//重新解析之前解析不了的节点
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
}
上面的代码走来会判断Set<String> loadedResources
集合中又没有mapper/DemoMapper.xml
这个值,这个是主配置文件中配置,判断这个mapper
文件有没有加载,如果加载了这儿就不会执行if中代码,由于我们这儿是第一次执行所以这个值返回的true
,我们继续看后买呢重要的方法configurationElement(parser.evalNode("/mapper"));
这个方法就是解析DemoMapper.xml
文件,同时将mapper
这个节点传入进去,我们跟进对应代码,具体的代码如下:
public class XMLMapperBuilder extends BaseBuilder {
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"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
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
的属性,这儿的namespace
是com.DemoMapper
,然后设置到builderAssistant
的currentNamespace
属性中去。后面的两个方法都是解析缓存的节点,后面我会着重说明一下。然后解析parameterMap
节点,这个时候这个节点中内容会创建一个ParameterMapping
对象,最终会添加到configuration
中的parameterMaps Map<String, ParameterMap>
变量中去,然后再看解析resultMap
节点,最终会创建一个ResultMap
对象,然后加他加入到configuration
中的resultMaps Map<String, ResultMap>
,然后再解析sql
节点,最终是将这个节点下的内容填充到configuration
的sqlFragments Map<String, XNode>。 这儿上篇博客,已经讲了,但是下面的buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
方法,讲的不够详细,今天着重讲这个方法。
public class XMLMapperBuilder extends BaseBuilder {
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
}
上面的databaseId
是在配置多数据源的时候有用,根据对应的databaseId
调用不同的数据源。这儿只有一个数据源,就直接传null
,默认的就可以了。我们继续跟进 buildStatementFromContext(list, null);
方法,具体的代码如下:
public class XMLMapperBuilder extends BaseBuilder {
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
//解析xml节点
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
//xml语句有问题时 存储到集合中 等解析完能解析的再重新解析
configuration.addIncompleteStatement(statementParser);
}
}
}
这儿做了一个如果解析不了直接将其添加到incompleteStatements
变量中去,这个是一个链表,因为有些东西可能是先使用,后定义的,就会导致报错,这儿直接添加到这个链表中去,然后后面再解析一次,我们继续跟进 statementParser.parseStatementNode();
方法。具体的代码如下:
public class XMLStatementBuilder extends BaseBuilder {
public void parseStatementNode() {
//这儿获取的selectAll
String id = context.getStringAttribute("id");
//空的
String databaseId = context.getStringAttribute("databaseId");
//不会进入此判断
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
//select
String nodeName = context.getNode().getNodeName();
//SELECT
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
//true
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
//是否刷新缓存 默认值:增删改刷新 查询不刷新 false
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
//是否使用二级缓存 默认值:查询使用 增删改不使用 true
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
// 三组数据 分成一个嵌套的查询结果 false
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
//替换Includes标签为对应的sql标签里面的值
includeParser.applyIncludes(context.getNode());
//解析parameterType属性
String parameterType = context.getStringAttribute("parameterType");
//然后从typeAliasRegistry中取,如果没有直接从工作空间加载
Class<?> parameterTypeClass = resolveClass(parameterType);
//解析配置的自定义脚本语言驱动 mybatis plus
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
// Parse selectKey after includes and remove them.
//解析selectKey
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文本来判断是否需要动态解析 如果没有动态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);
}
}
上面的代码代码都是解析对应的属性,然后进行设置,但是最核心的方法还是解析sql
语句,对应的方法是langDriver.createSqlSource(configuration, context, parameterTypeClass);
,具体的代码如下:
public class XMLLanguageDriver implements LanguageDriver {
@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
public SqlSource parseScriptNode() {
//# $
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource;
if (isDynamic) {
//不解析
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
//用占位符方式来解析
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
}
上面的代码会调用parseDynamicTags(context);
方法,也是解析的核心方法,我们跟进去看看,具体的代码如下:
public class XMLScriptBuilder extends BaseBuilder {
protected MixedSqlNode parseDynamicTags(XNode node) {
List<SqlNode> contents = new ArrayList<>();
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("");
TextSqlNode textSqlNode = new TextSqlNode(data);
if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
isDynamic = true;
} else {
contents.add(new StaticTextSqlNode(data));
}
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
String nodeName = child.getNode().getNodeName();
NodeHandler handler = nodeHandlerMap.get(nodeName);
if (handler == null) {
throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
handler.handleNode(child, contents);
isDynamic = true;
}
}
return new MixedSqlNode(contents);
}
}
这时候取出来的节点类型Node.TEXT_NODE
类型然后会取出data
的值,data
的值是select * from test
,然后又是核心的方法,就是调用textSqlNode.isDynamic()
方法,具体的代码如下:
public class TextSqlNode implements SqlNode {
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);
}
public String parse(String text) {
//如果上面传进来的值为空,直接返回
if (text == null || text.isEmpty()) {
return "";
}
// search open token 这个时候由于上面创建的所以这儿是${
int start = text.indexOf(openToken);
//没有匹配到,直接返回
if (start == -1) {
return text;
}
char[] src = text.toCharArray();
int offset = 0;
final StringBuilder builder = new StringBuilder();
StringBuilder expression = null;
//遍历里面所有的#{} select ? ,#{id1} ${}
while (start > -1) {
if (start > 0 && src[start - 1] == '\\') {
// this open token is escaped. remove the backslash and continue.
builder.append(src, offset, start - offset - 1).append(openToken);
offset = start + openToken.length();
} else {
// found open token. let's search close token.
if (expression == null) {
expression = new StringBuilder();
} else {
expression.setLength(0);
}
builder.append(src, offset, start - offset);
offset = start + openToken.length();
int end = text.indexOf(closeToken, offset);
while (end > -1) {
if (end > offset && src[end - 1] == '\\') {
// this close token is escaped. remove the backslash and continue.
expression.append(src, offset, end - offset - 1).append(closeToken);
offset = end + closeToken.length();
end = text.indexOf(closeToken, offset);
} else {
expression.append(src, offset, end - offset);
break;
}
}
if (end == -1) {
// close token was not found.
builder.append(src, start, src.length - start);
offset = src.length;
} else {
builder.append(handler.handleToken(expression.toString()));
offset = end + closeToken.length();
}
}
start = text.indexOf(openToken, offset);
}
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
return builder.toString();
}
}
先是创建了一个GenericTokenParser
对象并指定"${", "}"为匹配到字符,这儿如果没有找到直接返回,很明显这儿没有找到,直接返回text
,这儿就是select * from test
,然后调用checker.isDynamic();
的值,这儿就是false
。然后返回到原来的代码的执行的地方。具体的代码如下:
protected MixedSqlNode parseDynamicTags(XNode node) {
List<SqlNode> contents = new ArrayList<>();
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("");
TextSqlNode textSqlNode = new TextSqlNode(data);
if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
isDynamic = true;
} else {
contents.add(new StaticTextSqlNode(data));
}
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
String nodeName = child.getNode().getNodeName();
NodeHandler handler = nodeHandlerMap.get(nodeName);
if (handler == null) {
throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
handler.handleNode(child, contents);
isDynamic = true;
}
}
return new MixedSqlNode(contents);
}
这儿返回的值是false
,所以会执行else
中代码,创建一个StaticTextSqlNode
对象,然后添加到contents
集合中去。这个时候会遍历第二个标签标签,这个时候上面的那个if
就不会进去了。而是直接会执行else if
中的代码。这个时候获取到nodeName
的值是where
。这个时候会从nodeHandlerMap
中取出where
键对应的值,nodeHandlerMap
中有九个键,分别是otherwise
,foreach
,set
,bind
,trim
,where
,choose
,if
,when
。这个时候取出来的类就是WhereHandler
,然后执行WhereHandler
中的handleNode
的方法,具体的代码如下:
private class WhereHandler implements NodeHandler {
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode);
targetContents.add(where);
}
}
你会发现又执行到原来的代码,这儿是递归调用,做好准备,和我一起绕。具体执行下面的代码,传入的参数为:and age = #{param2}
protected MixedSqlNode parseDynamicTags(XNode node) {
List<SqlNode> contents = new ArrayList<>();
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("");
TextSqlNode textSqlNode = new TextSqlNode(data);
if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
isDynamic = true;
} else {
contents.add(new StaticTextSqlNode(data));
}
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
String nodeName = child.getNode().getNodeName();
NodeHandler handler = nodeHandlerMap.get(nodeName);
if (handler == null) {
throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
handler.handleNode(child, contents);
isDynamic = true;
}
}
return new MixedSqlNode(contents);
}
这个时候由于where
标签中1=1
所以直接将这个值加入contents
,然后继续解析if
标签,这个时候第一个if
判断又是不会进,而是直接进入else if
中的判断,然后取出nodeName
的值为if
,然后取出的handler
的值为IfHandler
,这个时候会调用IfHandler
中的handleNode
方法,具体的代码如下:
private class IfHandler implements NodeHandler {
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
String test = nodeToHandle.getStringAttribute("test");
IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
targetContents.add(ifSqlNode);
}
}
你会发现又进行了一层俄罗斯套娃,这个时候将 and age = #{parame2}
直接添加到contents
中去。这个这个for
到循环已经结束了,这个时候返回MixedSqlNode
对象,只是将contens
的值赋值给MixedSqlNode
,这个时候返回上面的代码的执行的地方,然后获取test
的属性,这儿的test
的值等于param2 != null and param2 != ''
这个时候会创建IfSqlNode
对象,这个对象中有三个值,分别是test
,contents
,evaluator=ExpressionEvaluator
,然后将这个对象添加到上一个调用他的contents
中。这个返回到原来的调用地方,具体的代码如下:
protected MixedSqlNode parseDynamicTags(XNode node) {
List<SqlNode> contents = new ArrayList<>();
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("");
TextSqlNode textSqlNode = new TextSqlNode(data);
if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
isDynamic = true;
} else {
contents.add(new StaticTextSqlNode(data));
}
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
String nodeName = child.getNode().getNodeName();
NodeHandler handler = nodeHandlerMap.get(nodeName);
if (handler == null) {
throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
//从if进去的,这个时候if执行结束,从这返回
handler.handleNode(child, contents);
isDynamic = true;
}
}
return new MixedSqlNode(contents);
}
从if
的标签执行结束返回到,这儿的contents
的值应该是两个,一个是StaticTextSqlNode
的对象,中间的值是1=1
,然后还有一个是IfSqlNode
,就是刚才添加的。这个时候将isDynamic
的值改成true
,这个时候where
标签中的值还没有执行结束,这个时候取出来的值是and id =${param2}
,注意这儿是$符号,所以我们要跟进去看看parse
方法,具体的代码如下:
public String parse(String text) {
//如果上面传进来的值为空,直接返回
if (text == null || text.isEmpty()) {
return "";
}
// search open token 这个时候由于上面创建的所以这儿是${
int start = text.indexOf(openToken);
//没有匹配到,直接返回
if (start == -1) {
return text;
}
char[] src = text.toCharArray();
int offset = 0;
final StringBuilder builder = new StringBuilder();
StringBuilder expression = null;
//遍历里面所有的#{} select ? ,#{id1} ${}
while (start > -1) {
if (start > 0 && src[start - 1] == '\\') {
// this open token is escaped. remove the backslash and continue.
builder.append(src, offset, start - offset - 1).append(openToken);
offset = start + openToken.length();
} else {
// found open token. let's search close token.
if (expression == null) {
expression = new StringBuilder();
} else {
expression.setLength(0);
}
builder.append(src, offset, start - offset);
offset = start + openToken.length();
int end = text.indexOf(closeToken, offset);
while (end > -1) {
if (end > offset && src[end - 1] == '\\') {
// this close token is escaped. remove the backslash and continue.
expression.append(src, offset, end - offset - 1).append(closeToken);
offset = end + closeToken.length();
end = text.indexOf(closeToken, offset);
} else {
expression.append(src, offset, end - offset);
break;
}
}
if (end == -1) {
// close token was not found.
builder.append(src, start, src.length - start);
offset = src.length;
} else {
builder.append(handler.handleToken(expression.toString()));
offset = end + closeToken.length();
}
}
start = text.indexOf(openToken, offset);
}
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
return builder.toString();
}
最终变成了and id =null
,等你发现,你返回的时候,这个返回值没有东西接收,你会发现刚才的修改是无效,有点搞不懂。这个时候这个动态的值,就是true
,因为前面再if
执行的时候,已经替换过了。这个时候会将and id =${param2}
的值添加到contents
中去。然后将isDynamic
的值设置为true
,这个时候这个循环会结束。这个时候会返回MixedSqlNode
对象,MixedSqlNode
中的contents
现在是三个值分别是一个是StaticTextSqlNode
,它的值是1=1
,然后就是IfSqlNode里面存的是test
和contents
,还有一个值是TextSqlNode
存的值是and id =${param2}
。这个时候执行完了where
标签了,然后就是下面的代码
private class WhereHandler implements NodeHandler {
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode);
targetContents.add(where);
}
}
这个时候会执行创建WhereHandler
对象的方法,具体的代码如下:
public class WhereSqlNode extends TrimSqlNode {
private static List<String> prefixList = Arrays.asList("AND ","OR ","AND\n", "OR\n", "AND\r", "OR\r", "AND\t", "OR\t");
public WhereSqlNode(Configuration configuration, SqlNode contents) {
super(configuration, contents, "WHERE", prefixList, null, null);
}
}
然后调用的是父类的方法,具体的代码如下:
protected TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, List<String> prefixesToOverride, String suffix, List<String> suffixesToOverride) {
this.contents = contents;
this.prefix = prefix;
this.prefixesToOverride = prefixesToOverride;
this.suffix = suffix;
this.suffixesToOverride = suffixesToOverride;
this.configuration = configuration;
}
然后将这个where
对象添加到contents
中返回。具体的代码如下:
protected MixedSqlNode parseDynamicTags(XNode node) {
List<SqlNode> contents = new ArrayList<>();
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("");
TextSqlNode textSqlNode = new TextSqlNode(data);
if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
isDynamic = true;
} else {
contents.add(new StaticTextSqlNode(data));
}
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
String nodeName = child.getNode().getNodeName();
NodeHandler handler = nodeHandlerMap.get(nodeName);
if (handler == null) {
throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
//从if进去的,这个时候if执行结束,从这返回
handler.handleNode(child, contents);
isDynamic = true;
}
}
return new MixedSqlNode(contents);
}
注意这儿的contents
中只有两个对象,一个是StaticTextSqlNode
其中的值是 select * from test
,然后就是WhereSqlNode
对象,这个对象中contents
中有三个对象,一个StaticTextSqlNode
,它的值是1=1
,然后就是IfSqlNode
对象,它其中也有一个contents
集合,它的值是and age = #{param2}
,然后就是TextSqlNode
对象,它其中的值是and id =${param2}
,记住只要是动态的标签中都有一个contents
集合。然后将isDynamic
设置为true
,最后再将这个总的contents
包装到MixedSqlNode
对象中去,然后返回,然后执行如下的代码
public class XMLScriptBuilder extends BaseBuilder {
public SqlSource parseScriptNode() {
//# $
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource;
if (isDynamic) {
//不解析
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
//用占位符方式来解析
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
}
这个时候isDynamic
的值为true
,所以会创建DynamicSqlSource
对象,具体的代码如下:
public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
this.configuration = configuration;
this.rootSqlNode = rootSqlNode;
}
可以发现我们就是将这两个属性赋值给DynamicSqlSource
对象。然后返回复制给sqlSource
变量,最后再返回。上面的代码执行是isDynamic
的值为true
的情况,那什么时候isDynamic
的值为false
?为false又会执行什么代码呢?解答第一个问题,我们看了解析的代码当出现$和动态标签时候都会将这个isDynamic
变成true
,其他的情况都是false
。为false的时候执行的代码如下:
public class RawSqlSource implements SqlSource {
public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
this(configuration, getSql(configuration, rootSqlNode), parameterType);
}
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<>());
}
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);
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
}
上面的代码就会将sql
中的#{}
转成?
,同时将返回StaticSqlSource
对象。
最后执行的代码如下:
public class XMLStatementBuilder extends BaseBuilder {
public void parseStatementNode() {
//这儿获取的selectAll
String id = context.getStringAttribute("id");
//空的
String databaseId = context.getStringAttribute("databaseId");
//不会进入此判断
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
//select
String nodeName = context.getNode().getNodeName();
//SELECT
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
//true
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
//是否刷新缓存 默认值:增删改刷新 查询不刷新 false
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
//是否使用二级缓存 默认值:查询使用 增删改不使用 true
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
// 三组数据 分成一个嵌套的查询结果 false
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
//替换Includes标签为对应的sql标签里面的值
includeParser.applyIncludes(context.getNode());
//解析parameterType属性
String parameterType = context.getStringAttribute("parameterType");
//然后从typeAliasRegistry中取,如果没有直接从工作空间加载
Class<?> parameterTypeClass = resolveClass(parameterType);
//解析配置的自定义脚本语言驱动 mybatis plus
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
// Parse selectKey after includes and remove them.
//解析selectKey
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文本来判断是否需要动态解析 如果没有动态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);
}
}
然后将所有的属性读出来,最终创建一个MappedStatement
对象,添加到configuration
的mappedStatements
变量中去,至此整个mapper
文件解析已经讲完了。后面会讲mybatis
的执行流程。