mybatis源码学习------动态sql的解析(SqlSource)
SqlSource
SqlSource为SQL 来源接口。它代表从 Mapper XML 或方法注解上,读取的一条 SQL 内容。
SqlSource接口
SqlSource接口的定义如下:
public interface SqlSource {
//根据传入的实际参数,返回一个BoundSql对象
BoundSql getBoundSql(Object parameterObject);
}
SqlSource有多个实现类,类图如下:
实现类的区别
-
DynamicSqlSource 表示带有
${}
占位符的sql语句,如:SELECT * FROM ${tableName} where ID = ?
-
RawSqlSource 表示可能带有
#{}
占位符的sql,如:select * from Blog where id = #{id}
-
StaticSqlSource 表示不带有占位符且可能会包含
?
的sql语句,如:select * from post order by id
-
ProviderSqlSource 表示sql来自基于方法上的
@ProviderXXX
注解所定义的sqlRawSqlSource的执行时机为在mybatis初始化时完成sql的解析,而DynamicSqlSource的执行时机为实际执行sql语句之前
StaticSqlSource
StaticSqlSource表示持有不包含 #{}
和 ${}
占位符的sql
public class StaticSqlSource implements SqlSource {
//持有的sql语句
private final String sql;
//参数映射集合,对应 https://mybatis.org/mybatis-3/zh/sqlmap-xml.html中的 参数
private final List<ParameterMapping> parameterMappings;
private final Configuration configuration;
public StaticSqlSource(Configuration configuration, String sql) {
this(configuration, sql, null);
}
public StaticSqlSource(Configuration configuration, String sql, List<ParameterMapping> parameterMappings) {
this.sql = sql;
this.parameterMappings = parameterMappings;
this.configuration = configuration;
}
@Override
public BoundSql getBoundSql(Object parameterObject) {
//直接创建一个BoundSql实例返回
return new BoundSql(configuration, sql, parameterMappings, parameterObject);
}
}
RawSqlSource
如果当前的sql语句不是动态sql,也就是说sql语句中不包含${}
占位符的话,RawSqlSource会将当前sql中的#{}
占位符替换为?
,并维护其参数映射关系,最终返回一个StaticSqlSource对象。
public class RawSqlSource implements SqlSource {
private final SqlSource sqlSource;
public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
this(configuration, getSql(configuration, rootSqlNode), parameterType);
}
public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
//创建一个SqlSourceBuilder实例,也是建造者
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
//获取形参的类型
Class<?> clazz = parameterType == null ? Object.class : parameterType;
//执行解析,也就是将sql语句中的#{}占位符替换成?
sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());
}
//从根节点开始处理所有的SqlNode
private static String getSql(Configuration configuration, SqlNode rootSqlNode) {
DynamicContext context = new DynamicContext(configuration, null);
rootSqlNode.apply(context);
return context.getSql();
}
@Override
public BoundSql getBoundSql(Object parameterObject) {
return sqlSource.getBoundSql(parameterObject);
}
}
DynamicSqlSource
动态的 SqlSource 实现类
public class DynamicSqlSource implements SqlSource {
//配置对象
private final Configuration configuration;
//SqlNode对象
private final SqlNode rootSqlNode;
public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
this.configuration = configuration;
this.rootSqlNode = rootSqlNode;
}
@Override
public BoundSql getBoundSql(Object parameterObject) {
//创建动态上下文对象
DynamicContext context = new DynamicContext(configuration, parameterObject);
//从根节点开始处理所有的SqlNode,其中在TextSqlNode实例中会将 ${}占位符替换为实际用户传入的值
rootSqlNode.apply(context);
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
//传入context.getBindings()中的实参数据用于生成对应的sql语句
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
//将bindings中的值添加到metaParameters属性中
context.getBindings().forEach(boundSql::setAdditionalParameter);
return boundSql;
}
}
ProviderSqlSource
ProviderSqlSource类型将会在注解配置解析的文章中进行分析
SqlSource的解析
SqlSource的入口在XMLScriptBuilder#parseScriptNode
,如果解析后的SqlNode是动态节点,则创建DynamicSqlSource的实例,如果SqlNode是静态节点则创建RawSqlSource的实例,代码很简单。
public SqlSource parseScriptNode() {
//解析动态标签
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource;
if (isDynamic) {//如果是动态sql,则创建DynamicSqlSource实例
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
SqlSourceBuilder
继承 BaseBuilder 抽象类,SqlSource 构建器,负责将 SQL 语句中的 #{}
替换成相应的 ?
占位符,并获取该 ?
占位符对应的 org.apache.ibatis.mapping.ParameterMapping
对象
parse方法
parse方法为SqlSourceBuilder类的核心方法,该方法可以将sql中的#{}
占位符替换为?
/**
*
* @param originalSql
* @param parameterType 实参的类型
* @param additionalParameters 对应的就是DynamicContext的bindings对应的集合
* @return
*/
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
//创建一个ParameterMapping的处理器,用于处理sql中出现的#{}占位符
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);
}
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
removeExtraWhitespaces
//移除字符串中多余的空格
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();
}
ParameterMappingTokenHandler类
private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {
//参数映射集合
private List<ParameterMapping> parameterMappings = new ArrayList<>();
//参数类型
private Class<?> parameterType;
//附加参数的MetaObject类型
private 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;
}
@Override
public String handleToken(String content) {
parameterMappings.add(buildParameterMapping(content));
return "?";
}
//根据配置的参数映射创建对应的ParameterMapping对象
// SqlSourceBuilder.java
private ParameterMapping buildParameterMapping(String content) {
//解析成 Map 集合
Map<String, String> propertiesMap = parseParameterMapping(content);
// 获得属性的名字和类型
String property = propertiesMap.get("property"); // 名字
Class<?> propertyType; // 类型
if (metaParameters.hasGetter(property)) { // issue #448 get type from additional params
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 对象
ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
// 初始化 ParameterMapping.Builder 对象的属性
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 " + parameterProperties);
}
}
// 如果 typeHandlerAlias 非空,则获得对应的 TypeHandler 对象,并设置到 ParameterMapping.Builder 对象中
if (typeHandlerAlias != null) {
builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));
}
// 创建 ParameterMapping 对象
return builder.build();
}
private Map<String, String> parseParameterMapping(String content) {
try {
return new ParameterExpression(content);
} catch (BuilderException ex) {
throw ex;
} catch (Exception ex) {
throw new BuilderException("Parsing error was found in mapping #{" + content + "}. Check syntax #{property|(expression), var1=value1, var2=value2, ...} ", ex);
}
}
}
BoundSql
可执行的SQL的封装
public class BoundSql {
/**
* SQL 语句
*/
private final String sql;
/**
* ParameterMapping 数组
*/
private final List<ParameterMapping> parameterMappings;
/**
* 参数对象
*/
private final Object parameterObject;
/**
* 附加的参数集合
*/
private final Map<String, Object> additionalParameters;
/**
* {@link #additionalParameters} 的 MetaObject 对象
*/
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);
}
public String getSql() {
return sql;
}
public List<ParameterMapping> getParameterMappings() {
return parameterMappings;
}
public Object getParameterObject() {
return parameterObject;
}
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);
}
}
ParameterMapping
参数映射,对应的配置为:
#{middleInitial, mode=OUT, jdbcType=STRUCT, jdbcTypeName=MY_TYPE, resultMap=departmentResultMap}
代码简单这里只贴出字段的含义
private Configuration configuration;
//属性的名字
private String property;
//参数类型。
private ParameterMode mode;
//参数的Java 类型
private Class<?> javaType = Object.class;
private JdbcType jdbcType;
//对于数值类型,还有一个小数保留位数的设置,来确定小数点后保留的位数
private Integer numericScale;
private TypeHandler<?> typeHandler;
private String resultMapId;
private String jdbcTypeName;
private String expression;