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对象。