1. 程式人生 > >mybatis--引數傳遞原始碼分析

mybatis--引數傳遞原始碼分析

單元測試:

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

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;
}
看上面的程式碼, Object param=method.convertArgsToSqlCommandParam(args);

這個裡面是: 

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的話, 我理解的是程式碼可讀性不高,況且太過於依賴引數的順序,不方便維護.

好了好了,就到這裡了