先說結論:
當輸入參數只有一個且沒有使用@Param註解時,MyBatis會直接傳遞這個參數;當輸入參數多於一個,或者使用了@Param註解時,MyBatis會將參數封裝在Map中傳遞,這時的Map的key分爲以下幾種可能:
- Map中會有param1, param2這樣的key,其順序對應輸入參數的順序。無論是否有@Param註解。
- 對於@Param註解的參數,Map中會保存註解中給定的名字作爲key
- 對於沒有用@Param註解的參數,Map中會用1、2、3 ..這樣的數字作爲key,按順序保存輸入參數。
下面來看一下源碼。
首先,判斷一個方法中是否有用@Param註解的參數:
private boolean hasNamedParams(Method method) {
final Object[][] paramAnnos = method.getParameterAnnotations();
for (Object[] paramAnno : paramAnnos) {
for (Object aParamAnno : paramAnno) {
if (aParamAnno instanceof Param) {
return true;
}
}
}
return false;
}
如果有用@Param註解的參數,取出註解中給出的參數名:
private String getParamNameFromAnnotation(Method method, int i, String paramName) {
final Object[] paramAnnos = method.getParameterAnnotations()[i]; // 獲取第i個參數的註解
for (Object paramAnno : paramAnnos) {
if (paramAnno instanceof Param) {
paramName = ((Param) paramAnno).value();
break;
}
}
return paramName;
}
注意方法的輸入參數,method表示是哪個方法上,i 表示第幾個參數, paramName是傳進來的參數名,如果該參數沒有用@Param註解,則返回傳進來的paramName。
下面這個方法返回一個TreeMap(有序),其key表示參數的順序,比如key=0代表第0個參數;value表示參數的名字,如果有用@Param註解標註,則爲標註的參數名,否則和key相等,即用參數的序號作爲參數的名字。
private SortedMap<Integer, String> getParams(Method method, boolean hasNamedParameters) {
final SortedMap<Integer, String> params = new TreeMap<Integer, String>();
final Class<?>[] argTypes = method.getParameterTypes();
for (int i = 0; i < argTypes.length; i++) {
if (!RowBounds.class.isAssignableFrom(argTypes[i]) && !ResultHandler.class.isAssignableFrom(argTypes[i])) {
String paramName = String.valueOf(params.size()); // 參數名,默認爲參數的序號
if (hasNamedParameters) { //如果有使用@Param註解,則獲取註解標註的參數名
paramName = getParamNameFromAnnotation(method, i, paramName); // 這裏paramName作爲參數傳進來,表示默認值
}
params.put(i, paramName);
}
}
return params;
}
其中hasNamedParameters只是從整個方法的維度,給出該方法是否有使用@Param註解的參數;即使其值爲true,具體到某一個參數上面,可能沒有使用@Param註解,因此調用getParamNameFromAnnotation傳入的paramName就作爲默認值返回,即參數的序號。
最後將調用方法的參數轉換爲MyBatis內部使用的參數:
public Object convertArgsToSqlCommandParam(Object[] args) {
final int paramCount = params.size();
if (args == null || paramCount == 0) {
return null;
} else if (!hasNamedParameters && paramCount == 1) {
return args[params.keySet().iterator().next().intValue()];
} else {
final Map<String, Object> param = new ParamMap<Object>();
int i = 0;
for (Map.Entry<Integer, String> entry : params.entrySet()) {
param.put(entry.getValue(), args[entry.getKey().intValue()]);
// issue #71, add param names as param1, param2...but ensure backward compatibility
final String genericParamName = "param" + String.valueOf(i + 1);
if (!param.containsKey(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
其中args爲Dao方法的輸入參數,這裏已經轉換成了數組,其實就是動態代理的invoke方法傳入的參數。
該方法首先對輸入參數進行計數,使用的params就是前面介紹的getParams方法的返回值。
if (!hasNamedParameters && paramCount == 1)
上面的條件判斷,即方法沒有使用@Param註解,且只有一個參數,這時返回
args[params.keySet().iterator().next().intValue()]
即直接將其作爲Object返回。
如果上面的條件不滿足的話,首先新建一個Map作爲返回值:
final Map<String, Object> param = new ParamMap<Object>();
然後,設置map的key和value:
param.put(entry.getValue(), args[entry.getKey().intValue()]);
然後爲了兼容性,做了如下操作
final String genericParamName = "param" + String.valueOf(i + 1);
if (!param.containsKey(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
即設置param1、param2這樣的key。
到此爲止,需要的參數對象Object就構建完成,其中封裝了Dao傳入的多個參數,並根據參數是否有@Param註解,影響了參數對象的類型(是否是map)。
參數封裝完成之後,下一步將其傳遞給SqlSession。