在上一篇文章当中,SqlSourceBuilder 当中有一步骤是通过 ParameterMappingTokenHandler 和 GenericTokenParser 来替换动态传入的参数,首先来分析 GenericTokenParser 的内容,GenericTokenParser 在判断是否是动态 sql 的时候也已经使用过了:
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) { StringBuilder builder = new StringBuilder(); if (text != null && text.length() > 0) { char[] src = text.toCharArray(); int offset = 0; int start = text.indexOf(openToken, offset); while (start > -1) { if (start > 0 && src[start - 1] == '//') { // the variable is escaped. remove the backslash. builder.append(src, offset, start - offset - 1).append(openToken); offset = start + openToken.length(); } else { int end = text.indexOf(closeToken, start); if (end == -1) { builder.append(src, offset, src.length - offset); offset = src.length; } else { builder.append(src, offset, start - offset); offset = start + openToken.length(); String content = new String(src, offset, end - offset); builder.append(handler.handleToken(content)); offset = end + closeToken.length(); } } start = text.indexOf(openToken, offset); } if (offset < src.length) { builder.append(src, offset, src.length - offset); } } return builder.toString(); } }
有三个参数,开始 token,结束 token,以及一个 TokenHandler,主要 TokenHandler 是 handleToken 来替换匹配的字符串,具体的 parse 方法不详细分析。
一般使用 GenericTokenParser 的方式如下:
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler); String sql = parser.parse(originalSql);
TokenHandler
看下 TokenHandler 接口的声明:
public interface TokenHandler { String handleToken(String content); }
再来看看 ParameterMappingTokenHandler 的实现:
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); } @Override public String handleToken(String content) { parameterMappings.add(buildParameterMapping(content)); return "?"; } private ParameterMapping buildParameterMapping(String content) { //省略代码 } private Map<String, String> parseParameterMapping(String content) { //省略代码 } }
其实在 ParameterMappingTokenHandler 的 handleToken 方法当中,我们只会返回 ?来代替那些参数。虽然返回 ? 字符串很简单,但是我们需要真正传递参数的时候,这个参数的 name 以及 mybatis 的一些其他属性。
//最基本的 #{id} <insert id="insertUser" parameterType="User"> insert into users (id, username, password) values (#{id}, #{username}, #{password}) </insert> #{property,javaType=int,jdbcType=NUMERIC} #{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler} #{height,javaType=double,jdbcType=NUMERIC,numericScale=2} #{department, mode=OUT, jdbcType=CURSOR, javaType=ResultSet, resultMap=departmentResultMap}
因为替换了这些内容,并且在真正执行的时候我们还需要知道,所以通过 ParameterMapping 对象来表示里面的属性值:
public class ParameterMapping { private Configuration configuration; private String property;//传入进来的参数 name private ParameterMode mode; private Class<?> javaType = Object.class; private JdbcType jdbcType; private Integer numericScale; private TypeHandler<?> typeHandler; private String resultMapId; private String jdbcTypeName; private String expression; //省略代码 }
然而我们在 ParameterMappingTokenHandler 当中可以看到有属性定义如下:
private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();
再来看 ParameterMappingTokenHandler.handleToken 方法:
public String handleToken(String content) { parameterMappings.add(buildParameterMapping(content)); return "?"; } private ParameterMapping buildParameterMapping(String content) { Map<String, String> propertiesMap = parseParameterMapping(content); String property = propertiesMap.get("property"); //省略部分代码 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); } }
首先我们来分析如何把: #{property,javaType=int,jdbcType=NUMERIC}
这样的内容转换成一个 map:
public class ParameterExpression extends HashMap<String, String> { private static final long serialVersionUID = -2417552199605158680L; public ParameterExpression(String expression) { parse(expression); } private void parse(String expression) { int p = skipWS(expression, 0); if (expression.charAt(p) == '(') { expression(expression, p + 1); } else { property(expression, p); } } private int skipWS(String expression, int p) { for (int i = p; i < expression.length(); i++) { if (expression.charAt(i) > 0x20) { return i; } } return expression.length(); } private void property(String expression, int left) { if (left < expression.length()) { int right = skipUntil(expression, left, ",:"); put("property", trimmedStr(expression, left, right)); jdbcTypeOpt(expression, right); } } //省略了一些方法,逐步分析 }
首先是 mybatis 自定义了一个 ParameterExpression,并且继承于 HashMap。
在创建 ParameterExpression 实例的时候需要把 expression 传递进来,并且进行解析。
以 id,javaType=int,jdbcType=NUMERIC
传递进来的这个字符串做例子。
在 parse 方法当中第一步是跳过空白符, 执行 skipWS 方法。
然后 if 判断会执行到 property 方法。传递进来的 left 是字符串开始的 index,然后通过 skipUntil 来获取结束 index。
private int skipUntil(String expression, int p, final String endChars) { for (int i = p; i < expression.length(); i++) { char c = expression.charAt(i); if (endChars.indexOf(c) > -1) { return i; } } return expression.length(); }
skipUntil 主要作用就是如果字符串 expression 从 p 索引开始,遇到了 endChars 当中的某个字符,就返回这个字符当前的索引。
然后在 property 方法当中继续执行:
put("property", trimmedStr(expression, left, right));
trimmedStr 就是去掉两边的空字符。所以根据上面传递进来的参数,在 map 当中有 key 是 property,value 是 id,这么一对键值。
然后继续执行 property 方法:
jdbcTypeOpt(expression, right); private void jdbcTypeOpt(String expression, int p) { p = skipWS(expression, p); if (p < expression.length()) { if (expression.charAt(p) == ':') { jdbcType(expression, p + 1); } else if (expression.charAt(p) == ',') { option(expression, p + 1); } else { throw new BuilderException("Parsing error in {" + new String(expression) + "} in position " + p); } } } private void option(String expression, int p) { int left = skipWS(expression, p); if (left < expression.length()) { int right = skipUntil(expression, left, "="); String name = trimmedStr(expression, left, right); left = right + 1; right = skipUntil(expression, left, ","); String value = trimmedStr(expression, left, right); put(name, value); option(expression, right + 1); } }
按照上面代码执行,到 option 方法当中,会递归调用 option 把 A=B 这样的形式以 A 为 key,B 为 value 存入到 map 当中。
继续分析 ParameterMappingTokenHandler.buildParameterMapping 方法:
private ParameterMapping buildParameterMapping(String content) { 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) { MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory()); if (metaClass.hasGetter(property)) { propertyType = metaClass.getGetterType(property); } else { propertyType = Object.class; } } else { propertyType = Object.class; } 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(); 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)); } return builder.build(); }
上面代码可以分为2个部分。
第一部分是获取 propertyType,为了创建 ParameterMapping.Builder 来构建 ParameterMapping 是实例。
第二部分是遍历刚才获取的 map,为 ParameterMapping 设置参数。
第一部分 MetaObject 相关的内容,后续分析。
—EOF—