首先看下SqlSource整体的解析过程,最后产生的可以直接传给sql执行的sql以及ParameterMapping就包含在BoundSql中
首先看下接口定义
/**
* 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.
*
* @author Clinton Begin
*/
public interface SqlSource {
BoundSql getBoundSql(Object parameterObject);
}
从注释可以看出,SqlSource代表MappedStatement的内容,可以通过SqlSource来产生数据库可以执行的sql
SqlSource主要有两个实现类
如果sql语句中包含${},那么会被被识别为动态sql,即DynamicSqlSource
下面看下是如何创建的
在初始化的源码分析中,我们知道是通过LanguageDriver来创建SqlSource的,默认使用的是LanguageDriver的实现类XMLLanguageDriver
@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
主要是交给XMLScriptBuilder来进行解析,接着看
public SqlSource parseScriptNode() {
// 根据解析的标签内容解析成sqlNode,并且判断是否是动态sql
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource;
// 根据是否是动态sql,来决定创建哪种类型的SqlSource
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
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));
// 如果是sql文本,会判断文本中是否出现了${},如果出现了,代表是动态sql,会创建一个TextSqlNode节点
// 否则是普通的静态sql文本,此时会创建一个StaticTextSqlNode
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
// 如果不是单纯的sql文本,而是 等标签,那么会判断为动态sql,并且创建相应标签的节点
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);
}
下面看下DynamicSqlSource是如何执行的
@Override
// 这里传入的就是Mapper方法的参数
public BoundSql getBoundSql(Object parameterObject) {
// 在将SqlNode的时候介绍过
DynamicContext context = new DynamicContext(configuration, parameterObject);
// 这里会对所有的sqlNode进行解析,将解析后的sql保存到DynamicContext中
rootSqlNode.apply(context);
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
// 使用sqlSourceBuilder来解析SqlNode解析之后的sql
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
// 将SqlNode解析过程中产生的参数放到boundSql中
// 经过上述的一系列处理,BoundSql中具有解析好的sql(所有${}都被直接使用参数值进行替换,所有#{}都使用?进行替换),并且具有#{}用到的所有参数
context.getBindings().forEach(boundSql::setAdditionalParameter);
return boundSql;
}
/**
* 主要是将sqlNode解析之后的sql进一步解析,将其中的#{}使用?进行替代,并且建立对应的ParameterMappings
* @param originalSql sqlNode解析之后的结果
* @param parameterType 参数类型
* @param additionalParameters sqlNode解析过程中生成的各种参数,比如_parameter {__frch_item_0}等
* @return
*/
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;
if (configuration.isShrinkWhitespacesInSql()) {
sql = parser.parse(removeExtraWhitespaces(originalSql));
} else {
sql = parser.parse(originalSql);
}
// 这里可以看到最终都会转换成StaticSqlSource
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
当GenericTokenParser遇到#{}时会将对应的内容交给ParameterMappingTokenHandler来处理
@Override
public String handleToken(String content) {
// 当遇到${param}时,会解析包裹的内容,然后添加一个ParameterMapping
parameterMappings.add(buildParameterMapping(content));
// 并且将${param}替换成?
return "?";
}
private ParameterMapping buildParameterMapping(String content) {
// 解析${param}中的内容
// 比如内容是id,javaType=String,jdbcType=varchar
// 那么解析后的propertiesMap是{jdbcType=varchar, property=id, javaType=String}
Map<String, String> propertiesMap = parseParameterMapping(content);
String property = propertiesMap.get("property");
Class<?> propertyType;
// 下面确定这个内容中使用的属性的类型
if (metaParameters.hasGetter(property)) { // issue #448 get type from additional params
// 这里的metaParameters代表在解析sqlNode过程中,解析出来的参数
// 比如解析时,会将每次遍历的item添加到这个对象中
propertyType = metaParameters.getGetterType(property);
} else if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
// 如果有对应的typeHandler,那么直接返回参数值类型
propertyType = parameterType;
} else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) {
propertyType = java.sql.ResultSet.class;
} else if (property == null || Map.class.isAssignableFrom(parameterType)) {
// 如果没有解析出property属性或者参数类型是Map,那么当前属性的类型就是Object
propertyType = Object.class;
} else {
MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());
if (metaClass.hasGetter(property)) {
propertyType = metaClass.getGetterType(property);
} else {
propertyType = Object.class;
}
}
// 下面使用上面解析的属性来创建一个ParameterMapping
ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
Class<?> javaType = propertyType;
String typeHandlerAlias = null;
for (Map.Entry<String, String> entry : propertiesMap.entrySet()) {
String name = entry.getKey();
String value = entry.getValue();
if ("javaType".equals(name)) {
javaType = resolveClass(value);
builder.javaType(javaType);
} else if ("jdbcType".equals(name)) {
builder.jdbcType(resolveJdbcType(value));
} else if ("mode".equals(name)) {
builder.mode(resolveParameterMode(value));
} else if ("numericScale".equals(name)) {
builder.numericScale(Integer.valueOf(value));
} else if ("resultMap".equals(name)) {
builder.resultMapId(value);
} else if ("typeHandler".equals(name)) {
typeHandlerAlias = value;
} else if ("jdbcTypeName".equals(name)) {
builder.jdbcTypeName(value);
} else if ("property".equals(name)) {
// Do Nothing
} else if ("expression".equals(name)) {
throw new BuilderException("Expression based parameters are not supported yet");
} else {
throw new BuilderException("An invalid property '" + name + "' was found in mapping #{" + content + "}. Valid properties are " + PARAMETER_PROPERTIES);
}
}
if (typeHandlerAlias != null) {
builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));
}
return builder.build();
}
从之前的源码可以看出,不管是DynamicSqlSource还是其他的子类,在执行getBoundSql时,都会使用SqlSourceBuilder进行解析,解析的结果都是StaticSqlSource下面看下StaticSqlSource的getBoundSql
public BoundSql getBoundSql(Object parameterObject) {
return new BoundSql(configuration, sql, parameterMappings, parameterObject);
}