mybatis--引數傳遞原始碼分析
阿新 • • 發佈:2019-02-08
單元測試:
SqlSession openSession = sqlSessionFactory.openSession();
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
Employee employee = mapper.getEmpByIdAndLastName(1, "Tom");
第三行在執行斷點除錯時, 先來到一個名字叫mapperProxy.class動態代理的InvocationHandler
public class MapperProxy<T> implements InvocationHandler, Serializable {}
然後看裡面的invoke方法
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //有一個判斷,當前方法宣告的類是在object裡面宣告的, 直接放行. if (Object.class.equals(method.getDeclaringClass())) { try { return method.invoke(this, args); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } //否則,把method包裝成一mapperMethod final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); }
下面看看如何執行的, 我們斷點進入execute
看上面的程式碼, Object param=method.convertArgsToSqlCommandParam(args);public Object execute(SqlSession sqlSession, Object[] args) { Object result; //執行之前,先判斷是什麼型別的,對應的走增刪改查的方法 //每次呼叫之前,resulte就是返回值,會把你傳過來的引數轉化為sql能用的. switch (command.getType()) { case INSERT: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.insert(command.getName(), param)); break; } case UPDATE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.update(command.getName(), param)); break; } case DELETE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.delete(command.getName(), param)); break; } case SELECT: if (method.returnsVoid() && method.hasResultHandler()) {//沒有返回值 executeWithResultHandler(sqlSession, args); result = null; } else if (method.returnsMany()) {//多個 result = executeForMany(sqlSession, args); } else if (method.returnsMap()) {//map result = executeForMap(sqlSession, args); } else if (method.returnsCursor()) {//遊標 result = executeForCursor(sqlSession, args); } else { Object param = method.convertArgsToSqlCommandParam(args);//返回單個物件 //底層呼叫的還是sqlSession result = sqlSession.selectOne(command.getName(), param); } break; case FLUSH: result = sqlSession.flushStatements(); break; default: throw new BindingException("Unknown execution method for: " + command.getName()); } if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) { throw new BindingException("Mapper method '" + command.getName() + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ")."); } return result; }
這個裡面是:
public Object convertArgsToSqlCommandParam(Object[] args) {
return paramNameResolver.getNamedParams(args);
}
我們跳進來看, 會發現這麼一個方法:getNamedParams這一段就是處理的程式碼, 我們看到param1,param2.這一塊就是把原來的值封裝成了param1和param2
ParamNameResolver解析引數封裝map的, 這一塊的程式碼我們來大致走一走
/**
* <p>
* A single non-special parameter is returned without a name.<br />
* Multiple parameters are named using the naming rule.<br />
* In addition to the default names, this method also adds the generic names (param1, param2,
* ...).
* </p>
*/
public Object getNamedParams(Object[] args) {
//上手先獲取了一個names.size(),而這個names裡面是有值的:{0=id,1=lastName}, key就是0,1 value就是id和lastName. 這裡我們就能看出來是呼叫的哪個mapper介面, 見下圖. 那name是如何確定的,我們見圖下面的分析
final int paramCount = names.size();
...........
}
這個name是我們ParamNameResolver的一個引數
private final SortedMap<Integer, String> names;
而這個引數值的確定, 我們來看這個類的構造器1.獲取每個標了param註解的param值:id ,lastname; 賦值給name
2.每次解析一個引數給map中儲存資訊:(key:引數索引,value:name的值)
name的值: 標註了param註解:註解的值
沒有標註:
1>全域性配置:useActualParamName(jdk1.8):name=引數名 2>name=map.size();相當於當前元素的索引,假如傳入了第三個,沒標註解,那就key是2,value也是2了{0=id, 1=lastName,2=2}
public ParamNameResolver(Configuration config, Method method) {
//先拿到所有的引數
final Class<?>[] paramTypes = method.getParameterTypes();
//以及引數的註解
final Annotation[][] paramAnnotations = method.getParameterAnnotations();
final SortedMap<Integer, String> map = new TreeMap<Integer, String>();
int paramCount = paramAnnotations.length;
// get names from @Param annotations 開始標註引數的索引
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
if (isSpecialParameter(paramTypes[paramIndex])) {
// skip special parameters
continue;
}
String name = null;
for (Annotation annotation : paramAnnotations[paramIndex]) {
if (annotation instanceof Param) {//如果當前引數的註解是param
hasParamAnnotation = true;
name = ((Param) annotation).value();//拿到param註解的value值
break;
}
}
if (name == null) {
// @Param was not specified.
if (config.isUseActualParamName()) {
name = getActualParamName(method, paramIndex);//Jdk1.8
}
if (name == null) {
// use the parameter index as the name ("0", "1", ...)
// gcode issue #71
name = String.valueOf(map.size());//沒標註解,name就是map的size
}
}
map.put(paramIndex, name);//map每確定一個引數,就會增大一下
}
names = Collections.unmodifiableSortedMap(map);
}
我們接著來看getNamedParams方法引數args 是[1,"Tom"]
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
//引數為null,直接返回
if (args == null || paramCount == 0) {
return null;
} else if (!hasParamAnnotation && paramCount == 1) {
//如果只有一個元素,並且沒有Param註解;args[0]: 單個引數直接返回
return args[names.firstKey()];
} else {
//多個元素或者有Param標註
final Map<String, Object> param = new ParamMap<Object>();
int i = 0;
//map儲存資料,最終是要返回的
//遍歷names,names我們上面講了, 在構造器的時候就確定好了 {0=id,1=lastName}
for (Map.Entry<Integer, String> entry : names.entrySet()) {
//name集合的value作為key;names集合的key又作為取值的參考args[0]:args[1,"Tom"]
//最終的效果: {id=args[0]:1,lastName=args[1]:Tom}
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)
//額外的將每一個引數也儲存到map中,使用新的key:param1...paramN
//效果:有Param註解可以#{指定的key},或者#{param1}
final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
// ensure not to overwrite parameter named with @Param
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
到這裡,就ok了, 轉成param1, value 了. 引數傳遞也就ok啦!!
這是多個引數寫了param的,如果沒有寫的話, param是一個map型別, 那麼key就會是它的序號作為名字了,所以建議多個引數的時候使用param,不然後面如果順序反了,就會錯了.方便動態sql使用吧.
另外還有一個,就是引數如果傳的是list. 只有一個引數, 通常也就不適用param了.
我們來看一下原始碼
if (object instanceof List) {
StrictMap<Object> map = new StrictMap<Object>();
map.put("list", object);
return map;
}
所以取的時候,應該是collection="list"
如果不用param呢,應該怎麼寫:
@Select("select * from t_sign where is_delete=#{arg2} AND status=#{arg0} AND id=#{arg1}")
// SignEntity selectBySignId(@Param("signId") String signId,@Param("status") String status);
SignEntity selectBySignId( String status ,String signId,String delete);
我測試了好幾種,最後得出的結論是: 取值必須得arg[i]來取,因為我們看過是上面的原始碼, 它的key值是索引. 並且取值順序依賴的是:介面引數的順序.
所以如果不用param的話, 我理解的是程式碼可讀性不高,況且太過於依賴引數的順序,不方便維護.
好了好了,就到這裡了