XMLStatementBuilder:对单个XNode节点进行解析,得到具体的SqlSource并以此生成MappedStatement
parseStatementNode方法:
private final MapperBuilderAssistant builderAssistant; // 记录了当前mapper的namespace等基础信息
private final XNode context; // 对应当前要解析的语句
private final String requiredDatabaseId;
public void parseStatementNode() {
// 1. 通过xnode.getStringAttribute获取id、databaseId、节点名称
String id = this.context.getStringAttribute("id");
String databaseId = this.context.getStringAttribute("databaseId");
if (this.databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
String nodeName = this.context.getNode().getNodeName();
// 2. 将节点名称转换为SqlCommandType类型
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
// 3. 判断是否为SELECT、是否需要使用缓存、是否清空缓存、是否对结果排序
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = this.context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = this.context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = this.context.getBooleanAttribute("resultOrdered", false);
// 4. 构造XMLIncludeTransformer,解析include节点
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(this.configuration, this.builderAssistant); // 获取基础配置信息
includeParser.applyIncludes(this.context.getNode());
// 5. lang属性指定LanguageDriver,解析<selectKey>节点
String parameterType = this.context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = this.resolveClass(parameterType);
String lang = this.context.getStringAttribute("lang");
LanguageDriver langDriver = this.getLanguageDriver(lang);
this.processSelectKeyNodes(id, parameterTypeClass, langDriver);
// 6. 拼接当前mapper的namespace和当前XNode的id作为当前语句的keyStatementId
String keyStatementId = id + "!selectKey";
keyStatementId = this.builderAssistant.applyCurrentNamespace(keyStatementId, true);
Object keyGenerator;
if (this.configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = this.configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = this.context.getBooleanAttribute("useGeneratedKeys", this.configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
// 7. 创建具体SqlSource
SqlSource sqlSource = langDriver.createSqlSource(this.configuration, this.context, parameterTypeClass);
// 8. 构造MappedStatement并插入MapperBuilderAssistant的configuration
StatementType statementType = StatementType.valueOf(this.context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
Integer fetchSize = this.context.getIntAttribute("fetchSize");
Integer timeout = this.context.getIntAttribute("timeout");
String parameterMap = this.context.getStringAttribute("parameterMap");
String resultType = this.context.getStringAttribute("resultType");
Class<?> resultTypeClass = this.resolveClass(resultType);
String resultMap = this.context.getStringAttribute("resultMap");
String resultSetType = this.context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = this.resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = this.configuration.getDefaultResultSetType();
}
String keyProperty = this.context.getStringAttribute("keyProperty");
String keyColumn = this.context.getStringAttribute("keyColumn");
String resultSets = this.context.getStringAttribute("resultSets");
this.builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, (KeyGenerator)keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
}
- 通过xnode.getStringAttribute获取id、databaseId、节点名称
- 将节点名称转换为SqlCommandType类型,SqlCommandType包含update、insert、delete、select、flush和unknown
- 判断是否为SELECT、是否需要使用缓存、是否清空缓存、是否对结果排序
- 构造XMLIncludeTransformer,调用applyIncludes方法解析include节点
public void applyIncludes(Node source) {
Properties variablesContext = new Properties();
Properties configurationVariables = this.configuration.getVariables();
Optional var10000 = Optional.ofNullable(configurationVariables);
Objects.requireNonNull(variablesContext);
var10000.ifPresent(variablesContext::putAll);
// 递归处理子节点,将include节点,替换为sqlFragment节点,将文本TEXT节点中的${}占位符进行替换
this.applyIncludes(source, variablesContext, false);
}
- 通过Xnode的lang属性指定LanguageDriver,并对\<selectKey>节点进行解析
- 拼接当前mapper的namespace和当前XNode的id作为当前语句的keyStatementId
- 创建具体SqlSource:XMLLanguageDriver调用XMLScriptBuilder进行构建
public class XMLLanguageDriver implements LanguageDriver{
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
}
- 构造MappedStatement,插入MapperBuilderAssistant的configuration,configuration的插入会根据key是否可缩写来插入一次或两次:
public V put(String key, V value) {
// 判断是否要抛异常
if (key.contains(".")) {
String shortKey = this.getShortName(key);
if (super.get(shortKey) == null) {
super.put(shortKey, value);
} else {
super.put(shortKey, new Ambiguity(shortKey));
}
}
return super.put(key, value);
}
例如要插入com.xx.xx.xxxMapper.deleteByPrimaryKey,会插入(com.xx.xx.xxxMapper.deleteByPrimaryKey, ms) 和 (deleteByPrimaryKey, ms)
XMLScriptBuilder:继承BaseBuilder,用于创建SqlSource
SqlSource:构造xml表达式对应的具体sql
- Represents the content of a mapped statement read from an XML file or an annotation.
- It creates the SQL that will be passed to the database out of the input parameter received from the user.
SqlSource 接口只有一个方法:根据参数对象获取BoundSql对象
public interface SqlSource {
BoundSql getBoundSql(Object var1);
}
SqlSource有四个实现类:
- DynamicSqlSource: 处理动态sql语句
- ProviderSqlSource: 处理注解形式的sql。
- RawSqlSource:处理静态sql语句,内部调用了StaticSqlSource
- StaticSqlSource:不会对原 SQL 语句进行任何处理
XMLScriptBuilder 内部类:将XNode转为对应的SqlNode
NodeHandler
XMLScriptBuilder定义类内部接口NodeHandler和实现这个接口的内部类xxxHandler
xxxHandler的handleNode方法逻辑:
- 调用parseDynamicTags方法得到MixedSqlNode
//parseDynamicTags判断是否是动态sql
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));
String nodeName;
// 判断是否是普通的文本sql
if (child.getNode().getNodeType() != 4 && child.getNode().getNodeType() != 3) {
if (child.getNode().getNodeType() == 1) {
// 对于标签节点,根据nodeName获取NodeHandler
nodeName = child.getNode().getNodeName();
NodeHandler handler = (NodeHandler)this.nodeHandlerMap.get(nodeName);
if (handler == null) {
throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
// 对当前标签进行处理
handler.handleNode(child, contents);
// 有标签则为动态sql
this.isDynamic = true;
}
} else {
// 对文本节点进行处理
nodeName = child.getStringBody("");
TextSqlNode textSqlNode = new TextSqlNode(nodeName);
// 根据文本中是否有${}判断是否是静态的文本节点
if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
this.isDynamic = true;
} else {
contents.add(new StaticTextSqlNode(nodeName));
}
}
}
}
- 对标签进行处理
- 根据标签和MixedSqlNode来初始化对应的xxxSqlNode
- 将xxxSqlNode当入List列表
以ForEachHandler为例:
private class ForEachHandler implements NodeHandler {
public ForEachHandler() {
}
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
// 1. 调用parseDynamicTags方法得到MixedSqlNode
MixedSqlNode mixedSqlNode = XMLScriptBuilder.this.parseDynamicTags(nodeToHandle);
// 2. 解析foreach的XNode中的标签和属性
String collection = nodeToHandle.getStringAttribute("collection");
String item = nodeToHandle.getStringAttribute("item");
String index = nodeToHandle.getStringAttribute("index");
String open = nodeToHandle.getStringAttribute("open");
String close = nodeToHandle.getStringAttribute("close");
String separator = nodeToHandle.getStringAttribute("separator");
// 3. 根据标签和MixedSqlNode来初始化对应的ForEachSqlNode
ForEachSqlNode forEachSqlNode = new ForEachSqlNode(XMLScriptBuilder.this.configuration, mixedSqlNode, collection, index, item, open, close, separator);
// 4. 将ForEachSqlNode当入List<SqlNode>列表
targetContents.add(forEachSqlNode);
}
}
SqlNode
SqlNode有以下实现:
- 标签节点:
- TrimSqlNode: trim标签,TrimHandler处理
- WhereSqlNode: where 标签,WhereHandler处理
- SetSqlNode:对应 set 标签,SetHandler处理
- ForEachSqlNode :foreach 标签,ForEachHandler处理
- IfSqlNode:对应 if 、when标签,IfHandler处理
- ChooseSqlNode:choose 标签,ChooseHandler处理
- VarDeclSqlNode : bind 标签,BindHandler处理
- 文本节点:
- StaticTextSqlNode:纯文本SQL
- TextSqlNode:包含 ${} 的SQL
XMLScriptBuilder 创建方法:初始化内部NodeHandler类
public class XMLScriptBuilder extends BaseBuilder {
private final XNode context;
private boolean isDynamic;
private final Class<?> parameterType;
private final Map<String, NodeHandler> nodeHandlerMap;
public XMLScriptBuilder(Configuration configuration, XNode context) {
this(configuration, context, (Class)null);
}
public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) {
super(configuration);
this.nodeHandlerMap = new HashMap();
this.context = context;
this.parameterType = parameterType;
this.initNodeHandlerMap();
}
private void initNodeHandlerMap() {
// 初始化XMLScriptBuilder时对NodeHandler的map进行初始化
this.nodeHandlerMap.put("trim", new TrimHandler());
this.nodeHandlerMap.put("where", new WhereHandler());
this.nodeHandlerMap.put("set", new SetHandler());
this.nodeHandlerMap.put("foreach", new ForEachHandler());
this.nodeHandlerMap.put("if", new IfHandler());
this.nodeHandlerMap.put("choose", new ChooseHandler());
this.nodeHandlerMap.put("when", new IfHandler());
this.nodeHandlerMap.put("otherwise", new OtherwiseHandler());
this.nodeHandlerMap.put("bind", new BindHandler());
}
private interface NodeHandler {
void handleNode(XNode var1, List<SqlNode> var2);
}
}
XMLScriptBuilder 解析方法:构造SqlSource
public SqlSource parseScriptNode() {
// 1. 查看是sql是否需要动态生成,对this.isDynamic赋值
MixedSqlNode rootSqlNode = this.parseDynamicTags(this.context);
Object sqlSource;
// 2. 根据是否需要动态生成来初始化不同的SqlSource
if (this.isDynamic) {
// 动态则生成 DynamicSqlSource
sqlSource = new DynamicSqlSource(this.configuration, rootSqlNode);
} else {
// 否则生成RawSqlSource
sqlSource = new RawSqlSource(this.configuration, rootSqlNode, this.parameterType);
}
return (SqlSource)sqlSource;
}