Mybatis动态解析sql
SqlNode
/**
* SQL节点, 主要与SQL的解析相关
*/
public interface SqlNode {
/**
* 根据用户传入的参数解析记录的动态SQL语句, 并添加到 DynamicContext.sqlBuilder 中进行保存
*/
boolean apply(DynamicContext context);
}
SqlNode接口,可以理解为xml中的每个标签,比如sql的update,trim,if标签。
SqlSource
public interface SqlSource {
// 通过解析得到 BoundSql 对象, BoundSql 中封装了包含 “?”的占位符的SQL, 以及绑定的参数
BoundSql getBoundSql(Object parameterObject);
}
代表从xml文件或注解映射的sql内容,主要就是用于创建BoundSql,有实现类DynamicSqlSource(动态Sql源),StaticSqlSource(静态Sql源)等
封装mybatis最终产生sql的类,包括sql语句,参数,参数源数据等参数:
/**
* {@link SqlSource}处理完动态内容之后, 会生产实际的 SQL 语句 ,
* 该语句中可能有占位符"?" 和 有序的参数映射,以及传入的参数
*/
public class BoundSql {
// 进行 #{ } 和 ${ } 替换完毕之后的结果sql, 注意每个 #{ }替换完之后就是一个 ?
private final String sql;
// 参数映射列表, 与 sql 中的占位符有序, 一一对应
private final List<ParameterMapping> parameterMappings;
// 用户传入的参数
private final Object parameterObject;
private final Map<String, Object> additionalParameters;
private 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);
}
LanguageDriver
public interface LanguageDriver {
/**
* 创建 ParameterHandler
* @see DefaultParameterHandler
*/
ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);
/**
* 创建 SqlSource 对象:从XML中读取 statetment
* @return
*/
SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType);
/**
* 创建 SqlSource 对象:从注释中读取 statetment
* @return
*/
SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);
}
XMLLanguageDriver(处理xml中的sql,RawLanguageDriver处理静态sql):
XMLLanguageDriver内部会使用XMLScriptBuilder解析xml中的sql部分。
解析动态 sql
buildStatementFromContext(context.evalNodes(“select|insert|update|delete”)); 这个方法是上篇文章最终解析sql的入口
/**
* 解析各个 SQL 语句
* @param list
* @param requiredDatabaseId
*/
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
// 遍历 XNode 节点
for (XNode context : list) {
// 建造者模式
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
// 解析
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
// 无法解析的添加到 Configuration 对象
configuration.addIncompleteStatement(statementParser);
}
}
}
public void parseStatementNode() {
// 获取 id 属性
String id = context.getStringAttribute("id");
// 获取 databaseid
String databaseId = context.getStringAttribute("databaseId");
//验证databaseId是否匹配
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
// 获取各个属性
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String resultMap = context.getStringAttribute("resultMap");
String resultType = context.getStringAttribute("resultType");
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
Class<?> resultTypeClass = resolveClass(resultType);
String resultSetType = context.getStringAttribute("resultSetType");
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
// 获取节点的类型
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
// 引入include 解析出的 sql 节点内容
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// Parse selectKey after includes and remove them.
// 处理 selectKey
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
//得到sqlSqlSource就可以得到BoundSql(这个就是最终的sql)
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
String resultSets = context.getStringAttribute("resultSets");
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
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;
}
//最总put到mappedStatements里面
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
//默认使用XMLLanguageDriver创建SqlSource
@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
/**
* 解析动态节点
* @return
*/
public SqlSource parseScriptNode() {
// 首先判断是不是动态节点 如果是动态节点会递归解析
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource = null;
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
/**
* @author Clinton Begin
* 负责动态SQL的解析
*/
public class DynamicSqlSource implements SqlSource {
private final Configuration configuration;
private final SqlNode rootSqlNode;
public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
this.configuration = configuration;
this.rootSqlNode = rootSqlNode;
}
@Override
public BoundSql getBoundSql(Object parameterObject) {
// 获取 DynamicContext 对象, parameterObject为用户传入的参数
// sql语句解析,优先解析${}字符
DynamicContext context = new DynamicContext(configuration, parameterObject);
// 解析 context
rootSqlNode.apply(context);
// 创建 SqlSourceBuilder 对象
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
// 获取参数的类型
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
//创建StaticSqlSource,其也会去解析#{}字符。
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
// 获取 BoundSql 对象
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
// 将 DynamicContext.bindings 中的参数复制到 metaParameters 集合中
for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
}
return boundSql;
}
//RawSqlSource 静态sql的解析 new RawSqlSource(configuration, rootSqlNode, parameterType);
/**
* 构造函数
*/
public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
// 如果没有参数类型, 则将参数类型定义为 Object
Class<?> clazz = parameterType == null ? Object.class : parameterType;
// 返回的是 StaticSqlSource
sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<String, Object>());
}
/**
* SQL的进一步处理
* @param originalSql 经过SqlNode初步处理之后的SQL语句
* @param parameterType 用户传入的参数类型
* @param additionalParameters 记录形参与实参的对应关系
* @return
*/
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
// 创建通用标记处理器, 主要是使用其 handleToken 方法
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
// 创建对应的解析器, 里面调用了处理器
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
// 并将内容替换为 ?
String sql = parser.parse(originalSql);
// 返回一个 StaticSqlSource 对象
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
动态sql和静态sql解析对比
-
DynamicSqlSource解析含有 ${} 的sql语句,而RawSqlSource解析含有 #{} 的sql语句
-
DynamicSqlSource涵盖的操作比RawSqlSource多了一步,便是优先处理*${}*字符,其本身也会调用去解析 #{} 字符
-
*KaTeX parse error: Expected 'EOF', got '#' at position 60: …值,不做字符串引号拼装;而 *#̲{}* 语句的解析是最终转换为…{pwd}
如果pwd参数传入
aaa or 1=1,则上述拼装后的结果为
select * from user where name=‘admin’ and pwd=aaa or 1=1。这个表达式会恒为真,直接会绕过验证,风险贼高。如果上述采用
#{pwd},则传入
aaa or 1=1,则最后的生成语句为
select * from user=‘admin’ and pwd=‘aaa or 1=1’`。这个表达式验证通不过,有较高的安全性,防止sql恶意注入