mybatis源码分析(五):SqlSource&SqlSourceBuilder

SqlSource

首先看下SqlSource整体的解析过程,最后产生的可以直接传给sql执行的sql以及ParameterMapping就包含在BoundSql中
mybatis源码分析(五):SqlSource&SqlSourceBuilder_第1张图片

首先看下接口定义

/**
 * Represents the content of a mapped statement read from an XML file or an annotation.
 * It creates the SQL that will be passed to the database out of the input parameter received from the user.
 *
 * @author Clinton Begin
 */
public interface SqlSource {

  BoundSql getBoundSql(Object parameterObject);

}

从注释可以看出,SqlSource代表MappedStatement的内容,可以通过SqlSource来产生数据库可以执行的sql
SqlSource主要有两个实现类
mybatis源码分析(五):SqlSource&SqlSourceBuilder_第2张图片
如果sql语句中包含${},那么会被被识别为动态sql,即DynamicSqlSource
下面看下是如何创建的

创建

在初始化的源码分析中,我们知道是通过LanguageDriver来创建SqlSource的,默认使用的是LanguageDriver的实现类XMLLanguageDriver

@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
  XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
  return builder.parseScriptNode();
}

主要是交给XMLScriptBuilder来进行解析,接着看

public SqlSource parseScriptNode() {
  // 根据解析的标签内容解析成sqlNode,并且判断是否是动态sql
  MixedSqlNode rootSqlNode = parseDynamicTags(context);
  SqlSource sqlSource;
  // 根据是否是动态sql,来决定创建哪种类型的SqlSource
  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<>();
  NodeList children = node.getNode().getChildNodes();
  for (int i = 0; i < children.getLength(); i++) {
    XNode child = node.newXNode(children.item(i));
    // 如果是sql文本,会判断文本中是否出现了${},如果出现了,代表是动态sql,会创建一个TextSqlNode节点
    // 否则是普通的静态sql文本,此时会创建一个StaticTextSqlNode
    if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
      String data = child.getStringBody("");
      TextSqlNode textSqlNode = new TextSqlNode(data);
      if (textSqlNode.isDynamic()) {
        contents.add(textSqlNode);
        isDynamic = true;
      } else {
        contents.add(new StaticTextSqlNode(data));
      }
    } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
      // 如果不是单纯的sql文本,而是 等标签,那么会判断为动态sql,并且创建相应标签的节点
      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);
}

执行

下面看下DynamicSqlSource是如何执行的

@Override
// 这里传入的就是Mapper方法的参数
public BoundSql getBoundSql(Object parameterObject) {
  // 在将SqlNode的时候介绍过
  DynamicContext context = new DynamicContext(configuration, parameterObject);
  // 这里会对所有的sqlNode进行解析,将解析后的sql保存到DynamicContext中
  rootSqlNode.apply(context);
  SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
  Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
  // 使用sqlSourceBuilder来解析SqlNode解析之后的sql
  SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
  BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
  // 将SqlNode解析过程中产生的参数放到boundSql中 
  // 经过上述的一系列处理,BoundSql中具有解析好的sql(所有${}都被直接使用参数值进行替换,所有#{}都使用?进行替换),并且具有#{}用到的所有参数
  context.getBindings().forEach(boundSql::setAdditionalParameter);
  return boundSql;
}

SqlSourceBuilder.parse

/**
 * 主要是将sqlNode解析之后的sql进一步解析,将其中的#{}使用?进行替代,并且建立对应的ParameterMappings
  * @param originalSql sqlNode解析之后的结果
  * @param parameterType 参数类型
  * @param additionalParameters sqlNode解析过程中生成的各种参数,比如_parameter {__frch_item_0}等
  * @return
  */
 public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
   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);
   }
   // 这里可以看到最终都会转换成StaticSqlSource
   return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
 }

当GenericTokenParser遇到#{}时会将对应的内容交给ParameterMappingTokenHandler来处理

@Override
public String handleToken(String content) {
  // 当遇到${param}时,会解析包裹的内容,然后添加一个ParameterMapping
  parameterMappings.add(buildParameterMapping(content));
  // 并且将${param}替换成?
  return "?";
}
private ParameterMapping buildParameterMapping(String content) {
   // 解析${param}中的内容
   // 比如内容是id,javaType=String,jdbcType=varchar
   // 那么解析后的propertiesMap是{jdbcType=varchar, property=id, javaType=String}
   Map<String, String> propertiesMap = parseParameterMapping(content);
   String property = propertiesMap.get("property");
   Class<?> propertyType;
   // 下面确定这个内容中使用的属性的类型
   if (metaParameters.hasGetter(property)) { // issue #448 get type from additional params
     // 这里的metaParameters代表在解析sqlNode过程中,解析出来的参数
     // 比如解析时,会将每次遍历的item添加到这个对象中
     propertyType = metaParameters.getGetterType(property);
   } else if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
     // 如果有对应的typeHandler,那么直接返回参数值类型
     propertyType = parameterType;
   } else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) {
     propertyType = java.sql.ResultSet.class;
   } else if (property == null || Map.class.isAssignableFrom(parameterType)) {
     // 如果没有解析出property属性或者参数类型是Map,那么当前属性的类型就是Object
     propertyType = Object.class;
   } else {
     MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());
     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();
     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 " + PARAMETER_PROPERTIES);
     }
   }
   if (typeHandlerAlias != null) {
     builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));
   }
   return builder.build();
 }

getBoundSql

从之前的源码可以看出,不管是DynamicSqlSource还是其他的子类,在执行getBoundSql时,都会使用SqlSourceBuilder进行解析,解析的结果都是StaticSqlSource下面看下StaticSqlSource的getBoundSql

public BoundSql getBoundSql(Object parameterObject) {
  return new BoundSql(configuration, sql, parameterMappings, parameterObject);
}

你可能感兴趣的:(mybatis,java,mybatis)