Mybatis之結果處理器

前言

在上文Mybatis之方法如何映射到XML中講到須要實例化SqlCommand和MethodSignature兩個類,在MethodSignature初始化的時候有一個resultHandlerIndex的參數用來指定是否設置了ResultHandler參數,本文將重點ResultHandler如何使用,分析如何觸發的以及如何自定義結果處理器。java

使用結果處理器

首先看一下Mybatis提供的結果處理器接口類ResultHandler:git

public interface ResultHandler<T> {

  void handleResult(ResultContext<? extends T> resultContext);

}

仍是比較簡單的,就只有一個handleResult的方法,方法參數是ResultContext裏面存放了正常執行sql的結果,這裏的結果處理器其實就是對結果進行二次加工處理;Mybatis提供了兩個默認的處理器分別是:DefaultResultHandler和DefaultMapResultHandler,分別處理list和map,下面看一下是如何使用這兩個處理器的:github

public static void selectHandler(SqlSession session, Configuration configuration) {
        BlogMapper mapper = session.getMapper(BlogMapper.class);
        //DefaultResultHandler內置結果處理器
        DefaultResultHandler defaultHandler = new DefaultResultHandler();
        mapper.selectBlogsByHandler("zhaohui", defaultHandler);
        System.out.println(defaultHandler.getResultList());

        //DefaultMapResultHandler內置結果處理器
        DefaultMapResultHandler<Long, Blog> defaultMapResultHandler = new DefaultMapResultHandler<Long, Blog>("id",
                configuration.getObjectFactory(), configuration.getObjectWrapperFactory(),
                configuration.getReflectorFactory());
        mapper.selectBlogsByHandler("zhaohui", defaultMapResultHandler);
        System.out.println(defaultMapResultHandler.getMappedResults());
    }

上面的實例分別使用了兩個內置處理器,分別處理獲取到的相同結果;map處理器指定了id做爲key;下面再看一下BlogMapper中的selectBlogsByHandler:sql

public void selectBlogsByHandler(String author, ResultHandler handler);

<select id="selectBlogsByHandler" parameterType="string" resultType="blog">
        select * from blog where author = #{author}
</select>

上面惟一要注意的點就是selectBlogsByHandler的返回值void類型,否則結果處理器是不會觸發處理結果的,這個後面再分析爲何;看一下輸出結果:segmentfault

[Blog [id=159, title=hello java, author=zhaohui, content=hello java666], Blog [id=160, title=hello java, author=zhaohui, content=hello java666]]
{160=Blog [id=160, title=hello java, author=zhaohui, content=hello java666], 159=Blog [id=159, title=hello java, author=zhaohui, content=hello java666]}

處理流程

首先咱們須要瞭解結果處理器在什麼狀況下才會被觸發,其次再看何時被調用的,最後咱們在分析一下內置的處理器何時被使用的;session

1.觸發條件

上一節中提到須要指定void返回類型,主要緣由能夠查看MapperMethod的execute方法,MapperMethod在Mybatis之方法如何映射到XML中是有介紹的,重點講到了SqlCommand和MethodSignature類,下面看一下execute方法部分代碼:mybatis

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      ...省略...
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } 
      ...省略...
    return result;
  }

能夠看到什麼狀況下才會調用結果處理器有三個要求
1.首先必須是查詢命令,其餘的增刪改是沒有結果處理器的;
2.其次是方法的返回值必須是void,這其實也好理解,若是自己方法就有返回值,結果處理器也有返回值,反而容易搞混;
3.必需要有結果處理器,這個確定是必須的,沒有也別談結果處理器了。app

2.什麼時候調用

具體什麼時候調用咱們本身的結果處理器,相關處理主要在DefaultResultSetHandler中,也就是在處理結果映射的時候,更多能夠參考Mybatis之XML如何映射到方法,在主方法handleResultSets中調用的handleResultSet方法:ide

private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
    try {
      if (parentMapping != null) {
        handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
      } else {
        if (resultHandler == null) {
          DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
          handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
          multipleResults.add(defaultResultHandler.getResultList());
        } else {
          handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
        }
      }
    } finally {
      // issue #228 (close resultsets)
      closeResultSet(rsw.getResultSet());
    }
  }

重點看一下沒有指定parentMapping的狀況下,若是沒有指定resultHandler則系統會建立一個默認的DefaultResultHandler,若是有則用用戶本身的處理器;而後就是經過對象工廠建立對象,而後經過類型處理器讀取ResultSet,最後調用callResultHandler方法來調用處理器:測試

private void callResultHandler(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue) {
    resultContext.nextResultObject(rowValue);
    ((ResultHandler<Object>) resultHandler).handleResult(resultContext);
  }

把封裝好數據的對象rowValue放入resultContext中,而後傳入處理器中進行處理;因此須要注意的是若是有多條記錄實際上是一條一條傳入結果處理器進行處理的,並非生成一個list而後交給ResultHandler處理,可是內置的Map結果集卻不是這樣處理的,接下來重點看一下Mybatis內置的兩個處理器;

3.內置結果處理器

3.1 DefaultResultHandler
內置了一個ArrayList<Object>對象,能夠簡單的理解爲將結果集放入list中;可是上面咱們也看到DefaultResultHandler並非給用戶使用的,而是Mybatis本身使用的,在用戶沒有指定處理器,Mybatis會本身建立一個DefaultResultHandler,而這個處理器是一個局部對象,每次getResultList以後其實仍是放在了一個multipleResults中:

public List<Object> handleResultSets(Statement stmt) throws SQLException {
    final List<Object> multipleResults = new ArrayList<Object>();
    ...處理ResultSet...

這裏定義的multipleResults是否能夠替換成DefaultResultHandler,感受會更加合理;

3.2 DefaultMapResultHandler
內置了一個HashMap對象,此類也是被Mybatis內部使用在處理結果集是Map時使用,具體代碼以下:

public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
    final List<? extends V> list = selectList(statement, parameter, rowBounds);
    final DefaultMapResultHandler<K, V> mapResultHandler = new DefaultMapResultHandler<K, V>(mapKey,
        configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory());
    final DefaultResultContext<V> context = new DefaultResultContext<V>();
    for (V o : list) {
      context.nextResultObject(o);
      mapResultHandler.handleResult(context);
    }
    return mapResultHandler.getMappedResults();
  }

這裏首先獲取結果集list,而後遍歷結果集經過DefaultMapResultHandler轉成Map結果集;這裏我的感受能夠直接把DefaultMapResultHandler做爲參數傳入selectList中,這樣最後就無需再次遍歷一遍;

自定義結果處理器

自定義一個結果處理器也很簡單,實現ResultHandler接口便可,好比下面的Map處理器:

public class MyResultHandler implements ResultHandler<Blog> {

    Map<Long, Blog> result = new HashMap<Long, Blog>();

    @Override
    public void handleResult(ResultContext<? extends Blog> resultContext) {
        Blog blog = resultContext.getResultObject();
        System.out.println(blog.toString());
        result.put(blog.getId(), blog);
    }

    public Map<Long, Blog> getResult() {
        return result;
    }

}

簡單測試一下,一樣查詢Blog,以下所示:

public static void selectMyHandler(SqlSession session) {
        BlogMapper mapper = session.getMapper(BlogMapper.class);
        MyResultHandler handler = new MyResultHandler();
        mapper.selectBlogsByHandler("zhaohui", handler);
        System.out.println(handler.getResult());
    }

日誌輸出以下:

Blog [id=159, title=hello java, author=zhaohui, content=hello java666]
Blog [id=160, title=hello java, author=zhaohui, content=hello java666]
{160=Blog [id=160, title=hello java, author=zhaohui, content=hello java666], 159=Blog [id=159, title=hello java, author=zhaohui, content=hello java666]}

能夠發現分別打印了兩次Blog,由於每次生成一個Blog對象都會調用一次handleResult;

總結

本文首先介紹瞭如何使用結果處理器,而後引出什麼狀況下才能觸發處理器須要有三個條件,以及Mybatis內置的兩個處理器分別處理list和map,最後自定義了一個簡單的結果處理器。

示例代碼地址

Github

相關文章
相關標籤/搜索