转载

mybatis 源码分析之 TokenHandler 等

在上一篇文章当中,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—

原文  http://renchx.com/mybatis9
正文到此结束
Loading...