MyBatis源码的学习(22)---关于#和$的区别,简单类型$中只能用value吗??

本文探讨了MyBatis中#和$在处理简单类型时的差异,通过测试和源码分析发现,在MyBatis 3.5.4版本中,不论是#{}还是${},在接收简单类型参数时,内部名称可以任意写,并无实际区别。文中详细介绍了源码解析的过程,特别是getBoundSql方法和handleToken方法的作用。

最近在整理mybatis中关于#和$的区别的时候,其中看到网上有一条是:

${}接收输入参数,类型可以是简单类型,pojo、hashmap。

如果接收简单类型,${}中只能写成value。

#{}表示一个占位符号,#{}接收输入参数,类型可以是简单类型,pojo、hashmap。

如果接收简单类型,#{}中可以写成value或其它名称。

上面加黑的部分,经过测试,不管是$还是#,单个参数时,二者没有区别的,里面可以任意写,版本是3.5.4的

package learn;

import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.session.RowBounds;

import java.util.List;

public interface UserMapper {

  //使用注解式的sql注入
  @Select("select * from u_user where usercode = #{简单类型只有一个参数的时候随便写}")
  public  User selectOne(String id);

  //使用注解式的sql注入
  @Select("select * from u_user where usercode = #{param1.userCode}")
  public  User selectOneByUser(@Param("user") User user);

  //使用注解式的sql注入
  @Select("select * from u_user where usercode = #{userCode}")
  public  User selectOneByUser2(User user666);

  //使用注解式的sql注入
  @Select("select * from u_user limit ${index},1")
  public  User selectOneByUser3(@Param("index") int index);

  //使用注解式的sql注入
  @Select("select * from u_user limit ${单个简单类型参数随便写没问题},1")
  public  User selectOneByUser4(Integer index);

  //使用注解式的sql注入
  @Select("select * from u_user where usercode like '%${随便写都可以}%' ")
  public  User selectOneByUser5(String id);

}

下面我们进行源码分析:

先分析#{}这种的情况

之前的文章都有介绍果参数赋值的操作,我们直接看赋值的代码

 //DefaultParameterHandler 类 进行赋值操作
@Override
  public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          String propertyName = parameterMapping.getProperty();
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
//对于 #{} 才会走这个方法进行赋值
//如果是简单类型,String,包装类,Date等40个默认处理器,存在,我们直接进行了赋值。也就是直接把
//入参的值传递给了value,所以  #{这里可以随便写}
            value = parameterObject;
          } else {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
//将value赋值操作
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException | SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

然后我们看,${} 的赋值操作,在我们进行getBoundSql()操作的时候,才会解析动态sql。所以我们直接看getBoundSql方法

// 来自 DynamicSqlSource类 
@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;
  }

//动态文本节点的信息存储在TextSqlNode类中,我们看它的解析方法

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

在这个parse方法中,我们重点看handleToken方法, 下面是处理前和处理后的效果:

点进去,一直断点跟踪,最后会到下面的逻辑:

重点看我们的map.get(name)

首先这个map是一个ContextMap,它重写了get方法

//DynamicContext$ContextMap 内部类 
 @Override
    public Object get(Object key) {
      String strKey = (String) key;
      if (super.containsKey(strKey)) {//存在key时,使用父类的get操作
        return super.get(strKey);
      }

      if (parameterMetaObject == null) {
        return null;
      }
      //我们的在这个分支
      if (fallbackParameterObject && !parameterMetaObject.hasGetter(strKey)) {
//返回我们的结果 
        return parameterMetaObject.getOriginalObject();
      } else {
        // issue #61 do not modify the context when reading
        return parameterMetaObject.getValue(strKey);
      }
    }
  }

结论:${},#{}在对简单类型处理的时候(包装类,字符串,Date),里面都是可以任意写的,在这里是没有区别的。 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值