Mybatis SqlSource

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: BoundSql 包含了解析之后的 SQL 语句(sql 字段)、每个“#{}”占位符的属性信息(parameterMappings 字段 ,List 类型)、实参信息(parameterObject 字段)以及 DynamicContext 中记录的 KV 信息(additionalParameters 集合,Map<String, Object> 类型)。

网络异常,图片无法展示
|

ublic interface SqlSource {
  BoundSql getBoundSql(Object parameterObject);
}
复制代码

网络异常,图片无法展示
|

BoundSql 包含了解析之后的 SQL 语句(sql 字段)、每个“#{}”占位符的属性信息(parameterMappings 字段 ,List 类型)、实参信息(parameterObject 字段)以及 DynamicContext 中记录的 KV 信息(additionalParameters 集合,Map<String, Object> 类型)。


在 SqlSource 接口中只定义了一个 getBoundSql() 方法,它控制着动态 SQL 语句解析的整个流程,它会根据从 Mapper.xml 映射文件(或注解)解析到的 SQL 语句以及执行 SQL 时传入的实参,返回一条可执行的 SQL。

网络异常,图片无法展示
|

  • DynamicSqlSource:当 SQL 语句中包含动态 SQL 的时候,会使用 DynamicSqlSource 对象。
  • RawSqlSource:当 SQL 语句中只包含静态 SQL 的时候,会使用 RawSqlSource 对象
  • StaticSqlSource:DynamicSqlSource 和 RawSqlSource 经过一系列解析之后,会得到最终可提交到数据库的 SQL 语句,这个时候就可以通过 StaticSqlSource 进行封装了
  • ProviderSqlSource:针对 @*Provider 注解 提供的 SQL


DynamicSqlSource


public class DynamicSqlSource implements SqlSource {
  private final Configuration configuration;
  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);
    // 动态 SQL 的解释
    rootSqlNode.apply(context);
    // 解释 #{} 将其替换为 ? 
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    context.getBindings().forEach(boundSql::setAdditionalParameter);
    return boundSql;
  }
}
复制代码

主要负责解析动态 SQL 语句

DynamicSqlSource 中维护了一个 SqlNode 类型的字段(rootSqlNode 字段),用于记录整个 SqlNode 树形结构的根节点。在 DynamicSqlSource 的 getBoundSql() 方法实现中,会使用前面介绍的 SqlNode、SqlSourceBuilder 等组件,完成动态 SQL 语句以及“#{}”占位符的解析


RawSqlSource


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 sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> clazz = parameterType == null ? Object.class : parameterType;
    sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());
  }
  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);
  }
}
复制代码

RawSqlSource 处理的是非动态 SQL 语句,DynamicSqlSource 处理的是动态 SQL 语句;


RawSqlSource 解析 SQL 语句的时机是在初始化流程中,而 DynamicSqlSource 解析动态 SQL 的时机是在程序运行过程中,也就是运行时解析

初始化的时候

// XMLLanguageDriver
@Override
public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
  // issue #3
  if (script.startsWith("<script>")) {
    XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver());
    return createSqlSource(configuration, parser.evalNode("/script"), parameterType);
  } else {
    // issue #127
    script = PropertyParser.parse(script, configuration.getVariables());
    TextSqlNode textSqlNode = new TextSqlNode(script);
    if (textSqlNode.isDynamic()) {
      return new DynamicSqlSource(configuration, textSqlNode);
    } else {
      return new RawSqlSource(configuration, script, parameterType);
    }
  }
}
复制代码

private final SqlSource sqlSource 存放的是 StaticSqlSource


StaticSqlSource


StaticSqlSource 中维护了解析之后的 SQL 语句以及“#{}”占位符的属性信息(List 集合),其 getBoundSql() 方法是真正创建 BoundSql 对象的地方,这个 BoundSql 对象包含了上述 StaticSqlSource 的两个字段以及实参的信息。

仅包含有 ?占位符的 SQL

public class StaticSqlSource implements SqlSource {
  private final String sql;
  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) {
    return new BoundSql(configuration, sql, parameterMappings, parameterObject);
  }
}
复制代码


ProviderSqlSource


网络异常,图片无法展示
|


这种方式使用得比较少、copy 一个网上使用的例子

public class ProviderSqlSourceDemo {
  public static void main(String[] args) throws NoSuchMethodException {
      Configuration configuration = new Configuration();
      SelectProvider provider = UserMapper.class.getMethod("select", String.class).getAnnotation(SelectProvider.class);
      SqlSource providerSqlSource = new ProviderSqlSource(configuration, provider, null, null);
      System.out.println(providerSqlSource.getBoundSql("wenhai").getSql());
  }
  public String getSql() {
      return "SELECT * FROM user WHERE name = #{name}";
  }
  interface UserMapper {
      @SelectProvider(type = ProviderSqlSourceDemo.class, method = "getSql")
      List<User> select(String name);
  }
}
复制代码


SqlSourceBuilder


我们再来看看这个 Builder 类

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);
  }
  return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
复制代码

最后都是依赖它来生成最后的 Sql 的


该类中存在一个静态内部类 ParameterMappingTokenHandler 用于解释参数

@Override
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");
  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 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();
}



目录
相关文章
|
2月前
|
SQL Java 关系型数据库
MyBatis-Plus详解(4)
MyBatis-Plus详解(4)
25 0
|
Java 数据库连接 测试技术
Mybatis-PLUS详解
Mybatis-PLUS详解
215 0
|
10月前
|
SQL Java 关系型数据库
mybatis plus很好,但是我被它坑了!
作者今天在开发一个后台发送消息的功能时,由于需要给多个用户发送消息,于是使用了 mybatis plus 提供的 saveBatch() 方法,在测试环境测试通过上预发布后,测试反应发送消息接口很慢得等 5、6 秒,于是我就登录预发布环境查看执行日志,发现是 mybatis plus 提供的 saveBatch() 方法执行很慢导致,于是也就有了本篇文章。
54 1
|
9月前
|
SQL 安全 Java
Mybatis中# 和 $ 的使用详解
Mybatis中# 和 $ 的使用详解
145 0
|
SQL XML 存储
Mybatis总结
Mybatis总结
101 0
|
SQL Oracle Java
|
SQL XML 缓存
|
SQL XML 存储
使用Mybatis-plus
使用Mybatis-plus
89 0
使用Mybatis-plus