• Mybatis源码解析之--谈谈${}


    一百度${}, #{} 网上有好多答案,不过还是自己跟过源码才能理解原理

    一 概述

      其实${} 没什么特别的,也可以从入参为POJO里获取属性值进行替换。需要注意的是它通过ognl表达式把POJO做变更,然后从里面获取属性值,进行纯粹的字符串拼接。

      所以需要注意,比如一张表有一个列是VARCHAR,如果在POJO里 setName("name") 是不够的,应该是setName("'name'"),加上单引号。

      还有就是$ #是可以同时用的。我们就来看看源码吧

    二 源码分析

      现在我们有如下的sql语句

      <insert id="insertDollar" parameterType="me.gacl.domain.User" >
        insert into t_user
        values (${userId}, #{userName}, ${userBirthday}, 
          ${userSalary})
      </insert>

      如果代码这样写是没有问题的

            User user = (User) ac.getBean("user");
            user.setUserId("'11'");
            user.setUserName("白虎神皇xdp");
            user.setUserBirthday("'123'");
            user.setUserSalary(10000D);
            user.setTableName("t_user");
            userService.insertDollar(user);

      首先要说明的是,如果一个sql里包含了$,mybatis在解析的时候是当作  DynamicSqlSource 的

      之前分析StatementHandler的时候我们知道,执行前要根据入参确定最终的sql语句也就是获取boundSql

      我们看  DynamicSqlSource.getBoundSql 

    public BoundSql getBoundSql(Object parameterObject) {
        DynamicContext context = new DynamicContext(configuration, parameterObject);
        rootSqlNode.apply(context);//经过了apply之后 insert into t_user values ('11', #{userName}, '123', 10000.0)
        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);
        for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
          boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
        }
        return boundSql;
      }

    继续分析apply,对于$,每一个sqlNode都是  TextSqlNode 

    @Override
      public boolean apply(DynamicContext context) {
        GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));
        context.appendSql(parser.parse(text));
        return true;
      }
      
      private GenericTokenParser createParser(TokenHandler handler) {
        return new GenericTokenParser("${", "}", handler);
      }

      parse方法之前在分析#的时候,是分析过的,就是根据输入的开始和结束符进行字符串的解析,很纯粹的字符串操作,这里面要注意的是 handler.handleToken(content) 

    public String parse(String text) {
        StringBuilder builder = new StringBuilder();
        if (text != null && text.length() > 0) {
          char[] src = text.toCharArray();
          int offset = 0;
          int start = text.indexOf(openToken, offset);
          while (start > -1) {
            if (start > 0 && src[start - 1] == '\') {
              // the variable is escaped. remove the backslash.
              builder.append(src, offset, start - offset - 1).append(openToken);
              offset = start + openToken.length();
            } else {
              int end = text.indexOf(closeToken, start);
              if (end == -1) {
                builder.append(src, offset, src.length - offset);
                offset = src.length;
              } else {
                builder.append(src, offset, start - offset);
                offset = start + openToken.length();
                String content = new String(src, offset, end - offset);
                builder.append(handler.handleToken(content));
                offset = end + closeToken.length();
              }
            }
            start = text.indexOf(openToken, offset);
          }
          if (offset < src.length) {
            builder.append(src, offset, src.length - offset);
          }
        }
        return builder.toString();
      }

       BindingTokenParser.handleToken 

    @Override
        public String handleToken(String content) {
          Object parameter = context.getBindings().get("_parameter");
          if (parameter == null) {
            context.getBindings().put("value", null);
          } else if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) {
            context.getBindings().put("value", parameter);
          }
          Object value = OgnlCache.getValue(content, context.getBindings());//通过ognl从对象里取值
          String srtValue = (value == null ? "" : String.valueOf(value)); // issue #274 return "" instead of "null"
          checkInjection(srtValue);
          return srtValue; //注意这里是String
        }

      到此关于$的源码就分析完了,特别注意# $是可以共存的,先解析$符号部分,然后再解析#部分。

  • 相关阅读:
    HDFS基本原理及数据存取实战
    关于软件工程的思考06:微软解决方案框架MSF
    关于软件工程的思考05:敏捷流程
    关于软件工程的思考04:团队和流程
    关于软件工程的思考03:两人合作
    关于软件工程的思考02:软件工程师的成长
    关于软件工程的思考01:个人技术流程
    Linux31:磁盘配额与磁盘阵列简介
    Linux30:LVM和SELinux简介
    Linux29:SSH
  • 原文地址:https://www.cnblogs.com/juniorMa/p/14044336.html
Copyright © 2020-2023  润新知