buildStatementFromContext方法的定义如下:其中参数list为select|insert|update|delete对应的类型为XNode的节点集合。
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
// list为mapper.xml文件中SQL操作(select|insert|update|delete)节点列表
for (XNode context : list) {
// 增删查改节点的解析器
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
// 解析mapper.xml文件中的相关SQL操作标签,
// 并存放到configuration的mappedStatements集合中
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
// 如果存在解析异常,则放到incompleteStatements中
configuration.addIncompleteStatement(statementParser);
}
}
}
在内部增删查改节点的解析主要是通过builder包的xml子包的XMLStatementBuilder来完成的,具体在XMLStatementBuilder的parseStatementNode方法中定义解析逻辑:
public void parseStatementNode() {
// mapper接口的方法名
String id = context.getStringAttribute("id");
// fetchSize,parameterMap,parameterType,resultType,resultMap相关属性的解析
...
// flushCache,useCache缓存相关的属性的解析
...
// include节点解析,主要用于include相关的SQL片段,如需要select的列
...
// 解析SQL,使用SqlSource存放
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
// keyGenerator相关
...
// 构造当前增删查改节点对应的MappedStatement对象
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
在parseStatementNode中,在解析好select|insert|update|delete节点相关的属性之后,最后通过builder包的MapperBuilderAssistant来创建对应的MappedStatement对象,然后保存该MappedStatement对象到Configuration的mappedStatements集合中。其中SqlSource是用来保存SQL节点的类,包括动态SQL节点。
public MappedStatement addMappedStatement(...参数列表) {
if (unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
}
id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
// MappedStatement构建器,组装select|insert|update|delete节点的属性值,构造SQL对应的MappedStatement对象
// 然后将该mappedStatement对象实例保存到Configuration的mappedStatements集合
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 statement = statementBuilder.build();
// 添加到configuration的mappedStatements集合中缓存起来
configuration.addMappedStatement(statement);
return statement;
}
Configuration的addMappedStatement方法实现:
public void addMappedStatement(MappedStatement ms) {
// id对应mapper接口的方法名
mappedStatements.put(ms.getId(), ms);
}
由以上分析可知,增删查改select|insert|update|delete对应的每个SQL节点对应一个mapping包的SqlSource类对象实例。SqlSource接口的定义如下:在应用代码调用mapper节点的方法时,在内部通过对应的MappedStatement调用其所关联的SqlSource的getBoundSql方法,获取需要执行的SQL语句。
// 保存在mapper.xml或者使用注解定义的mybatis风格的sql语句
public interface SqlSource {
BoundSql getBoundSql(Object parameterObject);
}
BoundSql的定义如下:其中sql属性就是实际执行的SQL语句。
// SQL语句对象类,对于动态参数已经替换为了SQL标准的问号?,并已经记录好了参数的顺序信息
public class BoundSql {
private final String sql; // SQL语句,带问号?的SQL语句
private final List<ParameterMapping> parameterMappings; // SQL参数名
private final Object parameterObject; // 参数名和参数值映射集合
private final Map<String, Object> additionalParameters;
private final MetaObject metaParameters;
...
}
在XMLStatementBuilder的parseStatementNode方法中,通过scripting包的xmltags子包的LanguageDriver的createSqlSource方法来创建对应的SqlSource对象,对应xml的实现类为XMLLanguageDriver。XMLLanguageDriver的createSqlSource方法实现如下:通过scripting包的xmltags子包的XMLScriptBuilder的parseScriptNode方法来解析该增删查改对应的xml节点。
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
XMLScriptBuilder的构造函数实现如下:
public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) {
super(configuration);
this.context = context;
this.parameterType = parameterType;
// 初始化子节点处理器
initNodeHandlerMap();
}
// 初始化各子节点的处理器,并保存在nodeHandlerMap
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());
}
XMLScriptBuilder的parseScriptNode方法,定义解析增删改查节点,同时判断是否为动态SQL,如果是则使用
DynamicSqlSource来封装,否则使用RawSqlSource。
public SqlSource parseScriptNode() {
// 解析获取一个SqlNode节点结合,通过MixedSqlNode来封装
// 在parseDynamicTags方法中,会根据节点信息,判断是否为动态SQL,为isDynamic设值
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource = null;
if (isDynamic) {
// 动态SQL,即包含if,foreach等节点的
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
// 普通静态SQL,不包含if,foreach等节点的,但可以存在使用#{}设置的动态参数
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
动态SQL对应的DynamicSqlSource的定义如下:由parseScriptNode方法可知,rootSqlNode的类型为MixedSqlNode,MixedSqlNode包含了if,foreach等节点的集合,如if节点对应scripting包的xmltags子包的IfSqlNode。
// 动态SQL,即包含动态参数的SQL
public class DynamicSqlSource implements SqlSource {
private final Configuration configuration;
private final SqlNode rootSqlNode;
...
}
public class MixedSqlNode implements SqlNode {
private final List<SqlNode> contents;
// 包含sql节点集合,如trim,where,if等
public MixedSqlNode(List<SqlNode> contents) {
this.contents = contents;
}
...
}
public class IfSqlNode implements SqlNode {
private final ExpressionEvaluator evaluator;
private final String test;
private final SqlNode contents;
...
}
其中是否为动态SQL的判断:对于纯文本,则根据是否存在使用${}来定义的动态SQL语句参数;对于SQL内部存在xml节点,如if, foreach等,则默认属于动态SQL;对于纯文本中存在#{}定义的字符串参数,不使用动态SQL。具体在parseDynamicTags方法中定义。
protected MixedSqlNode parseDynamicTags(XNode node) {
List<SqlNode> contents = new ArrayList<SqlNode>();
NodeList children = node.getNode().getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
XNode child = node.newXNode(children.item(i));
// 纯文本SQL,则判断是否存在"${"和"}"对,则说明为动态SQL
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
String data = child.getStringBody("");
TextSqlNode textSqlNode = new TextSqlNode(data);
// 如果是纯文本,即select|insert|update|delete节点内部,不存在if, foreach等子节点时,
// 存在"${"和"}"对,则说明为动态SQL
if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
isDynamic = true;
} else {
contents.add(new StaticTextSqlNode(data));
}
// SQL内部包含xml节点,即if, foreach, choose等节点,则是动态SQL
} 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);
// 设置为true
isDynamic = true;
}
}
return new MixedSqlNode(contents);
}
$ 与 # 的区别为:$是将传入的数据直接显示生成sql语句,#是将传入的值当做字符串的形式。如下:
select id,name,age from student where id =#{id}
#:select id,name,age from student where id ='1'
$:select id,name,age from student where id = 1