原因:
以前在传递参数的时候,出现传递单个参数,有的时候用#{id} 可以成功,有的时候报错,只能改成#{_parameter}
---------------------------------------------------------------------------------------------------------------------------
分析:
参数的使用分为两部分:
- 第一种就是常见
#{username}
或者${username}
。 - 第二种就是在动态SQL中作为条件,例如
<if test="username!=null and username !=''">
动态SQL为什么会处理参数呢?
主要是因为动态SQL中的<if>,<bind>,<foreache>
都会用到表达式,表达式中会用到属性名,属性名对应的属性值如何获取呢?获取方式就在这关键的一步。不知道多少人遇到Could not get property xxx from xxxClass或: Parameter ‘xxx’ not found. Available parameters are[…],都是不懂这里引起的。
在DynamicContext.java中,从构造方法看起:
public DynamicContext(Configuration configuration, Object parameterObject) {
if (parameterObject != null && !(parameterObject instanceof Map)) {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
bindings = new ContextMap(metaObject);
} else {
bindings = new ContextMap(null);
}
bindings.put(PARAMETER_OBJECT_KEY, parameterObject);
bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId());
}
这里的Object parameterObject
就是我们经过前面两步处理后的参数。这个参数经过前面两步处理后,到这里的时候,他只有下面三种情况:
null
,如果没有入参或者入参是null
,到这里也是null
。Map
类型,除了null
之外,前面两步主要是封装成Map
类型。- 数组、集合和
Map
以外的Object
类型,可以是基本类型或者实体类。
看上面构造方法,如果参数是1,2情况时,执行代码bindings = new ContextMap(null);
参数是3情况时执行if
中的代码。我们看看ContextMap
类,这是一个内部静态类,代码如下:
static class ContextMap extends HashMap<String, Object> {
private MetaObject parameterMetaObject;
public ContextMap(MetaObject parameterMetaObject) {
this.parameterMetaObject = parameterMetaObject;
}
public Object get(Object key) {
String strKey = (String) key;
if (super.containsKey(strKey)) {
return super.get(strKey);
}
if (parameterMetaObject != null) {
// issue #61 do not modify the context when reading
return parameterMetaObject.getValue(strKey);
}
return null;
}
}
我们先继续看DynamicContext
的构造方法,在if/else
之后还有两行:
bindings.put(PARAMETER_OBJECT_KEY, parameterObject);
bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId());
其中两个Key分别为:
public static final String PARAMETER_OBJECT_KEY = "_parameter";
public static final String DATABASE_ID_KEY = "_databaseId";
也就是说1,2两种情况的时候,参数值只存在于"_parameter"
的键值中。3情况的时候,参数值存在于"_parameter"
的键值中,也存在于bindings
本身。
当动态SQL取值的时候会通过OGNL从bindings
中获取值。MyBatis在OGNL中注册了ContextMap
:
static {
OgnlRuntime.setPropertyAccessor(ContextMap.class, new ContextAccessor());
}
当从ContextMap
取值的时候,会执行ContextAccessor
中的如下方法:
@Override
public Object getProperty(Map context, Object target, Object name)
throws OgnlException {
Map map = (Map) target;
Object result = map.get(name);
if (map.containsKey(name) || result != null) {
return result;
}
Object parameterObject = map.get(PARAMETER_OBJECT_KEY);
if (parameterObject instanceof Map) {
return ((Map)parameterObject).get(name);
}
return null;
}
参数中的target
就是ContextMap
类型的,所以可以直接强转为Map
类型。
参数中的name
就是我们写在动态SQL中的属性名。
-------------------------------------------------总结----------------------------------------------------
1 动态SQL 中的参数和${_param}的参数,采用的都是OGNL的方式来调用参数,这时,如果是单个参数的话,只能采用_paramter的方式来传递值
也就是只能靠
public static final String PARAMETER_OBJECT_KEY = "_parameter";
这个转换后的方式来传递参数
2 #{_parame}的方式,如果是传递的单个参数,那么在#{_param}中可以随便写,可以用#{id}(方便理解)或者#{_parameter}