最近在整理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),里面都是可以任意写的,在这里是没有区别的。
本文探讨了MyBatis中#和$在处理简单类型时的差异,通过测试和源码分析发现,在MyBatis 3.5.4版本中,不论是#{}还是${},在接收简单类型参数时,内部名称可以任意写,并无实际区别。文中详细介绍了源码解析的过程,特别是getBoundSql方法和handleToken方法的作用。
1761

被折叠的 条评论
为什么被折叠?



