欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

mybatis源码学习------动态sql的解析(SqlSource)

程序员文章站 2022-07-12 22:49:07
...

SqlSource

SqlSource为SQL 来源接口。它代表从 Mapper XML 或方法注解上,读取的一条 SQL 内容。

SqlSource接口

SqlSource接口的定义如下:

public interface SqlSource {
  //根据传入的实际参数,返回一个BoundSql对象
  BoundSql getBoundSql(Object parameterObject);
}

SqlSource有多个实现类,类图如下:
mybatis源码学习------动态sql的解析(SqlSource)

实现类的区别

  • DynamicSqlSource 表示带有${}占位符的sql语句,如:

    SELECT * FROM ${tableName} where  ID = ?
    
  • RawSqlSource 表示可能带有#{}占位符的sql,如:

    select * from Blog where id = #{id}
    
  • StaticSqlSource 表示不带有占位符且可能会包含 ? 的sql语句,如:

    select * from post order by id
    
  • ProviderSqlSource 表示sql来自基于方法上的 @ProviderXXX 注解所定义的sql

    RawSqlSource的执行时机为在mybatis初始化时完成sql的解析,而DynamicSqlSource的执行时机为实际执行sql语句之前

StaticSqlSource

StaticSqlSource表示持有不包含 #{}${} 占位符的sql

public class StaticSqlSource implements SqlSource {
  //持有的sql语句
  private final String sql;
  //参数映射集合,对应 https://mybatis.org/mybatis-3/zh/sqlmap-xml.html中的 参数
  private final List<ParameterMapping> parameterMappings;
  private final Configuration configuration;

  public StaticSqlSource(Configuration configuration, String sql) {
    this(configuration, sql, null);
  }

  public StaticSqlSource(Configuration configuration, String sql, List<ParameterMapping> parameterMappings) {
    this.sql = sql;
    this.parameterMappings = parameterMappings;
    this.configuration = configuration;
  }

  @Override
  public BoundSql getBoundSql(Object parameterObject) {
    //直接创建一个BoundSql实例返回
    return new BoundSql(configuration, sql, parameterMappings, parameterObject);
  }
}

RawSqlSource

如果当前的sql语句不是动态sql,也就是说sql语句中不包含${}占位符的话,RawSqlSource会将当前sql中的#{}占位符替换为?,并维护其参数映射关系,最终返回一个StaticSqlSource对象。

public class RawSqlSource implements SqlSource {

  private final SqlSource sqlSource;

  public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
    this(configuration, getSql(configuration, rootSqlNode), parameterType);
  }

  public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
    //创建一个SqlSourceBuilder实例,也是建造者
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    //获取形参的类型
    Class<?> clazz = parameterType == null ? Object.class : parameterType;
    //执行解析,也就是将sql语句中的#{}占位符替换成?
    sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());
  }
  //从根节点开始处理所有的SqlNode
  private static String getSql(Configuration configuration, SqlNode rootSqlNode) {
    DynamicContext context = new DynamicContext(configuration, null);
    rootSqlNode.apply(context);
    return context.getSql();
  }

  @Override
  public BoundSql getBoundSql(Object parameterObject) {
    return sqlSource.getBoundSql(parameterObject);
  }
}

DynamicSqlSource

动态的 SqlSource 实现类

public class DynamicSqlSource implements SqlSource {
  //配置对象
  private final Configuration configuration;
  //SqlNode对象
  private final SqlNode rootSqlNode;

  public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
    this.configuration = configuration;
    this.rootSqlNode = rootSqlNode;
  }

  @Override
  public BoundSql getBoundSql(Object parameterObject) {
    //创建动态上下文对象
    DynamicContext context = new DynamicContext(configuration, parameterObject);
    //从根节点开始处理所有的SqlNode,其中在TextSqlNode实例中会将 ${}占位符替换为实际用户传入的值
    rootSqlNode.apply(context);
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
    //传入context.getBindings()中的实参数据用于生成对应的sql语句
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    //将bindings中的值添加到metaParameters属性中
    context.getBindings().forEach(boundSql::setAdditionalParameter);
    return boundSql;
  }
}

ProviderSqlSource

ProviderSqlSource类型将会在注解配置解析的文章中进行分析

SqlSource的解析

SqlSource的入口在XMLScriptBuilder#parseScriptNode,如果解析后的SqlNode是动态节点,则创建DynamicSqlSource的实例,如果SqlNode是静态节点则创建RawSqlSource的实例,代码很简单。

public SqlSource parseScriptNode() {
  //解析动态标签  
  MixedSqlNode rootSqlNode = parseDynamicTags(context);
  SqlSource sqlSource;
  if (isDynamic) {//如果是动态sql,则创建DynamicSqlSource实例
    sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
  } else {
    sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
  }
  return sqlSource;
}

SqlSourceBuilder

继承 BaseBuilder 抽象类,SqlSource 构建器,负责将 SQL 语句中的 #{} 替换成相应的 ? 占位符,并获取该 ? 占位符对应的 org.apache.ibatis.mapping.ParameterMapping 对象

parse方法

parse方法为SqlSourceBuilder类的核心方法,该方法可以将sql中的#{}占位符替换为?

/**
 *
 * @param originalSql
 * @param parameterType 实参的类型
 * @param additionalParameters 对应的就是DynamicContext的bindings对应的集合
 * @return
 */
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
  //创建一个ParameterMapping的处理器,用于处理sql中出现的#{}占位符
  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);
  }
  return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}

removeExtraWhitespaces

//移除字符串中多余的空格
public static String removeExtraWhitespaces(String original) {
  StringTokenizer tokenizer = new StringTokenizer(original);
  StringBuilder builder = new StringBuilder();
  boolean hasMoreTokens = tokenizer.hasMoreTokens();
  while (hasMoreTokens) {
    builder.append(tokenizer.nextToken());
    hasMoreTokens = tokenizer.hasMoreTokens();
    if (hasMoreTokens) {
      builder.append(' ');
    }
  }
  return builder.toString();
}

ParameterMappingTokenHandler类

private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {
  //参数映射集合
  private List<ParameterMapping> parameterMappings = new ArrayList<>();
  //参数类型
  private Class<?> parameterType;
  //附加参数的MetaObject类型
  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 "?";
  }
  //根据配置的参数映射创建对应的ParameterMapping对象
// SqlSourceBuilder.java

private ParameterMapping buildParameterMapping(String content) {
    //解析成 Map 集合
    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 || Map.class.isAssignableFrom(parameterType)) {
        propertyType = Object.class;
    } else {
        MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());
        if (metaClass.hasGetter(property)) {
            propertyType = metaClass.getGetterType(property);
        } else {
            propertyType = Object.class;
        }
    }
    // 创建 ParameterMapping.Builder 对象
    ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
    //  初始化 ParameterMapping.Builder 对象的属性
    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);
        }
    }
    // 如果 typeHandlerAlias 非空,则获得对应的 TypeHandler 对象,并设置到 ParameterMapping.Builder 对象中
    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);
    }
  }
}

BoundSql

可执行的SQL的封装

public class BoundSql {

    /**
     * SQL 语句
     */
    private final String sql;
    /**
     * ParameterMapping 数组
     */
    private final List<ParameterMapping> parameterMappings;
    /**
     * 参数对象
     */
    private final Object parameterObject;
    /**
     * 附加的参数集合
     */
    private final Map<String, Object> additionalParameters;
    /**
     * {@link #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<>();
        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);
    }

}

ParameterMapping

参数映射,对应的配置为:

#{middleInitial, mode=OUT, jdbcType=STRUCT, jdbcTypeName=MY_TYPE, resultMap=departmentResultMap}

代码简单这里只贴出字段的含义

private Configuration configuration;
//属性的名字
private String property;
//参数类型。
private ParameterMode mode;
//参数的Java 类型
private Class<?> javaType = Object.class;
private JdbcType jdbcType;
//对于数值类型,还有一个小数保留位数的设置,来确定小数点后保留的位数
private Integer numericScale;
private TypeHandler<?> typeHandler;
private String resultMapId;
private String jdbcTypeName;
private String expression;