// 动态SQL标签的解析器
public class XMLScriptBuilder extends BaseBuilder {
// 映射语句的根节点信息 <select id="xxx"></select>
public final XNode context;
// 是否是动态的,如果节点包含${}或者动态标签<if>等,表示动态的,否则为非动态
public boolean isDynamic;
// 参数类型
public final Class<?> parameterType;
// 所有的SQL标签处理器
public final Map<String, NodeHandler> nodeHandlerMap = new HashMap<>();
public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) {
super(configuration);
this.context = context;
this.parameterType = parameterType;
this.initNodeHandlerMap();
}
// 初始话不同的动态SQL标签的处理器
public 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());
}
// 解析动态SQL标签
public SqlSource parseScriptNode() {
// 解析所有动态标签节点,返回根节点<select>中的所有子标签节点
SqlNode.MixedSqlNode rootSqlNode = this.parseDynamicTags(context);
SqlSource sqlSource;
// 当SQL中存在${}或者动态标签<if>等标签,表示该SQL是动态SQL,需要在执行的时候进行解析
if (isDynamic) {
// 返回动态的SQL源信息
sqlSource = new SqlSource.DynamicSqlSource(configuration, rootSqlNode);
} else {
// 原始的SQL分类,该SQL表示可以一个直接能使用的SQL或者具有#{}预编译占位符的SQL
sqlSource = new SqlSource.RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
/**
* 解析动态标签
*
* @param node 从映射语句的根标签 <select id="xxx"></select>开始,递归解析子标签,因为标签可以嵌套
* @return 返回一个包含的所有SQL节点的混合节点
*/
protected SqlNode.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));
// 如果当前子节点的是<CDATA>这种类型,或者是文本类型,就可以直接解析,否则的话表示其他SQL标签,那么就需要递归去解析这些嵌套标签
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
// 获取到文本内容
String data = child.getStringBody("");
// 封装成文本的Sql节点
SqlNode.TextSqlNode textSqlNode = new SqlNode.TextSqlNode(data);
// 如果包含${}表示是动态的,否则不是
if (textSqlNode.isDynamic()) {
// 保存该动态的文本节点
contents.add(textSqlNode);
// 标记当前SQL是一个动态的,不能直接使用,还需要解析${}占位符
isDynamic = true;
} else {
// 如果不包含${},表示该节点是一个静态的SQL节点,不需要再次解析了
contents.add(new SqlNode.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);
// 以上这些节点也要标记为动态SQL节点
isDynamic = true;
}
}
// 返回当前正在处理的节点下的所有子节点信息
return new SqlNode.MixedSqlNode(contents);
}
public interface NodeHandler {
void handleNode(XNode nodeToHandle, List<SqlNode> targetContents);
}
// <bind>标签处理器
public static class BindHandler implements NodeHandler {
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
// 获取名称和value属性
final String name = nodeToHandle.getStringAttribute("name");
final String expression = nodeToHandle.getStringAttribute("value");
// 创建一个变量定义的SQL节点,该节点可以定义标签中可以使用的变量
final SqlNode.VarDeclSqlNode node = new SqlNode.VarDeclSqlNode(name, expression);
// 保存当前节点信息
targetContents.add(node);
}
}
// <trim>标签处理器
public class TrimHandler implements NodeHandler {
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
// 解析<trim>内部动态SQL标签
SqlNode.MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
// 获取标签属性
String prefix = nodeToHandle.getStringAttribute("prefix");
String prefixOverrides = nodeToHandle.getStringAttribute("prefixOverrides");
String suffix = nodeToHandle.getStringAttribute("suffix");
String suffixOverrides = nodeToHandle.getStringAttribute("suffixOverrides");
// 封装为TrimSqlNode进行解析
SqlNode.TrimSqlNode trim = new SqlNode.TrimSqlNode(configuration, mixedSqlNode, prefix, prefixOverrides, suffix, suffixOverrides);
// 保存当前节点信息
targetContents.add(trim);
}
}
// <where>标签处理器
public class WhereHandler implements NodeHandler {
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
// 解析<where>内部动态SQL标签
SqlNode.MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
// 封装成WhereSqlNode进行解析
SqlNode.WhereSqlNode where = new SqlNode.WhereSqlNode(configuration, mixedSqlNode);
// 保存当前节点信息
targetContents.add(where);
}
}
// <set>标签处理器
public class SetHandler implements NodeHandler {
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
// 解析<set>内部动态SQL标签
SqlNode.MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
// 封装为SetSqlNode进行解析
SqlNode.SetSqlNode set = new SqlNode.SetSqlNode(configuration, mixedSqlNode);
// 保存当前节点信息
targetContents.add(set);
}
}
// <foreach>标签处理器
public class ForEachHandler implements NodeHandler {
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
// 解析<foreach>内部动态SQL标签
SqlNode.MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
// 获取标签中的属性
String collection = nodeToHandle.getStringAttribute("collection");
Boolean nullable = nodeToHandle.getBooleanAttribute("nullable");
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");
// 封装为ForEachSqlNode进行解析
SqlNode.ForEachSqlNode forEachSqlNode = new SqlNode.ForEachSqlNode(configuration, mixedSqlNode, collection, nullable, index, item, open, close, separator);
// 保存当前节点信息
targetContents.add(forEachSqlNode);
}
}
// <if>标签处理器
public class IfHandler implements NodeHandler {
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
// 解析<if>内部动态SQL标签
SqlNode.MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
// 获取if的表达式
String test = nodeToHandle.getStringAttribute("test");
// 封装为IfSqlNode进行解析
SqlNode.IfSqlNode ifSqlNode = new SqlNode.IfSqlNode(mixedSqlNode, test);
// 保存当前节点信息
targetContents.add(ifSqlNode);
}
}
// <otherwise>标签处理器
public class OtherwiseHandler implements NodeHandler {
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
// 解析<otherwise>内部动态SQL标签
SqlNode.MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
// 直接保存内部的节点,因为otherwise内部保存的就是文本节点,也不存在嵌套关系,无需再次处理
targetContents.add(mixedSqlNode);
}
}
// <choose>标签处理器
public class ChooseHandler implements NodeHandler {
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
// 所有的when子节点
List<SqlNode> whenSqlNodes = new ArrayList<>();
// 所有的otherwise子节点
List<SqlNode> otherwiseSqlNodes = new ArrayList<>();
// 处理when和Otherwise子节点
this.handleWhenOtherwiseNodes(nodeToHandle, whenSqlNodes, otherwiseSqlNodes);
// 获取otherwise,并且校验只能有一个otherwise标签
SqlNode defaultSqlNode = getDefaultSqlNode(otherwiseSqlNodes);
// 封装为ChooseSqlNode进行解析
SqlNode.ChooseSqlNode chooseSqlNode = new SqlNode.ChooseSqlNode(whenSqlNodes, defaultSqlNode);
// 保存当前节点信息
targetContents.add(chooseSqlNode);
}
// 处理when和otherwise子节点
public void handleWhenOtherwiseNodes(XNode chooseSqlNode, List<SqlNode> ifSqlNodes, List<SqlNode> defaultSqlNodes) {
// 遍历choose的子节点
List<XNode> children = chooseSqlNode.getChildren();
for (XNode child : children) {
// 获取节点名称
String nodeName = child.getNode().getNodeName();
// 获取对应节点的处理器
NodeHandler handler = nodeHandlerMap.get(nodeName);
// 如果是when节点(if节点和when节点都是使用的IfHandler)
if (handler instanceof IfHandler) {
((IfHandler) handler).handleNode(child, ifSqlNodes);
}
// 如果是otherwise节点
else if (handler instanceof OtherwiseHandler) {
((OtherwiseHandler) handler).handleNode(child, defaultSqlNodes);
}
}
}
// 获取otherwise,并且校验只能有一个otherwise标签
public SqlNode getDefaultSqlNode(List<SqlNode> defaultSqlNodes) {
SqlNode defaultSqlNode = null;
if (defaultSqlNodes.size() > 1) {
throw new BuilderException("Too many default (otherwise) elements in choose statement.");
}
if (defaultSqlNodes.size() == 1) {
defaultSqlNode = defaultSqlNodes.get(0);
}
return defaultSqlNode
}
}
}
// Mapper.xml语言模板解析器
public interface LanguageDriver {
// 创建参数处理器,因为需要解析模板中的参数
ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);
// 创建SQL的分类,需要解析之后才能判断是动态SQL还是静态SQL
SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType);
// 创建SQL的分类,需要解析之后才能判断是动态SQL还是静态SQL
SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);
// 默认的XML语言的解析器,用于解析SQL模板
public class XMLLanguageDriver implements LanguageDriver {
// 创建参数解析器
@Override
public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
return new ParameterHandler.DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
}
// 创建SQL的分类信息,该方法是通过xml节点来解析
@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
// 使用XML脚本的解析器对该SQL脚本进行解析
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
// 解析<insert,select>中的<if>等等动态标签节点
return builder.parseScriptNode();
}
// 创建SQL的分类信息,该方法是提供脚本字符串来解析
@Override
public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
// 如果是以<script>开头,表示是一个SQL动态脚本
if (script.startsWith("<script>")) {
// 使用XML解析器解析该脚本字符串
XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver());
// 解析成xml节点
XNode script = parser.evalNode("/script");
// 使用XML脚本的解析器对该SQL脚本进行解析
return this.createSqlSource(configuration, script, parameterType);
}
// 不是<script>,就不能写<if>动态标签的SQL
/**
* 如果不是以<script>开头,那么使用${}占位符解析器对该脚本进行解析,解析${}内部的变量值,最终将占位符替换实际的变量值
* 就是从{@link Configuration#variables}属性变量中获取值进行替换,如果开启了org.apache.ibatis.parsing.PropertyParser.enable-default-value=true的全局属性
* 那么,${}具有提供默认值的功能,例如{id:10},从全局变量中获取id的值,如果不存在,使用默认值10
* 默认的分割符为":",如果要修改,可以设置全局属性org.apache.ibatis.parsing.PropertyParser.default-value-separator
*/
script = PropertyParser.parse(script, configuration.variables);
// 将该SQL脚本封装为TextSqlNode文本节点
SqlNode.TextSqlNode textSqlNode = new SqlNode.TextSqlNode(script);
// 是否是动态的SQL,包含${}表示是动态的,否则不是
if (textSqlNode.isDynamic()) {
// 返回动态的数据源,需要在执行的过程中进行解析
return new SqlSource.DynamicSqlSource(configuration, textSqlNode);
}
// 如果不是动态的数据源,那么该SQL就是原始的SQL类型,说白了该SQL可以直接使用
return new SqlSource.RawSqlSource(configuration, script, parameterType);
}
}
}
// 全局属性脚本解析器
public class PropertyParser {
private static final String KEY_PREFIX = "org.apache.ibatis.parsing.PropertyParser.";
public static final String KEY_ENABLE_DEFAULT_VALUE = KEY_PREFIX + "enable-default-value";
public static final String KEY_DEFAULT_VALUE_SEPARATOR = KEY_PREFIX + "default-value-separator";
private static final String ENABLE_DEFAULT_VALUE = "false";
private static final String DEFAULT_VALUE_SEPARATOR = ":";
/**
* 解析脚本
* 就是从{@link Configuration#variables}属性变量中获取值进行替换,如果开启了org.apache.ibatis.parsing.PropertyParser.enable-default-value=true的全局属性
* 那么,${}具有提供默认值的功能,例如{id:10},从全局变量中获取id的值,如果不存在,使用默认值10
* 默认的分割符为":",如果要修改,可以设置全局属性org.apache.ibatis.parsing.PropertyParser.default-value-separator
*
* @param string 给定的SQL脚本
* @param variables config对象中的全局属性
*/
public static String parse(String string, Properties variables) {
VariableTokenHandler handler = new VariableTokenHandler(variables);
GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
return parser.parse(string);
}
// 基于变量的占位符解析器
private static class VariableTokenHandler implements TokenHandler {
/**
* 数据源,该数据源是{@link Configuration#variables},是在mybatis-config中配置的属性信息
*/
private final Properties variables;
// 是否开启默认值
private final boolean enableDefaultValue;
// 默认值的分割符号
private final String defaultValueSeparator;
private VariableTokenHandler(Properties variables) {
this.variables = variables;
// 是否开启默认值
// 配置全局属性org.apache.ibatis.parsing.PropertyParser.enable-default-value的值,默认为false
this.enableDefaultValue = Boolean.parseBoolean(getPropertyValue(KEY_ENABLE_DEFAULT_VALUE, ENABLE_DEFAULT_VALUE));
// 配置全局属性org.apache.ibatis.parsing.PropertyParser.default-value-separator的值,默认为:
this.defaultValueSeparator = getPropertyValue(KEY_DEFAULT_VALUE_SEPARATOR, DEFAULT_VALUE_SEPARATOR);
}
// 从数据源中获取
private String getPropertyValue(String key, String defaultValue) {
return variables == null ? defaultValue : variables.getProperty(key, defaultValue);
}
/**
* 就是从{@link Configuration#variables}属性变量中获取值进行替换,如果开启了org.apache.ibatis.parsing.PropertyParser.enable-default-value=true的全局属性
* 那么,${}具有提供默认值的功能,例如{id:10},从全局变量中获取id的值,如果不存在,使用默认值10
* 默认的分割符为":",如果要修改,可以设置全局属性org.apache.ibatis.parsing.PropertyParser.default-value-separator
*
* @param content 占位符中的内容
*/
@Override
public String handleToken(String content) {
// 如果不存在通用的属性变量,不处理,原样将占位符返回
if (variables != null) {
String key = content;
// 是否开启了默认值,默认为false
if (enableDefaultValue) {
// 在解析出来的变量中查找分割符号,默认为:
final int separatorIndex = content.indexOf(defaultValueSeparator);
String defaultValue = null;
// 如果找到了,并且不是以:开头才行
if (separatorIndex >= 0) {
// ":"前面的作为key
key = content.substring(0, separatorIndex);
// ":"后面的作为默认值
defaultValue = content.substring(separatorIndex + defaultValueSeparator.length());
}
// 如果解析到了默认值
if (defaultValue != null) {
// 从全局变量中获取,如果全局变量中不存在,则返回表达式提供的默认值
return variables.getProperty(key, defaultValue);
}
}
// 如果未开启默认值模式,那么只会从全局变量中找对应的变量
if (variables.containsKey(key)) {
return variables.getProperty(key);
}
}
// 如果上面没有成功解析该变量,则原样返回
return "${" + content + "}";
}
}
}
// SQL的绑定信息
public class BoundSql {
// SQL语句
public final String sql;
// 需要使用的参数映射信息
public final List<ParameterMapping> parameterMappings;
// 参数对象
public final Object parameterObject;
// 额外设置的自定义参数
public final Map<String, Object> additionalParameters;
// 参数集合的元数据信息对象
public final MetaObject metaParameters;
public BoundSql(Configuration configuration, String sql, List<ParameterMapping> parameterMappings, Object parameterObject) {
this.sql = sql;
this.parameterMappings = parameterMappings;
this.parameterObject = parameterObject;
this.additionalParameters = new HashMap<>();
this.metaParameters = configuration.newMetaObject(additionalParameters);
}
// 是否存在自定义参数
public boolean hasAdditionalParameter(String name) {
String paramName = new PropertyTokenizer(name).getName();
return additionalParameters.containsKey(paramName);
}
// 添加自定义参数
public void setAdditionalParameter(String name, Object value) {
metaParameters.setValue(name, value);
}
// 获取自定义参数的值
public Object getAdditionalParameter(String name) {
return metaParameters.getValue(name);
}
// 获取所有的自定义参数
public Map<String, Object> getAdditionalParameters() {
return additionalParameters;
}
}
// SQL分类的构建器,用于解析SQL
public class SqlSourceBuilder extends BaseBuilder {
// 参数属性
public static final String PARAMETER_PROPERTIES = "javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName";
public SqlSourceBuilder(Configuration configuration) {
super(configuration);
}
// 解析SQL,将#{}替换为?
public SqlSource.StaticSqlSource 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 = removeExtraWhitespaces(originalSql);
// 使用#{}类型的占位符解析器解析SQL
sql = parser.parse(sql);
} else {
// 使用#{}类型的占位符解析器解析SQL
sql = parser.parse(originalSql);
}
// 此时SQL已经解析好了,因此返回静态的SQL分类
return new SqlSource.StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
// 删除多余的空格
public static String removeExtraWhitespaces(String original) {
StringTokenizer tokenizer = new StringTokenizer(original);
StringBuilder builder = new StringBuilder();
boolean hasMoreTokens = tokenizer.hasMoreTokens();
while (hasMoreTokens) {
builder.append(tokenizer.nextToken());
hasMoreTokens = tokenizer.hasMoreTokens();
if (hasMoreTokens) {
builder.append(' ');
}
}
return builder.toString();
}
// 参数映射的占位符解析器
public static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {
// 该SQL解析出来的参数的映射信息
// 例如 username=#{username,jdbcType=VARCHAR},就要构建 属性名为username,值为参数对象的username值的映射关系
public final List<ParameterMapping> parameterMappings = new ArrayList<>();
// 参数类型
public final Class<?> parameterType;
// 解析SQL过程中提供的参数
public final MetaObject metaParameters;
public ParameterMappingTokenHandler(Configuration configuration, Class<?> parameterType, Map<String, Object> additionalParameters) {
super(configuration);
this.parameterType = parameterType;
this.metaParameters = configuration.newMetaObject(additionalParameters);
}
public List<ParameterMapping> getParameterMappings() {
return parameterMappings;
}
/**
* 处理占位符
*
* @param content 为占位符内部的内容
* @return 处理占位符之后的结果
*/
@Override
public String handleToken(String content) {
// 自动根据占位符来构建对应的参数映射信息
// 例如 id = #{id},就要构建 属性名为id,值为参数对象的Id值的映射关系
ParameterMapping mapping = this.buildParameterMapping(content);
// 保存该参数的映射信息
parameterMappings.add(mapping);
// 将占位符使用?替代,也就是将#{id} = ? 替代
return "?";
}
// 自动根据占位符来构建对应的参数映射信息
// 例如 id = #{id},就要构建 属性名为id,值为参数对象的Id值的映射关系
public ParameterMapping buildParameterMapping(String content) {
// 解析sql中的#{}内部的属性信息
// 例如 username=#{username,jdbcType=VARCHAR}
// 默认第一个","分隔符之前的会使用固定的key:property,value为username保存
// 之后就使用对应的键值对保存
Map<String, String> propertiesMap = this.parseParameterMapping(content);
// 获取解析到的参数名称
String property = propertiesMap.get("property");
// 要获取该属性的java类型
Class<?> propertyType;
if (metaParameters.hasGetter(property)) {
propertyType = metaParameters.getGetterType(property);
} else if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
propertyType = parameterType;
} else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) {
propertyType = java.sql.ResultSet.class;
} else if (property == null || Map.class.isAssignableFrom(parameterType)) {
propertyType = Object.class;
} else {
MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());
if (metaClass.hasGetter(property)) {
propertyType = metaClass.getGetterType(property);
} else {
propertyType = Object.class;
}
}
// 构建参数映射信息
ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
//
Class<?> javaType = propertyType;
String typeHandlerAlias = null;
// 遍历解析到占位符内的所有键值对
// 例如 username=#{username,jdbcType=VARCHAR}
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)) {
} 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();
}
}
}
// 用于描述SQL语句分类信息
public interface SqlSource {
// 获取绑定的SQL
BoundSql getBoundSql(Object parameterObject);
// 表示当前SQL是一个动态SQL,需要在执行的过程进行解析
public static class DynamicSqlSource implements SqlSource {
// 配置类
public Configuration configuration;
/**
* SQL节点,因为它是动态SQL,只能保存节点信息,在执行的过程中对该节点进行解析
* 实际上是{@link SqlNode.MixedSqlNode}类型,内部包含了该映射语句中的所有动态标签节点
*/
public SqlNode.MixedSqlNode rootSqlNode;
// 在运行的时候进行调用
@Override
public BoundSql getBoundSql(Object parameterObject) {
// 创建动态执行的上下文对象
DynamicContext context = new DynamicContext(configuration, parameterObject);
// 处理动态SQL标签,执行所有动态SQL标签的解析方法,因为这些标签需要根据运行时的参数进行表达式计算,这运行的过程中执行就能成功的完成解析
rootSqlNode.apply(context);
// 创建SQL解析器,用于解析#{}
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
// 获取到方法的参数的java类型
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
// 解析SQL中的#{}占位符替换为?,得到一个静态的Sql分类,表示SQL已经解析好了
SqlSource.StaticSqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
// 将SQL信息封装为BoundSQL对象
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
// 将解析过程中保存到上下文的参数对象都设置到boundSQL中,因为SQL中的占位符需要的参数就可能在这里面,到时候设置参数就可以直接从boundSql的参数对象中获取
context.getBindings().forEach(boundSql::setAdditionalParameter);
return boundSql;
}
}
// 静态的SQL分类,保存的就是可以使用的SQL
public static class StaticSqlSource implements SqlSource {
// SQL语句
public String sql;
// 参数映射信息
public List<ParameterMapping> parameterMappings;
// 配置类
public Configuration configuration;
@Override
public BoundSql getBoundSql(Object parameterObject) {
return new BoundSql(configuration, sql, parameterMappings, parameterObject);
}
}
// 原始的SQL分类,它也是静态SQL分类的一种,该SQL分类表示可以一个直接能使用的SQL或者具有#{}预编译占位符的SQL
public class RawSqlSource implements SqlSource {
/**
* 实际上就是静态SQL分类为{@link SqlSource.StaticSqlSource}
*/
public final SqlSource.StaticSqlSource sqlSource;
/**
* 通过XML节点解析到的SQL类别
*
* @param configuration 配置信息
* @param rootSqlNode <select>中所有子节点,包括<if>等动态标签
* @param parameterType
*/
public RawSqlSource(Configuration configuration, SqlNode.MixedSqlNode rootSqlNode, Class<?> parameterType) {
// 解析动态SQL节点<if>等等
String sql = this.getSql(configuration, rootSqlNode);
this(configuration, sql, parameterType);
}
// 通过字符串脚本解析到的SQL类别
public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
// 创建SQL解析器,用于解析#{}
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> clazz = parameterType == null ? Object.class : parameterType;
// 解析SQL,将#{}替换为?
sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());
}
// 解析动态SQL节点<if>等等
public static String getSql(Configuration configuration, SqlNode rootSqlNode) {
// 创建动态SQL的上下文
DynamicContext context = new DynamicContext(configuration, null);
// 解析动态SQL解析
rootSqlNode.apply(context);
// 获取解析后的SQL
return context.getSql();
}
@Override
public BoundSql getBoundSql(Object parameterObject) {
return sqlSource.getBoundSql(parameterObject);
}
}
// 使用@SqlProvider这种注解提供的SQL分类
public static class ProviderSqlSource implements SqlSource {
// 配置类
public Configuration configuration;
// 提供SQL的类
public Class<?> providerType;
// 语言驱动,解析模板的
public LanguageDriver languageDriver;
// Mapper接口中的方法
public Method mapperMethod;
// providerType中提供生成Sql的方法对象
public Method providerMethod;
// providerMethod方法的参数名称
public String[] providerMethodArgumentNames;
// providerMethod方法的参数类型名称
public Class<?>[] providerMethodParameterTypes;
// 处理的上下文对象
public ProviderContext providerContext;
// 处理的上下文对象的索引
public Integer providerContextIndex;
/**
* 使用@SqlProvider,可以提供type属性,该类用来提供执行的SQL,如果没有设置指定的method属性,则默认生成SQL的方法为provideSql
* 如果存在多个返回值为String,方法名相同的多个方法,则会抛出异常,不会校验参数
*/
public ProviderSqlSource(Configuration configuration, Annotation provider, Class<?> mapperType, Method mapperMethod) {
// 提供SQL的方法名称
String candidateProviderMethodName;
// 提供SQL的方法对象
Method candidateProviderMethod = null;
this.configuration = configuration;
this.mapperMethod = mapperMethod;
// 获取Mapper接口中方法上的@Lang注解
Lang lang = mapperMethod == null ? null : mapperMethod.getAnnotation(Lang.class);
// 使用注解指定的语言模版解析器
this.languageDriver = configuration.getLanguageDriver(lang == null ? null : lang.value());
// 获取sqlProvider注解中的类型
this.providerType = this.getProviderType(configuration, provider, mapperMethod);
// 获取@SqlProvider的method方法的值
candidateProviderMethodName = (String) provider.annotationType().getMethod("method").invoke(provider);
// 如果并没有提供方法,而提供类型是ProviderMethodResolver,该接口能返回一个方法对象
if (candidateProviderMethodName.length() == 0 && ProviderMethodResolver.class.isAssignableFrom(this.providerType)) {
// 调用该ProviderMethodResolver接口实现的resolveMethod方法,得到一个指定的提供sql的方法对象
candidateProviderMethod = ((ProviderMethodResolver) this.providerType.getDeclaredConstructor().newInstance()).resolveMethod(new ProviderContext(mapperType, mapperMethod, configuration.getDatabaseId()));
}
// 如果提供SQL的类型不是ProviderMethodResolver的时候或者ProviderMethodResolver没有提供方法对象
if (candidateProviderMethod == null) {
// 如果没有设置method属性,则默认的方法名为provideSql,如果提供了,则为指定的方法名
candidateProviderMethodName = candidateProviderMethodName.length() == 0 ? "provideSql" : candidateProviderMethodName;
// 在指定提供SQL的类中找到与之匹配的方法名,并且返回值为String的方法对象,
for (Method m : this.providerType.getMethods()) {
if (candidateProviderMethodName.equals(m.getName()) && CharSequence.class.isAssignableFrom(m.getReturnType())) {
// 如果不为空,表示之前找到了与该方法同名的提供SQL的方法,抛出异常
if (candidateProviderMethod != null) {
throw new BuilderException("Error creating SqlSource for SqlProvider. Method '" + candidateProviderMethodName + "' is found multiple in SqlProvider '" + this.providerType.getName() + "'. Sql provider method can not overload.");
}
// 保存该提供SQL的方法
candidateProviderMethod = m;
}
}
}
// 提供SQL的方法对象
this.providerMethod = candidateProviderMethod;
// 找到提供SQL的方法的参数名称,存在@Param注解,使用注解的名称,否则使用方法参数的实际名称
this.providerMethodArgumentNames = new ParamNameResolver(configuration, this.providerMethod).getNames();
// 找到提供SQL的方法的参数的类型
this.providerMethodParameterTypes = this.providerMethod.getParameterTypes();
// 提供SQL的上下文对象
ProviderContext candidateProviderContext = null;
// 提供SQL的上下文对象的参数索引
Integer candidateProviderContextIndex = null;
// 遍历所有的参数
for (int i = 0; i < this.providerMethodParameterTypes.length; i++) {
Class<?> parameterType = this.providerMethodParameterTypes[i];
// 如果提供SQL的方法的参数为ProviderContext
if (parameterType == ProviderContext.class) {
// 校验方法是否存在多个ProviderContext的参数,如果存在多个,抛出异常
if (candidateProviderContext != null) {
throw new BuilderException("Error creating SqlSource for SqlProvider. ProviderContext found multiple in SqlProvider method (" + this.providerType.getName() + "." + providerMethod.getName() + "). ProviderContext can not define multiple in SqlProvider method argument.");
}
// 提供SQL的上下文对象
candidateProviderContext = new ProviderContext(mapperType, mapperMethod, configuration.getDatabaseId());
// 保存提供SQL的上下文对象的参数索引
candidateProviderContextIndex = i;
}
}
// 提供SQL的上下文对象
this.providerContext = candidateProviderContext;
// 提供SQL的上下文对象的参数索引
this.providerContextIndex = candidateProviderContextIndex;
}
// 获取到注解中提供SQL的类
public Class<?> getProviderType(Configuration configuration, Annotation providerAnnotation, Method mapperMethod) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
// 获取注解中的type/value属性
Class<?> type = (Class<?>) providerAnnotation.annotationType().getMethod("type").invoke(providerAnnotation);
Class<?> value = (Class<?>) providerAnnotation.annotationType().getMethod("value").invoke(providerAnnotation);
// 如果是默认的void类型
if (value == void.class && type == void.class) {
// 如果设置了默认的sqlProvider
if (configuration.getDefaultSqlProviderType() != null) {
// 使用默认的sqlProvider
return configuration.getDefaultSqlProviderType();
}
// 如果存在@SqlProvider注解,但是没有设置提供SQL的类,则抛出异常
throw new BuilderException("Please specify either 'value' or 'type' attribute of @" + providerAnnotation.annotationType().getSimpleName() + " at the '" + mapperMethod.toString() + "'.");
}
// 同时设置了value和type,也抛出异常
if (value != void.class && type != void.class && value != type) {
throw new BuilderException("Cannot specify different class on 'value' and 'type' attribute of @" + providerAnnotation.annotationType().getSimpleName() + " at the '" + mapperMethod.toString() + "'.");
}
// 返回注解中的其中一个属性设置的类
return value == void.class ? type : value;
}
}
}
// 动态SQL的上下文
public class DynamicContext {
// 参数对象的KEY
public static final String PARAMETER_OBJECT_KEY = "_parameter";
// 数据库Id的KEY
public static final String DATABASE_ID_KEY = "_databaseId";
static {
// OGNL表达式解析器
OgnlRuntime.setPropertyAccessor(ContextMap.class, new ContextAccessor());
}
// 在整个解析过程中绑定的属性信息
public final ContextMap bindings;
// SQL拼接器
public final StringJoiner sqlBuilder = new StringJoiner(" ");
// 提供一个唯一的数字
public int uniqueNumber;
public DynamicContext(Configuration configuration, Object parameterObject) {
// 如果存在参数,但是参数不是Map
if (parameterObject != null && !(parameterObject instanceof Map)) {
// 将该对象封装成对象的元数据信息
MetaObject metaObject = configuration.newMetaObject(parameterObject);
// 参数对象是否存在类型转换器,如果存在,那么到时候获取参数对象中的某个属性值时候,当那个属性值没有提供get方法的时候,将会直接返回整个参数对象作为指定属性的值
boolean existsTypeHandler = configuration.getTypeHandlerRegistry().hasTypeHandler(parameterObject.getClass());
// 创建在整个解析过程中绑定的属性信息
bindings = new ContextMap(metaObject, existsTypeHandler);
}
// 如果是Map
else {
bindings = new ContextMap(null, false);
}
// 初始化两个默认的属性
// 1. 参数对象在解析的过程中一直存在
bindings.put(PARAMETER_OBJECT_KEY, parameterObject);
// 2. 数据库ID在解析的过程中一直存在
bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId());
}
public Map<String, Object> getBindings() {
return bindings;
}
// 添加绑定字段信息
public void bind(String name, Object value) {
bindings.put(name, value);
}
// 拼接SQL
public void appendSql(String sql) {
sqlBuilder.add(sql);
}
// 获取解析好的SQL语句
public String getSql() {
return sqlBuilder.toString().trim();
}
public int getUniqueNumber() {
return uniqueNumber++;
}
// 在整个解析过程中绑定的属性信息
public static class ContextMap extends HashMap<String, Object> {
// 参数对象
public final MetaObject parameterMetaObject;
// 是否是都兜底的参数对象
public final boolean fallbackParameterObject;
public ContextMap(MetaObject parameterMetaObject, boolean fallbackParameterObject) {
this.parameterMetaObject = parameterMetaObject;
this.fallbackParameterObject = fallbackParameterObject;
}
@Override
public Object get(Object key) {
// 获取绑定的属性值
String strKey = (String) key;
if (super.containsKey(strKey)) {
return super.get(strKey);
}
// 如果不存在参数对象,返回null
if (parameterMetaObject == null) {
return null;
}
// 如果开启了兜底的参数模式,并且该参数没有get方法,此时直接返回整个参数对象,并不会返回指定参数的值
if (fallbackParameterObject && !parameterMetaObject.hasGetter(strKey)) {
return parameterMetaObject.getOriginalObject();
}
// 直接从参数对象中获取属性值
return parameterMetaObject.getValue(strKey);
}
}
}
// 动态SQL标签
public interface SqlNode {
// 处理动态SQL标签
boolean apply(DynamicContext context);
// <if>动态标签
public class IfSqlNode implements SqlNode {
// 表单式计算器
public final ExpressionEvaluator evaluator;
// if表达式
public final String test;
// <if>的子标签,因为可能存在嵌套if,或者SQL语句(也会当做一个SQL节点)
public final SqlNode contents;
@Override
public boolean apply(DynamicContext context) {
// 解析OGNL表达式
// 如果OGNL符合条件
if (evaluator.evaluateBoolean(test, context.getBindings())) {
// 执行该SQL中所有子节点的解析逻辑
contents.apply(context);
// 符合OGNL条件
return true;
}
// 如果OGNL表达式不符合,返回false
return false;
}
}
// <choose>动态标签
public class ChooseSqlNode implements SqlNode {
// otherwise节点信息
private final SqlNode defaultSqlNode;
// when节点信息
private final List<IfSqlNode> ifSqlNodes;
@Override
public boolean apply(DynamicContext context) {
// 执行所有when节点的
for (IfSqlNode sqlNode : ifSqlNodes) {
// 只要有一个when符合条件就返回
if (sqlNode.apply(context)) {
return true;
}
}
// 如果when都没有满足,解析otherwise内部的节点,内部又递归解析子标签
if (defaultSqlNode != null) {
defaultSqlNode.apply(context);
return true;
}
return false;
}
}
// <bind>变量定义的SQL节点
public class VarDeclSqlNode implements SqlNode {
// 变量名称
public final String name;
// 变量表达式
public final String expression;
public VarDeclSqlNode(String name, String exp) {
this.name = name;
this.expression = exp;
}
@Override
public boolean apply(DynamicContext context) {
// 解析OGNL表达式,获取表达式最终的值
final Object value = OgnlCache.getValue(expression, context.getBindings());
// 将该变量和值绑定到动态执行的上下文中,同一个上下文可以共享这个变量
context.bind(name, value);
return true;
}
}
/**
* <pre>
* <trim prefix="WHERE" prefixOverrides="AND | OR ">
* <if test="state != null">
* state = #{state}
* </if>
* <if test="title != null">
* AND title like #{title}
* </if>
* </trim>
* </pre>
*/
// <trim>标签节点
public class TrimSqlNode implements SqlNode {
// 内部的所有子节点
public final SqlNode contents;
// 需要添加的前缀
public final String prefix;
// 需要添加的后缀
public final String suffix;
// 需要删除的前缀
public final List<String> prefixesToOverride;
// 需要删除的后缀
public final List<String> suffixesToOverride;
// 配置信息
public final Configuration configuration;
public TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, String prefixesToOverride, String suffix, String suffixesToOverride) {
this(configuration, contents, prefix, parseOverrides(prefixesToOverride), suffix, parseOverrides(suffixesToOverride));
}
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;
}
// 解析节点
@Override
public boolean apply(DynamicContext context) {
// 创建带有过滤的动态上下文
FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context);
/**
* 执行所有子节点的SQL解析方法
* {@link SqlNode.MixedSqlNode#apply(DynamicContext)}
*/
boolean result = contents.apply(filteredDynamicContext);
// 将解析的SQL进行过滤,过滤多余的前缀或后缀
filteredDynamicContext.applyAll();
return result;
}
// 解析Overrides表达式,例如 <trim prefix="WHERE" prefixOverrides="AND | OR ">,需要保存AND和OR
public static List<String> parseOverrides(String overrides) {
if (overrides != null) {
final StringTokenizer parser = new StringTokenizer(overrides, "|", false);
final List<String> list = new ArrayList<>(parser.countTokens());
while (parser.hasMoreTokens()) {
list.add(parser.nextToken().toUpperCase(Locale.ENGLISH));
}
return list;
}
return Collections.emptyList();
}
// 具有过滤功能的动态SQL上下文
public class FilteredDynamicContext extends DynamicContext {
// 代理的上下文
public final DynamicContext delegate;
// 是否已经添加过前缀
public boolean prefixApplied;
// 是否已经添加过后缀
public boolean suffixApplied;
// SqlNode解析的节点,会被当前上下文拦截,在SqlNode解析的时候,会调用appendSql保存到这里
// 最终的SQL是保存到delegate中,当前上下文拦截之后,就可以对这些SQL进行修改,修改之后再放回到原来的上下文中
public StringBuilder sqlBuffer;
// 执行过滤SQL
public void applyAll() {
// 保存原来的SQL
sqlBuffer = new StringBuilder(sqlBuffer.toString().trim());
// 全部转换为小写
String trimmedUppercaseSql = sqlBuffer.toString().toUpperCase(Locale.ENGLISH);
// 如果存在SQL
if (trimmedUppercaseSql.length() > 0) {
// 添加前缀操作
applyPrefix(sqlBuffer, trimmedUppercaseSql);
// 添加后缀操作
applySuffix(sqlBuffer, trimmedUppercaseSql);
}
// 将过滤好的sql真正的添加到动态上下文中
delegate.appendSql(sqlBuffer.toString());
}
@Override
public void appendSql(String sql) {
sqlBuffer.append(sql);
}
// 执行前缀的添加和删除
public void applyPrefix(StringBuilder sql, String trimmedUppercaseSql) {
// 第一次执行,默认为false
if (!prefixApplied) {
// 标记为true
prefixApplied = true;
// 如果需要删除前缀
if (prefixesToOverride != null) {
// 执行删除前缀操作
for (String toRemove : prefixesToOverride) {
if (trimmedUppercaseSql.startsWith(toRemove)) {
sql.delete(0, toRemove.trim().length());
break;
}
}
}
// 如果需要添加前缀
if (prefix != null) {
// 添加空格,再添加前缀
sql.insert(0, " ");
sql.insert(0, prefix);
}
}
}
// 执行后缀的添加和删除
public void applySuffix(StringBuilder sql, String trimmedUppercaseSql) {
// 第一次执行,默认为false
if (!suffixApplied) {
// 标记为true
suffixApplied = true;
// 如果需要删除后缀
if (suffixesToOverride != null) {
// 执行删除后缀操作
for (String toRemove : suffixesToOverride) {
if (trimmedUppercaseSql.endsWith(toRemove) || trimmedUppercaseSql.endsWith(toRemove.trim())) {
int start = sql.length() - toRemove.trim().length();
int end = sql.length();
sql.delete(start, end);
break;
}
}
}// 如果需要添加后缀
if (suffix != null) {
// 添加空格,再添加后缀
sql.append(" ");
sql.append(suffix);
}
}
}
}
}
// <where>标签节点,就是借助了<trim>完成
public class WhereSqlNode extends TrimSqlNode {
private static final List<String> prefixList = Arrays.asList("AND ", "OR ", "AND\n", "OR\n", "AND\r", "OR\r", "AND\t", "OR\t");
// 会自动添加WHERE前缀和删除AND|OR前缀
public WhereSqlNode(Configuration configuration, SqlNode contents) {
super(configuration, contents, "WHERE", prefixList, null, null);
}
}
// <set>标签节点,就是借助了<trim>完成
public class SetSqlNode extends TrimSqlNode {
private static final List<String> COMMA = Collections.singletonList(",");
// 会自动添加SET前缀和删除","前缀
public SetSqlNode(Configuration configuration, SqlNode contents) {
super(configuration, contents, "SET", COMMA, null, COMMA);
}
}
// <foreach>标签节点
public class ForEachSqlNode implements SqlNode {
public static final String ITEM_PREFIX = "__frch_";
// 表达式计算器
private final ExpressionEvaluator evaluator;
/**
* 例如:
* <pre>
* <select id="findByIds">
* select *
* from users
* where id in
* <foreach collection="ids" item="id" open="(" close=")" separator="," nullable="false">
* #{id}
* </foreach>
* </select>
* </pre>
*/
// 表示变量集合的名称
private final String collectionExpression;
// 表示给定的集合是否可以为空,默认为false,为空抛出异常
private final Boolean nullable;
// <foreach>所有的子节点
private final MixedSqlNode contents;
// 表示循环之前需要拼接的前缀
private final String open;
// 表示循环之后需要拼接的前缀
private final String close;
// 每一个元素之间的分割符号
private final String separator;
// 元素的变量名
private final String item;
// 元素的索引名称
private final String index;
// 配置信息
private final Configuration configuration;
/**
* 解析标签
* <pre>
* <select id="findByIds">
* select *
* from users
* where id in
* <foreach collection="ids" item="id" open="(" close=")" separator=",">
* #{id}
* </foreach>
* </select>
* </pre>
*/
@Override
public boolean apply(DynamicContext context) {
//
/**
* 从上下文中获取已经绑定的参数变量信息
*
* 从{@link DynamicContext#DynamicContext}上下对象中,将参数到bindinds对象中了
* 而对于执行参数是集合的时候,{@link ParamNameResolver#wrapToMapIfCollection}会自动将参数转换为Map进行执行,并且会添加额外的参数
*/
Map<String, Object> bindings = context.getBindings();
// 获取到执行的参数对象
final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings, Optional.ofNullable(nullable).orElseGet(configuration.nullableOnForEach));
// 如果不存在参数或者参数没有数据,不往下处理
if (iterable == null || !iterable.iterator().hasNext()) {
return true;
}
boolean first = true;
// 添加open字符串到SQL中
applyOpen(context);
int i = 0;
// 遍历所有的数据
for (Object o : iterable) {
// 保存原来的上下文
DynamicContext oldContext = context;
// 第一次执行或者没有设置分割符号
if (first || separator == null) {
// 创建一个不需要拼接前缀的上下文对象
context = new PrefixedContext(context, "");
} else {
// 其他情况都需要拼接分割符号
context = new PrefixedContext(context, separator);
}
int uniqueNumber = context.getUniqueNumber();
// 如果集合中的对象是一个map
if (o instanceof Map.Entry) {
Map.Entry<Object, Object> mapEntry = (Map.Entry<Object, Object>) o;
// 绑定index变量
applyIndex(context, mapEntry.getKey(), uniqueNumber);
// 绑定item元素变量
applyItem(context, mapEntry.getValue(), uniqueNumber);
} else {
// 其他对象
// 绑定index变量
applyIndex(context, i, uniqueNumber);
// 绑定item元素变量
applyItem(context, o, uniqueNumber);
}
// 执行所有子节点的SQL解析操作,并且会通过FilteredDynamicContext上下文对象将SQL进行改写
contents.apply(new FilteredDynamicContext(configuration, context, index, item, uniqueNumber));
if (first) {
// 如果没有添加过前缀,first就一直为true
first = !((PrefixedContext) context).isPrefixApplied();
}
// 还原原来的上下文对象
context = oldContext;
i++;
}
// 拼接后缀
applyClose(context);
// 删除绑定的item和index变量值
context.getBindings().remove(item);
context.getBindings().remove(index);
return true;
}
// 绑定index变量,会绑定用户自己提供的index变量和mybatis自己生成的变量
private void applyIndex(DynamicContext context, Object o, int i) {
if (index != null) {
context.bind(index, o);
context.bind(itemizeItem(index, i), o);
}
}
// 绑定item变量,会绑定用户自己提供的item变量和mybatis自己生成的变量
private void applyItem(DynamicContext context, Object o, int i) {
if (item != null) {
context.bind(item, o);
context.bind(itemizeItem(item, i), o);
}
}
private void applyOpen(DynamicContext context) {
if (open != null) {
context.appendSql(open);
}
}
private void applyClose(DynamicContext context) {
if (close != null) {
context.appendSql(close);
}
}
// 将指定变量与元素的索引进行拼接
// #{__frch_id_0}
// #{__frch_id_1}
// #{__frch_id_N}
private static String itemizeItem(String item, int i) {
return ITEM_PREFIX + item + "_" + i;
}
// 具有过滤功能的上下文对象,因为最终foreach需要对标签内的SQL进行改写
private static class FilteredDynamicContext extends DynamicContext {
// 实际保存SQL的上下文对象
private final DynamicContext delegate;
// 解析第几个变量
private final int index;
// 正在解析的标签中索引的名称: idx <foreach index="idx" >
private final String itemIndex;
// 正在解析的变量名: id <foreach index="idx" > #{id} </foreach>
private final String item;
@Override
public Map<String, Object> getBindings() {
return delegate.getBindings();
}
@Override
public void bind(String name, Object value) {
delegate.bind(name, value);
}
@Override
public String getSql() {
return delegate.getSql();
}
// 拦截拼接sql方法,处理之后再将sql保存到实际的上下文对象中
@Override
public void appendSql(String sql) {
// 使用#{}占位符解析器解析,如果解析到#{}内部的变量
GenericTokenParser parser = new GenericTokenParser("#{", "}", content -> {
// content为解析#{}到的内部的变量名
// 将指定变量与元素的索引进行拼接,变成以下格式
// #{__frch_id_0}
// #{__frch_id_1}
// #{__frch_id_N}
String newContent = content.replaceFirst("^\\s*" + item + "(?![^.,:\\s])", itemizeItem(item, index));
if (itemIndex != null && newContent.equals(content)) {
newContent = content.replaceFirst("^\\s*" + itemIndex + "(?![^.,:\\s])", itemizeItem(itemIndex, index));
}
//
return "#{" + newContent + "}";
});
// 使用占位符解析器开始解析
sql = parser.parse(sql);
// 最终解析好的SQL为 select * from users where id in ( #{__frch_id_0},#{__frch_id_1},#{__frch_id_n})
delegate.appendSql(sql);
}
@Override
public int getUniqueNumber() {
return delegate.getUniqueNumber();
}
}
// 用于处理前缀的上下文对象
private class PrefixedContext extends DynamicContext {
// 使用保存SQL的上下文
private final DynamicContext delegate;
// 需要添加的前缀
private final String prefix;
// 是否已经添加过前缀
private boolean prefixApplied;
public PrefixedContext(DynamicContext delegate, String prefix) {
super(configuration, null);
this.delegate = delegate;
this.prefix = prefix;
this.prefixApplied = false;
}
public boolean isPrefixApplied() {
return prefixApplied;
}
@Override
public Map<String, Object> getBindings() {
return delegate.getBindings();
}
@Override
public void bind(String name, Object value) {
delegate.bind(name, value);
}
// 拦截拼接sql方法,处理之后再将sql保存到实际的上下文对象中
@Override
public void appendSql(String sql) {
// 如果未添加过前缀,并且存在SQL
if (!prefixApplied && sql != null && sql.trim().length() > 0) {
// 将该SQL添加前缀
delegate.appendSql(prefix);
// 标记为已经添加过前缀
prefixApplied = true;
}
// 将加工后的SQL保存到原始上下文中
delegate.appendSql(sql);
}
@Override
public String getSql() {
return delegate.getSql();
}
@Override
public int getUniqueNumber() {
return delegate.getUniqueNumber();
}
}
}
// 混合的SQL标签
public class MixedSqlNode implements SqlNode {
// 混合的所有SQL标签
public final List<SqlNode> contents;
public MixedSqlNode(List<SqlNode> contents) {
this.contents = contents;
}
@Override
public boolean apply(DynamicContext context) {
// 执行所有的SQL标签
contents.forEach(node -> node.apply(context));
return true;
}
}
// 静态的SQL节点,该节点保存的SQL可以直接使用
public class StaticTextSqlNode implements SqlNode {
public final String text;
public StaticTextSqlNode(String text) {
this.text = text;
}
// 直接将该SQL进行拼接
@Override
public boolean apply(DynamicContext context) {
context.appendSql(text);
return true;
}
}
// XML中的文本节点,就是在XML中写的一个具体的SQL,没有经过处理的原样SQL
// 例如 select * from users where id = ${id} 或者 select * from users where username=#{username,jdbcType=VARCHAR} and password=#{password}
public class TextSqlNode implements SqlNode {
// SQL语句
public final String text;
// 过滤器
public final Pattern injectionFilter;
// 是否是动态的SQL,包含${}表示是动态的,否则不是
public boolean isDynamic() {
DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
// 创建${}占位符解析器
GenericTokenParser parser = createParser(checker);
// 解析节点
parser.parse(text);
// 如果SQL中包含${},就会调用DynamicCheckerTokenParser处理逻辑,会将动态的标识设置为true,表示是动态的,否则不是
return checker.isDynamic();
}
// 解析当前SQL节点
@Override
public boolean apply(DynamicContext context) {
GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));
context.appendSql(parser.parse(text));
return true;
}
// 普通的占位符解析器,为${}
public GenericTokenParser createParser(TokenHandler handler) {
return new GenericTokenParser("${", "}", handler);
}
// 动态检查的占位符解析器
public static class DynamicCheckerTokenParser implements TokenHandler {
public boolean isDynamic;
public boolean isDynamic() {
return isDynamic;
}
@Override
public String handleToken(String content) {
// 只要该类处理,那么就是动态的
this.isDynamic = true;
return null;
}
}
}
}
Mybatis中的SQL以及动态标签是如何解析的
最新推荐文章于 2024-07-29 09:42:34 发布