mybatis源码(六)mybatis动态sql的解析过程上篇
mybaits支持动态sql的使用。常见的动态sql标签:<where></where>标签、<if></if>、<choose|when|otherwise>、<foreach>、<trim>、<set>
1.组件介绍
1.1 SqlSource :用于描述MyBatis中的SQL资源信息。是个接口,只有一个方法
public interface SqlSource {
BoundSql getBoundSql(Object parameterObject);
}
由以下几个实现类 这4种SqlSource实现类的作用如下。
● ProviderSqlSource: 用于描述通过@Select、@SelectProvider等注解配置的SQL资源信息。
● DynamicSqlSource: 用于描述Mapper XML文件中配置的SQL资源信息,这些SQL通常包含动态SQL配置或者${}参数占位符,需要在Mapper调用时才能确定具体的SQL语句。
● RawSqlSource: 用于描述Mapper XML文件中配置的SQL资源信息,与DynamicSqlSource不同的是,这些SQL语句在解析XML配置的时候就能确定,即不包含动态SQL相关配置。
● StaticSqlSource: 用于描述ProviderSqlSource、DynamicSqlSource及RawSq|Source解析后得到的静态SQL资源。

配置sql信息的两种方式:1.注解的方式:@SELECT @INSERT @Delete等 2.通过xml配置文件的方式
1.2 BoundSql : BoundSql是对动态SQL解析生成的SQL语句和参数映射信息的封装.是个class类
源码如下:
public class BoundSql {
// Mapper配置解析后的sql语句
private final String sql;
// Mapper参数映射信息
private final List<ParameterMapping> parameterMappings;
// Mapper参数对象
private final Object parameterObject;
// 额外参数信息,包括<bind>标签绑定的参数,内置参数
private final Map<String, Object> 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<String, Object>();
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);
}
}
1.3 LanguageDriver : 用于解析SQL配置,将SQL 配置信息转换为SqlSource对象
LanguageDriver 接口中一共有3个方法,其中createParameterHandler()方法用于创建ParameterHandler对象,另外还有两个重载的createSqlSource()方法,这两个重载的方法用于创建Sq|Source对象。有两个实现类:
XMLanguageDriver : 为XML语言驱动,为MyBatis提供了通过XML标签(我们常用的<if>、<where>等标签)结合OGNL表达式语法实现动态SQL的功能。
a.java注解也可使用动态sql,但是sql语句中要加入<script></script>标签括起来
b.java注解方式演示动态sql代码
@Data public class UserEntity { private Long id; private String name; private Date createTime; private String password; private String phone; private String nickName; } public interface UserMapper { List<UserEntity> getUserByEntity(UserEntity user); List<UserEntity> getUserInfo(UserEntity user); List<UserEntity> getUserByPhones(@Param("phones") List<String> phones); @Select("<script>" + "select * from user " + "<where> " + " <if test="name != null"> " + " AND name = #{name} " + " </if> " + " <if test="phone != null"> " + " AND phone = #{phone} " + " </if> " + "</where>" + "</script>") UserEntity getUserByPhoneAndName(@Param("phone") String phone, @Param("name") String name); List<UserEntity> getUserByNames(@Param("names") List<String> names); UserEntity getUserByName(@Param("userName") String userName); }
RawLanguageDriver : 表示仅支持静态SQL配置,不支持动态SQL功能
c.MyBatis从3.2版本开始支持可插拔脚本语言,这允许我们插入一种脚本语言驱动,并基于这种语言来编写动态SQL语句。例如,我们可以让MyBatis的Mapper配置支持Velocity (或者Freemaker)语法,并基于Velocity (或者Freemaker) 语法编写动态SQL。要实现自定义的脚本语言驱动,只需要实现LanguageDriver接口,创建自定义的SqlSource对象,然后对SqlSource对象进行解析,生成最终的BoundSq|对象即可。首先需要在项目中添加该模块的依赖.
<dependency>
<groupId>org.mybatis.scripting</groupId>
<artifactId>mybatis-velocity</artifactId>
<version>2.0-SNAPSHOT</version>
</dependency>
1.4SqlNode : 用于描述动态SQL中<if>、<where>等标签信息,LanguageDriver解析SQL配置时,会把<if>、<where> 等动态SQL标签转换为SqINode对象,封装在Sq|Source中。
SqlNode是一个接口,只有一个方法,源码如下:
public interface SqlNode {
boolean apply(DynamicContext context);
}
提供了10种动态sql的实现类:
● IfSqINode: 用于描述动态SQL中<if>标签的内容,XMLanguageDriver在解析Mapper SQL配置生成SqlSource时,会对动态SQL中的<if>标签进行解析,将<if>标签转换为lfSqlNode对象。
● ForEachSqlNode: 用于描述动态SQL配置中的<foreach>标签,<foreach>标签配置信息在Mapper解析时会转换为ForEachSqlNode对象。
● ChooseSqlINode: 用于描述动态SQL配置中的<choose>标签内容,Mapper解析时会把<choose>标签配置内容转换为ChooseSqINode对象。
● MixedSqlNode: 用于描述一组SqINode对象, 通常一个Mapper配置是由多个SqlNode对象组成的,这些SqlNode对象通过MixedSqlNode进行关联,组成一个完整的动态SQL配置。
● SetSqlNode: 用于描述动态SQL配置中的<set>标签,Mapper解析时会把<set>标签配置信息转换为SetSqINode对象。
● TrimSq|Node: 用于描述动态SQL中的<trim>标签,动态SQL解析时,会把<trim>标签内容转 换为TrimSqlNode对象。在MyBatis动态SQL使用时,<where>标签和
<set>标签实现的内容都可以使用<trim>标签来完成,因此WhereSqlNode和SetSqlNode类设计为TrimSqlNode类的子类,属于特殊的TrimSqlNode。
● StaticTextSqlNode: 用于描述动态SQL中的静态文本内容。
● TextSqlNode: 该类与StaticTextSqlNode类不同的是,当静态文本中包含${}占位符时,说明${}需要在Mapper调用时将${}替换为具体的参数值。因此,使用TextSqlNode类来描述。
● VarDecISqlNode: 用于描述动态SQL中的<bind>标签,动态SQL解析时,会把<bind>标签配置信息转换为VarDeclSqlNode对象。
● WhereSq|INode: 用于描述动态SQL中的<where>标签,动态SQL解析时,会把<where>标签内容转换为WhereSqINode对象。

用测试代码描述sqlNode的使用过程
@Data public class UserEntity { private Long id; private String name; private Date createTime; private String password; private String phone; private String nickName; } <sql id="userAllField"> id,create_time, name, password, phone, nick_name </sql> <select id="getUserInfo" resultType="com.blog4java.mybatis.example.entity.User"> select <include refid="userAllField"/> from user where 1 = 1 <choose> <when test="id != null"> AND id = #{id} </when> <when test="name != null"> AND name = #{name} </when> <otherwise> AND phone is not null </otherwise> </choose> </select> @Test public void testSqlNode() { // 构建SqlNode SqlNode sn1 = new StaticTextSqlNode("select * from user where 1=1"); SqlNode sn2 = new IfSqlNode(new StaticTextSqlNode(" AND id = #{id}"),"id != null"); SqlNode sn3 = new IfSqlNode(new StaticTextSqlNode(" AND name = #{name}"),"name != null"); SqlNode sn4 = new IfSqlNode(new StaticTextSqlNode(" AND phone = #{phone}"),"phone != null"); SqlNode mixedSqlNode = new MixedSqlNode(Arrays.asList(sn1, sn2, sn3, sn4)); // 创建参数对象 Map<String, Object> paramMap = new HashMap<>(); paramMap.put("id","1"); // 创建动态SQL解析上下文 DynamicContext context = new DynamicContext(sqlSession.getConfiguration(),paramMap); // 调用SqlNode的apply()方法解析动态SQL mixedSqlNode.apply(context); // 调用DynamicContext对象的getSql()方法获取动态SQL解析后的SQL语句 System.out.println(context.getSql()); }
1.5 NodeHandler:这是一个接口,提供了8种实现类。每种处理器用于处理对应的sql的动态标签。例如IfHandler用于处理动态sql配置中的<if>标签。负责将<if>标签内容转换为ifSqlNode对象
八种实现类分别是:BindHandler、TrimHandler、WhereHandler、SetHandler、ForEachHandler、IfHandler、OtherwiseHandler、ChooseHandler
public class XMLScriptBuilder extends BaseBuilder { private final XNode context; private boolean isDynamic; private final Class<?> parameterType; private final Map<String, NodeHandler> nodeHandlerMap = new HashMap<String, NodeHandler>(); public XMLScriptBuilder(Configuration configuration, XNode context) { this(configuration, context, null); } public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) { super(configuration); this.context = context; this.parameterType = parameterType; initNodeHandlerMap(); } 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()); } public SqlSource parseScriptNode() { // 调用parseDynamicTags()方法將SQL配置转换为SqlNode对象 MixedSqlNode rootSqlNode = parseDynamicTags(context); SqlSource sqlSource = null; // 判断Mapper SQL配置中是否包含动态SQL元素,如果是创建DynamicSqlSource对象,否则创建RawSqlSource对象 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<SqlNode>(); NodeList children = node.getNode().getChildNodes(); // 对XML子元素进行遍历 for (int i = 0; i < children.getLength(); i++) { XNode child = node.newXNode(children.item(i)); // 如果子元素为SQL文本内容,则使用TextSqlNode描述该节点 if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) { String data = child.getStringBody(""); TextSqlNode textSqlNode = new TextSqlNode(data); // 判断SQL文本中包含${}参数占位符,则为动态SQL if (textSqlNode.isDynamic()) { contents.add(textSqlNode); isDynamic = true; } else { // 如果SQL文本中不包含${}参数占位符,则不是动态SQL contents.add(new StaticTextSqlNode(data)); } } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // 如果子元素为<if>、<where>等标签,则使用对应的NodeHandler处理 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); } private interface NodeHandler { void handleNode(XNode nodeToHandle, List<SqlNode> targetContents); } private class BindHandler implements NodeHandler { public BindHandler() { // Prevent Synthetic Access } @Override public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) { final String name = nodeToHandle.getStringAttribute("name"); final String expression = nodeToHandle.getStringAttribute("value"); final VarDeclSqlNode node = new VarDeclSqlNode(name, expression); targetContents.add(node); } } private class TrimHandler implements NodeHandler { public TrimHandler() { // Prevent Synthetic Access } @Override public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) { 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 trim = new TrimSqlNode(configuration, mixedSqlNode, prefix, prefixOverrides, suffix, suffixOverrides); targetContents.add(trim); } } private class WhereHandler implements NodeHandler { public WhereHandler() { // Prevent Synthetic Access } @Override public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) { MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle); WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode); targetContents.add(where); } } private class SetHandler implements NodeHandler { public SetHandler() { // Prevent Synthetic Access } @Override public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) { MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle); SetSqlNode set = new SetSqlNode(configuration, mixedSqlNode); targetContents.add(set); } } private class ForEachHandler implements NodeHandler { public ForEachHandler() { // Prevent Synthetic Access } @Override public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) { // 首先调用parseDynamicTags()方法解析<foreach>标签子元素 MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle); String collection = nodeToHandle.getStringAttribute("collection"); 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 forEachSqlNode = new ForEachSqlNode(configuration, mixedSqlNode, collection, index, item, open, close, separator); targetContents.add(forEachSqlNode); } } private class IfHandler implements NodeHandler { public IfHandler() { // Prevent Synthetic Access } @Override public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) { // 继续调用parseDynamicTags()方法解析<if>标签中的子节点 MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle); // 获取<if>标签test属性 String test = nodeToHandle.getStringAttribute("test"); // 创建IfSqlNode对象 IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test); // 將IfSqlNode对象添加到List中 targetContents.add(ifSqlNode); } } private class OtherwiseHandler implements NodeHandler { public OtherwiseHandler() { // Prevent Synthetic Access } @Override public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) { MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle); targetContents.add(mixedSqlNode); } } private class ChooseHandler implements NodeHandler { public ChooseHandler() { // Prevent Synthetic Access } @Override public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) { List<SqlNode> whenSqlNodes = new ArrayList<SqlNode>(); List<SqlNode> otherwiseSqlNodes = new ArrayList<SqlNode>(); handleWhenOtherwiseNodes(nodeToHandle, whenSqlNodes, otherwiseSqlNodes); SqlNode defaultSqlNode = getDefaultSqlNode(otherwiseSqlNodes); ChooseSqlNode chooseSqlNode = new ChooseSqlNode(whenSqlNodes, defaultSqlNode); targetContents.add(chooseSqlNode); } private void handleWhenOtherwiseNodes(XNode chooseSqlNode, List<SqlNode> ifSqlNodes, List<SqlNode> defaultSqlNodes) { List<XNode> children = chooseSqlNode.getChildren(); for (XNode child : children) { String nodeName = child.getNode().getNodeName(); NodeHandler handler = nodeHandlerMap.get(nodeName); if (handler instanceof IfHandler) { handler.handleNode(child, ifSqlNodes); } else if (handler instanceof OtherwiseHandler) { handler.handleNode(child, defaultSqlNodes); } } } private SqlNode getDefaultSqlNode(List<SqlNode> defaultSqlNodes) { SqlNode defaultSqlNode = null; if (defaultSqlNodes.size() == 1) { defaultSqlNode = defaultSqlNodes.get(0); } else if (defaultSqlNodes.size() > 1) { throw new BuilderException("Too many default (otherwise) elements in choose statement."); } return defaultSqlNode; } } }
1.6 GenericTokenParser:Token解析器,用于解析#{}参数,获取#{}参数占位符中内容
例如下面是一个可能的#{}参数占位符配置 :#{userld,javaType=long,jdbcType=NUMERIC,typeHandler=MyTypeHandler} 该参数占位符经过GenericTokenParser解析后,获取参数占位符内容,即 userld,javaType=long,jdbcType=NUMERIC,typeHandler=MyTypeHandler,该内容会经过ParameterMappingTokenHandler对象进行替换处理。
public class GenericTokenParser {
private final String openToken;
private final String closeToken;
private final TokenHandler handler;
public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
this.openToken = openToken;
this.closeToken = closeToken;
this.handler = handler;
}
public String parse(String text) {
if (text == null || text.isEmpty()) {
return "";
}
// 获取第一个openToken在SQL中的位置
int start = text.indexOf(openToken, 0);
// start为-1说明SQL中不存在任何参数占位符
if (start == -1) {
return text;
}
// 將SQL转换为char数组
char[] src = text.toCharArray();
// offset用于记录已解析的#{或者}的偏移量,避免重复解析
int offset = 0;
final StringBuilder builder = new StringBuilder();
// expression为参数占位符中的内容
StringBuilder expression = null;
// 遍历获取所有参数占位符的内容,然后调用TokenHandler的handleToken()方法替换参数占位符
while (start > -1) {
if (start > 0 && src[start - 1] == '\') {
// this open token is escaped. remove the backslash and continue.
builder.append(src, offset, start - offset - 1).append(openToken);
offset = start + openToken.length();
} else {
// found open token. let's search close token.
if (expression == null) {
expression = new StringBuilder();
} else {
expression.setLength(0);
}
builder.append(src, offset, start - offset);
offset = start + openToken.length();
int end = text.indexOf(closeToken, offset);
while (end > -1) {
if (end > offset && src[end - 1] == '\') {
// this close token is escaped. remove the backslash and continue.
expression.append(src, offset, end - offset - 1).append(closeToken);
offset = end + closeToken.length();
end = text.indexOf(closeToken, offset);
} else {
expression.append(src, offset, end - offset);
offset = end + closeToken.length();
break;
}
}
if (end == -1) {
// close token was not found.
builder.append(src, start, src.length - start);
offset = src.length;
} else {
// 调用TokenHandler的handleToken()方法替换参数占位符
builder.append(handler.handleToken(expression.toString()));
offset = end + closeToken.length();
}
}
start = text.indexOf(openToken, offset);
}
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
return builder.toString();
}
}
如上面的代码所示,在GenericTokenParser的parse()方法中, 对SQL配置中的所有#{}参数占位符进行解析,获取参数占位符的内容,然后调用ParameterMappingTokenHandler的handleToken()方法对参
数占位符内容进行替换。参数占位符的替换过程 ->ParameterMappingTokenHandler的handleToken()方法
1.7 ParameterMappingTokenHandler:ParameterMappingTokenHandler为Mybatis参数映射处理器,用于处理SQL中的#{}参数占位符
public class SqlSourceBuilder extends BaseBuilder {
private static final String parameterProperties = "javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName";
public SqlSourceBuilder(Configuration configuration) {
super(configuration);
}
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
// ParameterMappingTokenHandler为Mybatis参数映射处理器,用于处理SQL中的#{}参数占位符
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
// Token解析器,用于解析#{}参数
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
// 调用GenericTokenParser对象的parse()方法將#{}参数占位符转换为?
String sql = parser.parse(originalSql);
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {
private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();
private Class<?> parameterType;
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 "?";
}
private ParameterMapping buildParameterMapping(String content) {
// 將#{}占位符内容转换为Map对象
Map<String, String> propertiesMap = parseParameterMapping(content);
// property 对应的值为参数占位符名称,例如userId
String property = propertiesMap.get("property");
// 推断参数类型
Class<?> propertyType;
// 如果内置参数,或<bind>标签绑定的参数包含该属性,则参数类型为Getter方法返回值类型
if (metaParameters.hasGetter(property)) {
propertyType = metaParameters.getGetterType(property);
// 判读该参数类型是否注册了TypeHandler,如果注册了则使用参数类型
} else if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
propertyType = parameterType;
// 如果指定了jdbcType属性,并且为CURSOR类型,则使用ResultSet类型
} else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) {
propertyType = java.sql.ResultSet.class;
// 如果参数类型为Map接口的子类型,则使用Object类型
} else if (property == null || Map.class.isAssignableFrom(parameterType)) {
propertyType = Object.class;
} else {
// 获取parameterType对应的MetaClass对象,方便获取参数类型的反射信息
MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());
// 如果参数类型中包含该属性,则使用Getter方法返回类型
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();
// 指定ParameterMapping对象的属性
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);
}
}
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);
}
}
}
}
从上面的代码可以看出,SQL配置中的所有#{}参数占位符内容都被替换成了"?" 字符,为什么要替换成一一个"?" 字符呢?
因为MyBatis默认情况下会使用PreparedStatement对象与数据库进行交互,因此#{}参数占位符内容被替换成了问号,然后调用PreparedStatement对象的setXXX()方法为参数占位符设置值。
除此之外,ParameterMappingTokenHandler的handleToken()方法中还做了另一件事情,就是调用buildParameterMapping()方法对占位符内容进行解析,将占位符内容转换为ParameterMapping对象。
ParameterMapping 对象用于描述MyBatis参数映射信息,便于后续根据参数映射信息获取对应的TypeHandler为PreparedStatement对象设置值。
buildParameterMapping()方法解析参数占位符生成ParameterMapping对象的过程也在上述代码中
如上面的代码所示,在ParameterMappingTokenHandler类的buildParameterMapping()方法中首先将参数占位符内容转换为Map对
象,例如参数占位符内容如下:#{userld,javaType=long,jdbcType=NUMERIC,typeHandler=MyTypeHandler}
将会转换成如下Map对象:
Map<String, String> map = new HashMap<>() ;
map .put ("property", "userId") ;
map .put ("javaType", "long") ;
map.put ("jdbcType", "NUMERIC") ;
map.put ("typeHandler", "MyTypeHandler") ;
然后通过一系列的逻辑判断参数的类型(javaType属性值)。最后通过建造者模式构建ParameterMapping对象。