Mybatis源碼(3)- MapperProxy與MapperMethod

1.前言

前面<<Mybatis源碼2>>分析了事務(wù)工廠的加載,數(shù)據(jù)源的加載序芦,以及mapper的加載吞彤。那么在加載完成了以后檩淋,我們就開始后續(xù)的步驟拘央。

這里再放一下mybatis查詢的步驟:

        LearnMybatisApplication application = new LearnMybatisApplication();
        // 1.讀取xml內(nèi)容
        @Cleanup InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");

        // 2.讀取配置,生成創(chuàng)建工廠
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        // 3.生成SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();

        // 4.獲取Mapper對象
        IUserMapper mapper = sqlSession.getMapper(IUserMapper.class);

        // 5.調(diào)用方法挠羔,查詢數(shù)據(jù)庫
        List<User> users = mapper.list("jiang");

2.openSession

進入第三步的openSession

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      //通過事務(wù)工廠來產(chǎn)生一個事務(wù)
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //生成一個執(zhí)行器(事務(wù)包含在執(zhí)行器里)
      final Executor executor = configuration.newExecutor(tx, execType);
      //然后產(chǎn)生一個DefaultSqlSession
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      //如果打開事務(wù)出錯墓陈,則關(guān)閉它
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      //最后清空錯誤上下文
      ErrorContext.instance().reset();
    }
  }

這里最終是會生成一個DefaultSqlSession恶守,主要是將TransactionFactoryExecutor組裝到里面,TransactionFactory的話贡必,前面已經(jīng)分析了兔港,而Executor則先不分析這里面的具體實現(xiàn),放一放赊级,等后面介紹到Executor再分析Executor押框。
主要是知道我們獲取到了一個DefaultSqlSession對象。

3.sqlSession.getMapper

IUserMapper mapper = sqlSession.getMapper(IUserMapper.class);這一步會去DefaultSqlSession對象里面獲取IUserMapper的代理對象理逊。
進入getMapper橡伞,可以看到調(diào)用的是configuration的getMapper

  public <T> T getMapper(Class<T> type) {
    //最后會去調(diào)用MapperRegistry.getMapper
    return configuration.<T>getMapper(type, this);
  }

再進入configuration的getMapper,發(fā)現(xiàn)是調(diào)用MapperRegistry.getMapper晋被,那我們直接到MapperRegistry.getMapper

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

這個knownMappers還記得嗎兑徘,就是上篇文章中,根據(jù)IUserMapper類型存入MapperProxyFactory對象的Map羡洛,所以這里就會獲取出MapperProxyFactory挂脑,然后調(diào)用它的newInstance生成代理對象MapperProxy

  protected T newInstance(MapperProxy<T> mapperProxy) {
    //用JDK自帶的動態(tài)代理生成映射器
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

4.調(diào)用IUserMapper的list

在我們獲取到了IUserMapper的代理對象MapperProxy欲侮,然后調(diào)用list方法崭闲,所以自然會進MapperProxy的invoke方法

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //代理以后,所有Mapper的方法調(diào)用時威蕉,都會調(diào)用這個invoke方法
    //并不是任何一個方法都需要執(zhí)行調(diào)用代理對象進行執(zhí)行刁俭,如果這個方法是Object中通用的方法(toString、hashCode等)無需執(zhí)行
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    //這里優(yōu)化了韧涨,去緩存中找MapperMethod
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    //執(zhí)行
    return mapperMethod.execute(sqlSession, args);
  }

首先如果是Object的方法牍戚,則直接調(diào)用,然后返回虑粥,不走下面的邏輯如孝。
然后去獲取MapperMethod這個對象,獲取到了再調(diào)用execute娩贷,所以這個MapperMethod就是這次要分析的核心第晰。
這里是如果緩存中有MapperMethod就從緩存中拿,沒有就會自己創(chuàng)建。

5.MapperMethod

這個類的定義:

public class MapperMethod {

  private final SqlCommand command;
  private final MethodSignature method;

  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, method);
  }

  //執(zhí)行
  public Object execute(SqlSession sqlSession, Object[] args) {
    xxxxxx
  }
// xxxxxx

可以看到也比較簡單茁瘦,就是兩個成員變量SqlCommandMethodSignature罗岖,這個類主要的作用就是:
1.解析我們傳給sql需要的參數(shù),變成一個Map腹躁,傳遞給后面的執(zhí)行器
2.調(diào)用SqlSession的方法
3.對SqlSession方法返回的值簡單的處理一下桑包,用的較少

5.1 MapperMethod的創(chuàng)建

那么先看它的創(chuàng)建,也就是構(gòu)造函數(shù)纺非。

SqlCommand

 public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, method);
  }

這個SqlCommand會保存著statementId哑了,這里是com.learn.mybatis.learnmybatis.mapper.IUserMapper.list,這個后續(xù)也會被用到烧颖,用于定位是哪個MappedStatement弱左。還有保存著一個type就是這個sql是什么類型,比如這里就是SELECT炕淮。
里面的邏輯不復(fù)雜拆火,就不看了。

MethodSignature

這個類定義:

  //方法簽名涂圆,靜態(tài)內(nèi)部類
  public static class MethodSignature {

    private final boolean returnsMany;
    private final boolean returnsMap;
    private final boolean returnsVoid;
    private final Class<?> returnType;
    private final String mapKey;
    private final Integer resultHandlerIndex;
    private final Integer rowBoundsIndex;
    private final SortedMap<Integer, String> params;
    private final boolean hasNamedParameters;
   // xxxxx
}

這個類主要作用:
1.初始化一些對SqlSession方法返回值處理的配置
2.解析@Param们镜,獲取出里面的值傳遞給后面的SqlSession

那我們分析這個是怎么去解析@Param的。
先看MethodSignature的構(gòu)造函數(shù)

    public MethodSignature(Configuration configuration, Method method) {
      this.returnType = method.getReturnType();
      this.returnsVoid = void.class.equals(this.returnType);
      this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray());
      this.mapKey = getMapKey(method);
      this.returnsMap = (this.mapKey != null);
      // 判斷是否有@Param
      this.hasNamedParameters = hasNamedParams(method);
      //以下重復(fù)循環(huán)2遍調(diào)用getUniqueParamIndex润歉,是不是降低效率了
      //記下RowBounds是第幾個參數(shù)
      this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
      //記下ResultHandler是第幾個參數(shù)
      this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
      // 解析@Param
      this.params = Collections.unmodifiableSortedMap(getParams(method, this.hasNamedParameters));
    }

hasNamedParams(method)的代碼:

private boolean hasNamedParams(Method method) {
      boolean hasNamedParams = false;
      final Object[][] paramAnnos = method.getParameterAnnotations();
      for (Object[] paramAnno : paramAnnos) {
        for (Object aParamAnno : paramAnno) {
          if (aParamAnno instanceof Param) {
            //查找@Param模狭,有一個則返回true
            hasNamedParams = true;
            break;
          }
        }
      }
      return hasNamedParams;
    }

遍歷這個方法上面的是否有@Param注解,有就返回true

然后到Collections.unmodifiableSortedMap(getParams(method, this.hasNamedParameters));getParams:

    //得到所有參數(shù)
    private SortedMap<Integer, String> getParams(Method method, boolean hasNamedParameters) {
      //用一個TreeMap,這樣就保證還是按參數(shù)的先后順序
      final SortedMap<Integer, String> params = new TreeMap<Integer, String>();
      final Class<?>[] argTypes = method.getParameterTypes();
      for (int i = 0; i < argTypes.length; i++) {
        //是否不是RowBounds/ResultHandler類型的參數(shù)
        if (!RowBounds.class.isAssignableFrom(argTypes[i]) && !ResultHandler.class.isAssignableFrom(argTypes[i])) {
          //參數(shù)名字默認(rèn)為0,1,2踩衩,這就是為什么xml里面可以用#{1}這樣的寫法來表示參數(shù)了
          String paramName = String.valueOf(params.size());
          if (hasNamedParameters) {
            //還可以用注解@Param來重命名參數(shù)
            paramName = getParamNameFromAnnotation(method, i, paramName);
          }
          params.put(i, paramName);
        }
      }
      return params;
    }

    private String getParamNameFromAnnotation(Method method, int i, String paramName) {
      final Object[] paramAnnos = method.getParameterAnnotations()[i];
      for (Object paramAnno : paramAnnos) {
        if (paramAnno instanceof Param) {
          paramName = ((Param) paramAnno).value();
        }
      }
      return paramName;
    }

這里可以看到getParams里面用了一個SorteMap來存放參數(shù)下標(biāo)和參數(shù)名嚼鹉,這里有一點,如果我們的參數(shù)不使用@Param驱富,那么存在SorteMap的Key就會是數(shù)字從0一直遞增锚赤,值也是從0一直遞增,如果指定了@Param褐鸥,那value就會變成里面@Param里value的值线脚。

舉個例子:
如果我們的Mapper是這樣寫的:

public interface IUserMapper {
    List<User> list(@Param(value = "name") String name);
}

那么存在SorteMap的就會是這樣的Key為0,值為name晶疼。
如果沒有@Param(value = "name")酒贬,那么存在SorteMap的就會是這樣的Key為0又憨,值為0翠霍。

5.2 execute

在創(chuàng)建好了MapperMethod后,就開始調(diào)用execute方法了蠢莺。

 //執(zhí)行
  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    //可以看到執(zhí)行時就是4種情況寒匙,insert|update|delete|select,分別調(diào)用SqlSession的4大類方法
    if (SqlCommandType.INSERT == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.insert(command.getName(), param));
    } else if (SqlCommandType.UPDATE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
    } else if (SqlCommandType.DELETE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
    } else if (SqlCommandType.SELECT == command.getType()) {
      if (method.returnsVoid() && method.hasResultHandler()) {
        //如果有結(jié)果處理器
        executeWithResultHandler(sqlSession, args);
        result = null;
      } else if (method.returnsMany()) {
        //如果結(jié)果有多條記錄
        result = executeForMany(sqlSession, args);
      } else if (method.returnsMap()) {
        //如果結(jié)果是map
        result = executeForMap(sqlSession, args);
      } else {
        //否則就是一條記錄
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
      }
    } else {
      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;
  }

這里就對應(yīng)著增刪改查操作,我們這里是select锄弱,并且返回的結(jié)果是List考蕾,那么就會進入executeForMany

 //多條記錄
  private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    Object param = method.convertArgsToSqlCommandParam(args);
    //代入RowBounds
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
    } else {
      result = sqlSession.<E>selectList(command.getName(), param);
    }
    // issue #510 Collections & arrays support
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
      if (method.getReturnType().isArray()) {
        return convertToArray(result);
      } else {
        return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
      }
    }
    return result;
  }

這里關(guān)鍵就是:
1.MethodSignature的convertArgsToSqlCommandParam
2.sqlSession.selectList

那這個convertArgsToSqlCommandParam干了什么呢会宪?

  public Object convertArgsToSqlCommandParam(Object[] args) {
      final int paramCount = params.size();
      if (args == null || paramCount == 0) {
        //如果沒參數(shù)
        return null;
      } else if (!hasNamedParameters && paramCount == 1) {
        //如果只有一個參數(shù)
        return args[params.keySet().iterator().next().intValue()];
      } else {
        //否則肖卧,返回一個ParamMap,修改參數(shù)名掸鹅,參數(shù)名就是其位置
        final Map<String, Object> param = new ParamMap<Object>();
        int i = 0;
        for (Map.Entry<Integer, String> entry : params.entrySet()) {
          //1.先加一個#{0},#{1},#{2}...參數(shù)
          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)) {
            //2.再加一個#{param1},#{param2}...參數(shù)
            //你可以傳遞多個參數(shù)給一個映射器方法塞帐。如果你這樣做了,
            //默認(rèn)情況下它們將會以它們在參數(shù)列表中的位置來命名,比如:#{param1},#{param2}等。
            //如果你想改變參數(shù)的名稱(只在多參數(shù)情況下) ,那么你可以在參數(shù)上使用@Param(“paramName”)注解巍沙。
            param.put(genericParamName, args[entry.getKey()]);
          }
          i++;
        }
        return param;
      }
    }

上面的代碼就可以發(fā)現(xiàn)葵姥,它根據(jù)之前的params,也就是之前的SorteMap:

0 -> 'name'

遍歷這個map句携,然后獲取第一個傳入的參數(shù)榔幸,由于我們是mapper.list("jiang");調(diào)用的,所以這個參數(shù)就會是jiang矮嫉,
它首先將name -> jiang加入削咆,然后再加入一個param1 - jiang,然后返回這個param蠢笋。
所以我們得到一個這樣的Map:

image.png

那么獲取到了這個param后态辛,就是我們的最后一步

result = sqlSession.<E>selectList(command.getName(), param);

這里的commond.getName就是com.learn.mybatis.learnmybatis.mapper.IUserMapper.list,然后param就是我們剛剛convertArgsToSqlCommandParam的返回值挺尿。

這樣MapperMethod就將我們傳遞的參數(shù)封裝好奏黑,并且將statementId一并傳入到SqlSession的selectList中,后面就可以獲取到對應(yīng)的MappedStatement编矾,以及sql的參數(shù)熟史,去Mysql中查詢

6.總結(jié)

**1.DefaultSqlSession通過getMapper,去MapperRegistry的knownMappers中獲取出了Mapper的代理對象工廠MapperProxyFactory窄俏,然后創(chuàng)建出了MapperProxy

2.MapperMethod對象初始化的時候構(gòu)建了SqlCommand以及MethodSignature蹂匹,分別是存儲了statementId也就是com.learn.mybatis.learnmybatis.mapper.IUserMapper.list,以及將傳入的參數(shù)根據(jù)@Param看解析轉(zhuǎn)成Map凹蜈,例如:[name: "jiang"]限寞,這里用json表示一下

3.將com.learn.mybatis.learnmybatis.mapper.IUserMapper.list以及參數(shù)傳給了sqlSession的selectList,然后開始后續(xù)的流程**

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末仰坦,一起剝皮案震驚了整個濱河市履植,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌悄晃,老刑警劉巖玫霎,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡庶近,警方通過查閱死者的電腦和手機翁脆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鼻种,“玉大人反番,你說我怎么就攤上這事〔嬖浚” “怎么了恬口?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長沼侣。 經(jīng)常有香客問我祖能,道長,這世上最難降的妖魔是什么蛾洛? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任养铸,我火速辦了婚禮,結(jié)果婚禮上轧膘,老公的妹妹穿的比我還像新娘钞螟。我一直安慰自己,他們只是感情好谎碍,可當(dāng)我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布鳞滨。 她就那樣靜靜地躺著,像睡著了一般蟆淀。 火紅的嫁衣襯著肌膚如雪拯啦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天熔任,我揣著相機與錄音褒链,去河邊找鬼。 笑死疑苔,一個胖子當(dāng)著我的面吹牛甫匹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播惦费,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼兵迅,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了薪贫?” 一聲冷哼從身側(cè)響起恍箭,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎后雷,沒想到半個月后季惯,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡臀突,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年勉抓,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片候学。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡藕筋,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出梳码,到底是詐尸還是另有隱情隐圾,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布掰茶,位于F島的核電站暇藏,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏濒蒋。R本人自食惡果不足惜盐碱,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望沪伙。 院中可真熱鬧瓮顽,春花似錦、人聲如沸围橡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽翁授。三九已至拣播,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間收擦,已是汗流浹背诫尽。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留炬守,地道東北人牧嫉。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像减途,于是被迫代替她去往敵國和親酣藻。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容