Ibatis 关于DynamicSql源码的解读

一. 业务背景

由于最近在做数据罗盘的开发,其中会有挺多的排序,聚会运算的要求。而我们利用了集团的garuda运算平台,想利用它建的智能索引和智能查询优化器来优化sql处理复杂业务,所以sql会堆上一些较复杂业务。为了更好的实践动态sql,了解它背后的原理,于是对它的源码进行了一番探索。


二. 分析Ibatis DynamicSql原理

1.实现动态SQL的核心流程
  动态sql的动态语句是通过动态标签来表达语义的,它们维护在配置文件中。生成动态sql主要有两个步骤:
  a. 初始化过程中,解析配置文件中的动态标签与sql配置,生成由SqlChild节点组成的抽象语法树。
  b. 请求处理过程中,根据运行期的参数对象解释抽象语法树,生成当前请求的动态SQL语句。

与动态sql解析生成的核心类有:
(1) SqlResource
该接口含义是作为sql对象的来源,通过该接口可以获取sql对象。其唯一的实现类是XmlSqlResource,表示通过xml文件生成sql对象。
(2) Sql
该接口可以生成sql语句和获取sql相关的上下文环境(如ParameterMap、ResultMap等),有三个实现类: RawSql表示为原生的sql语句,在初始化即可确定sql语句;SimpleDynamicSql表示简单的动态sql,即sql语句中参数通过$property$方式指定,参数在sql生成过程中会被替换,不作为sql执行参数;DynamicSql表示动态sql,即sql描述文件中包含isNotNull、isGreaterThan等条件标签。
(3)SqlChild
该接口表示sql抽象语法树的一个节点,包含sql语句的片段信息。该接口有两个实现类: SqlTag表示动态sql片段,即配置文件中的一个动态标签,内含动态sql属性值(如prepend、property值等);SqlText表示静态sql片段,即为原生的sql语句。每条动态sql通过SqlTag和SqlText构成相应的抽象语法树。
(4)SqlTagHandler
该接口表示SqlTag(即不同的动态标签)对应的处理方式。比如实现类IsEmptyTagHandler用于处理isEmpty标签,IsEqualTagHandler用于处理isEqual标签等。
(5)SqlTagContext
用于解释sql抽象语法树时使用的上下文环境。通过解释语法树每个节点,将生成的sql存入SqlTagContext。最终通过SqlTagContext获取完整的sql语句。
我使用的ibatis的版本是2.3.4.726,下面就结合源码分析下整个流程

2.解读源码

(1)初始化过程
a.XmlSqlSource.getSql()

   public Sql getSql() {
 state.getConfig().getErrorContext().setActivity("processing an SQL statement");

    boolean isDynamic = false;
    StringBuffer sqlBuffer = new StringBuffer(); 
    // 根据解析sqlmap的xml文件的配置,初始化DynamicSql对象
    DynamicSql dynamic = new DynamicSql(state.getConfig().getClient().getDelegate());
    //parseDynamicTags()方法通过配置文件生成DynamicSql的抽象语法树
    isDynamic = parseDynamicTags(parentNode, dynamic, sqlBuffer, isDynamic, false);
    String sqlStatement = sqlBuffer.toString();
    //判断是否是动态sql
    if (isDynamic) {
      return dynamic;
    //如果不是动态sql,返回原生sql
    } else {
      return new RawSql(sqlStatement);
    }
  }

该段代码主要是XmlSqlSource通过xml文件的配置生成sql对象,parseDynamicTags()生成抽象语法树方法是核心。

b.XmlSqlSource.parseDynamicTags()

private boolean parseDynamicTags(Node node, DynamicParent dynamic, StringBuffer sqlBuffer, boolean isDynamic, boolean postParseRequired) {
    state.getConfig().getErrorContext().setActivity("parsing dynamic SQL tags");

    // 遍历子节点
    NodeList children = node.getChildNodes(); 
    for (int i = 0; i < children.getLength(); i++) {
      Node child = children.item(i);
      String nodeName = child.getNodeName();
      // 如果是文本类型的子节点
      if (child.getNodeType() == Node.CDATA_SECTION_NODE
          || child.getNodeType() == Node.TEXT_NODE) {
        // 获得该节点的文本数据
        String data = ((CharacterData) child).getData();
        data = NodeletUtils.parsePropertyTokens(data, state.getGlobalProps());

        SqlText sqlText;
        // 如果不需要解析sql,直接将data的纯文本传入sqlText
        if (postParseRequired) {
          sqlText = new SqlText();
          sqlText.setPostParseRequired(postParseRequired);
          sqlText.setText(data);
        } else {
        // 否则调用parseInlineParameterMap()解析sql文本 
        // 主要将其中的参数#param#替换为占位符?
        sqlText = PARAM_PARSER.parseInlineParameterMap(state.getConfig().getClient().getDelegate().getTypeHandlerFactory(), data, null);
          sqlText.setPostParseRequired(postParseRequired);
        }

        // 将当前节点加入语法树中的父节点
        dynamic.addChild(sqlText);
        // 将该节点数据拼接上原生sql串
        sqlBuffer.append(data);

        // 如果是include标签
      } else if ("include".equals(nodeName)) {
        // 遍历出该子节点所有的属性,取出refid的属性值
        Properties attributes = NodeletUtils.parseAttributes(child, state.getGlobalProps());
        String refid = (String) attributes.get("refid");
        // 找到refid引用的node节点
        Node includeNode = (Node) state.getSqlIncludes().get(refid);
        
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值