mybaits之生成可执行SQL语句

写在前面

本文主要是分析一个MappedStatement是如何生成最终的sql语句的,其中会涉及到动态SQL语句的解析,关于这部分的内容可以参考这篇文章
想要系统学习的,可以参考这篇文章,重要!!!

1:入口

当我们执行一次数据操作的是时候,使用的API是org.apach.ibatis.session.SqlSession,其内部调用org.apache.ibatis.executor.Executor,然后执行器内部调用当前执行的statement的MappedStatement对象,MappeStatement对象内部会调用org.apache.ibatis.mapping.SqlSource的API来生成最终可执行的sql语句,最后通过JDBC的API执行,返回最终结果,如下是可能一个调用过程:
在这里插入图片描述
其中的org.apache.ibatis.mapping.SqlSource的API就是我们本文要分析的,也就是要分析的入口了,具体参考2:SqlSource获取SQL

2:SqlSource获取SQL

接口org.apache.ibatis.mapping.SqlSource源码如下:

org.apache.ibatis.mapping.SqlSource.
// 代表来自XML或者是注解配置的MappedStatement对应的SQL语句信息,
// 通过传入用户参数获取最终可执行的sql语句对象
public interface SqlSource {
  // 传入用户参数,获取可执行的sql语句对象
  BoundSql getBoundSql(Object parameterObject);

}

实现类:
在这里插入图片描述
不同的类对应了不同的MappedStatement的来源,如下:

1:存在动态SQL语句的statement(xml和注解):调用DynamicSqlSource,然后内部调用StaticSqlSource。
2:存在#{}形式参数的(xml和注解):调用RawSqlSource,然后内部调用StaticSqlSource。
3:sql provider:调用ProviderSqlSource,再调用StaticSqlSource。

关于存在动态SQL语句的statement的场景参考3:获取动态sql语句。关于存在#{}形式参数的场景参考5:获取Raw sql语句。sql provider形式参考6:sql provider生成sql语句

3:获取动态sql语句

比如可能的如下配置:

<select id="queryListWithXmlDynamicSqlForeach" resultMap="myResultMap" parameterType="java.util.List">
    SELECT * FROM test_dynamic_sql t
    WHERE t.`id` IN
    <foreach collection="list" index="" open="(" close=")" item="itemId" separator=",">
        #{itemId}
    </foreach>
</select>

源码如下:

org.apache.ibatis.scripting.xmltags.DynamicSqlSource#getBoundSql
// parameterObject:封装的是用户调用方法时传入的参数信息
public BoundSql getBoundSql(Object parameterObject) {
  // 使用全局配置文件对应的configuration对象和用户传入参数信息创建DynamicContext对象
  // 该对象用于辅助解析动态sql节点生成sql语句
  DynamicContext context = new DynamicContext(configuration, parameterObject);
  // rootSqlNode是MixSqlNode对象,封装了对应的动态sql节点的所有子节点信息
  // apply方法调用后,在context中就生成了sql语句了
  // <2021-08-19 13:43:10>
  rootSqlNode.apply(context);
  // 创建SqlSource构造器对象
  // <2021-08-19 13:51:54>
  SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
  Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
  SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
  // 执行getBoundSql获取sql,这里的类型一般是StaticSqlSource
  // <2021-08-19 14:47:32>
  BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
  // 获取动态生成的参数信息,并设置到BoundSql对象中
  for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
    boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
  }
  // 返回BoundSql对象,用于后续的具体的sql执行工作
  return boundSql;
}

<2021-08-19 13:43:10>处是应用动态sql语句的所有子节点,生成sql语句,具体参考这篇文章<2021-08-19 13:51:54>处是创建SqlSourceBuilder对象,然后调用其parse方法,该对象用于替换sql语句中的#{}?并获取占位符对应的ParameterMapping,具体参考4:SqlSourceBuilder<2021-08-19 14:47:32>处是调用获取BoundSql对象,具体参考4.2:通过StaticBoundSql获取sql语句

4:SqlSourceBuilder

构造函数如下:

org.apache.ibatis.builder.SqlSourceBuilder#SqlSourceBuilder
public SqlSourceBuilder(Configuration configuration) {
  super(configuration);
}

public BaseBuilder(Configuration configuration) {
  this.configuration = configuration;
  // 类型别名注册器
  this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
  // 类型处理器注册器
  this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
}

parse方法源码如下:

org.apache.ibatis.builder.SqlSourceBuilder#parse
// originalSql:解析后生成的sql语句,如foreach场景中可能是:
// SELECT * FROM test_dynamic_sql t WHERE t.`id` IN (   #{__frch_itemId_0}  ,  #{__frch_itemId_1} )
// parameterType:参数的类型
// additionalParameters:额外参数,即动态生成的参数,如bind,foreach动态生成参数
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
  // 创建ParameterMappingTokenHandler对象
  // <2021-08-19 14:09:55>
  ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
  // 创建GenericTokenParser,并以"#{","}"进行解析
  GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
  // 执行解析
  // <2021-08-19 14:44:00>
  String sql = parser.parse(originalSql);
  // 创建StaticSqlSource并返回
  // sql:替换#{xxx}为?后的sql语句,如INSERT INTO `test_dynamic_sql` (`myname`, `myage`) VALUES (?, ?) 
  // handler.getParameterMappings():封装了sql语句中?对应的参数信息的ParameterMapping集合
  return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}

<2021-08-19 14:09:55>处是创建ParameterMappingTokenHandler对象,关于该对象具体参考4.1:ParameterMappingTokenHandler,<2021-08-19 14:44:00>是解析#{xxx}配置的信息生成ParameterMapping对象,具体参考4.1:ParameterMappingTokenHandler

4.1:ParameterMappingTokenHandler

该类负责将匹配的#{}替换成,并获取对应的ParemterMapping对象,构造函数如下:

handleToken方法用于解析参数对应的ParameterMapping对象,并添加到集合中,源码如下

org.apache.ibatis.builder.SqlSourceBuilder.ParameterMappingTokenHandler#handleToken
public String handleToken(String content) {
  // 构建ParameterMapping并添加到paramterMapping集合中
  // private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();
  // <2021-08-19 14:24:42>
  parameterMappings.add(buildParameterMapping(content));
  // 返回?号作为替换的值,即#{}变为?
  return "?";
}

<2021-08-19 14:24:42>处源码如下:

org.apache.ibatis.builder.SqlSourceBuilder.ParameterMappingTokenHandler#buildParameterMapping
// content:#{xxx}中xxx的值
private ParameterMapping buildParameterMapping(String content) {
  // 存储#{}内部所有信息的字典,如javaType,jdbcType,typeHandler等
  Map<String, String> propertiesMap = parseParameterMapping(content);
  // 获取#{}内的属性名称
  String property = propertiesMap.get("property");
  Class<?> propertyType;
  // 获取当前属性的类型,即#{xxx}中配置的xxx的数据类型
  if (metaParameters.hasGetter(property)) { 
    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);
    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;
  // 循环处理#{xxx}对应的字段的所有信息,处理所有可能配置的属性
  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));
  }
  // 构造#{xxx}对应的ParameterMapping对象并返回
  return builder.build();
}

如下是当配置为#{myname}时的结果:
在这里插入图片描述
有参数名称信息,出参入参信息,java类型信息,数据库数据转换为当前属性值的类型处理器信息,等等,当查询结果后进行设置值时需要用到这里的信息。

4.2:通过StaticBoundSql获取sql语句

源码如下:

public class StaticSqlSource implements SqlSource {
  // 待执行的sql语句,如select * from foo where id in (?, ?, ?)
  private String sql;
  // 待执行sql语句对应的参数值集合
  private List<ParameterMapping> parameterMappings;
  // 全局配置文件对应的Configuration对象
  private 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;
  }

  public BoundSql getBoundSql(Object parameterObject) {
    // 创建BoundSql对象并返回
    // <2021-08-19 14:52:12>
    return new BoundSql(configuration, sql, parameterMappings, parameterObject);
  }

}

<2021-08-19 14:52:12>处是创建BoundSql并返回,其中BoundSql是一个用于封装sql和参数等信息的对象,源码如下:

// 封装处理了动态sql内容后的sql语句信息,可能包含?,以及对应顺序的ParameterMapping参数信息
// 在ParamterMapping中包含了至少参数名称的信息,以便获取参数信息,当然也可能有通过动态sql
// 生成的参数信息,比如通过bind
public class BoundSql {
  // 要执行的sql
  private String sql;
  // 参数信息
  private List<ParameterMapping> parameterMappings;
  // 原始参数对象
  private Object parameterObject;
  // 动态生成的参数信息,如通过bind,或者是foreach动态生成的
  private Map<String, Object> additionalParameters;
  private 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<String, Object>();
    this.metaParameters = configuration.newMetaObject(additionalParameters);
  }
  // 获取sql语句
  public String getSql() {
    return sql;
  }
  
  // 获取要使用的参数
  public List<ParameterMapping> getParameterMappings() {
    return parameterMappings;
  }

  public Object getParameterObject() {
    return parameterObject;
  }

  public boolean hasAdditionalParameter(String name) {
    return metaParameters.hasGetter(name);
  }

  public void setAdditionalParameter(String name, Object value) {
    metaParameters.setValue(name, value);
  }

  public Object getAdditionalParameter(String name) {
    return metaParameters.getValue(name);
  }
}

5:获取Raw sql语句

比如可能的如下配置:

org.apache.ibatis.scripting.defaults.RawSqlSource#getBoundSql
public BoundSql getBoundSql(Object parameterObject) {
  // sqlSource为StaticSqlSource
  // <2021-08-19 15:04:54>
  return sqlSource.getBoundSql(parameterObject);
}

<2021-08-19 15:04:54>处具体参考4.2:通过StaticBoundSql获取sql语句

6:sql provider生成sql语句

可能如下配置:

@InsertProvider(type = MySelectSqlProvider.class, method = "provideInsertSql")
/*
    @SelectKey(
            statement = { "SELECT UNIX_TIMESTAMP(NOW())" },
            keyProperty = "id",
            before = true,
            resultType = java.lang.Void.class,
            statementType = org.apache.ibatis.mapping.StatementType.PREPARED)
*/
void insertWithSelectSqlProvider(TestMybatisAnnotationDto testMybatisAnnotationDto);

源码如下:

org.apache.ibatis.builder.annotation.ProviderSqlSource#getBoundSql
public BoundSql getBoundSql(Object parameterObject) {
  // 创建SqlSource,这里结果为StaticSqlSource
  // <2021-08-19 15:13:16>
  SqlSource sqlSource = createSqlSource(parameterObject);
  // 生成BoundSql
  return sqlSource.getBoundSql(parameterObject);
}

<2021-08-19 15:13:16>处源码如下:

org.apache.ibatis.builder.annotation.ProviderSqlSource#createSqlSource
private SqlSource createSqlSource(Object parameterObject) {
  try {
    String sql;
    // 反射调用sql provider类的方法获取sql语句
    if (providerTakesParameterObject) {
      sql = (String) providerMethod.invoke(providerType.newInstance(), parameterObject);
    } else {
      sql = (String) providerMethod.invoke(providerType.newInstance());
    }
    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
    // 解析生成SqlSource对象
    // <2021-08-19 15:17:03>
    return sqlSourceParser.parse(sql, parameterType, new HashMap<String, Object>());
  } catch (Exception e) {
    throw new BuilderException("Error invoking SqlProvider method ("
        + providerType.getName() + "." + providerMethod.getName()
        + ").  Cause: " + e, e);
  }
}

<2021-08-19 15:17:03>处具体参考4:SqlSourceBuilder

写在后面

到这里,终于生成了可以执行的sql语句了,那么接下来的问题就是sql到底是如何执行的了,关于这部分内容可以参考这篇文章

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值