MyBatis源碼解讀(3)——MapperMethod

來源:互聯網
上載者:User

標籤:但我   nbsp   自己實現   erp   date   catch   dem   rtt   stp   

在前面兩篇的MyBatis源碼解讀中,我們一路跟蹤到了MapperProxy,知道了儘管是使用了動態代理技術使得我們能直接使用介面方法。為鞏固加深動態代理,我們不妨再來回憶一遍何為動態代理。

我相信在初學MyBatis的時候幾乎每個人都會發出一個疑問,為什麼明明是XXXDao介面,我沒有用任何代碼實現這個介面,但卻能直接使用這個介面的方法。現在清楚了,動態代理。我們來寫一個demo小程式來看看。

首先是一個Test.java的介面,只有一個say方法。

 1 package day_16_proxy; 2  3 /** 4  * @author 餘林豐 5  * 6  * 2016年11月16日 7  */ 8 public interface Test { 9     void say();10 }

我們現在想像MyBatis那樣不用實現它而是直接調用。

test.say();

當然不可能直接執行個體化一個介面,此時就需要產生一個代理類TestProxy.java,這個類需實現InvocationHandler介面。

 1 package day_16_proxy; 2  3 import java.lang.reflect.InvocationHandler; 4 import java.lang.reflect.Method; 5  6 /** 7  * @author 餘林豐 8  * 9  * 2016年11月16日10  */11 public class TestProxy implements InvocationHandler {12 13     /* (non-Javadoc)14      * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[])15      */16     @Override17     public Object invoke(Object proxy, Method method, Object[] args)18             throws Throwable {19         if (method.getName().equals("say")){20             System.out.println("hello world");21         }22         return null;23     }24 25 }

需要實現invoke方法,意思就是“調用”的意思(當然我們想要調用介面中的方法時並不會顯示調用invoke)。我們從第19行看到,當調用的方法是say時,輸出“hello world”。有了這個TestProxy.java代理類過後,我們再來用戶端代碼中測試。

 1 package day_16_proxy; 2  3 import java.lang.reflect.Proxy; 4  5 /** 6  * @author 餘林豐 7  * 8  * 2016年11月16日 9  */10 public class Client {11 12     public static void main(String[] args){13         Test test = (Test)Proxy.newProxyInstance(Test.class.getClassLoader(), new Class<?>[]{Test.class}, new TestProxy());14         test.say();15     }16 }

在第14行代碼中,我們已經能夠直接調用Test介面中的say方法了,原因就在於我們通過Proxy.newProxyInstance方法產生了一個代理類執行個體即TestProxy。

回到我們的MyBatis源碼,在上一節中我們知道了一個Dao介面實際上是通過MapperProxyFactory產生了一個MapperProxy代理類。

1 //org.apache.ibatis.binding.MapperProxyFactory2 protected T newInstance(MapperProxy<T> mapperProxy) {3   return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);4 }

第3行代碼是不是和在開頭的例子中用戶端測試代碼如出一轍?組建代理程式類,這個代理類就是MapperProxy。清楚了MyBatis是如何構造出代理類的算是解決了第一個問題——一個介面怎麼能直接調用其方法。

現在拋出第二個問題——介面中每個具體的方法是如何做到一一實現代理的呢?我們再來看看MapperProxy類。這次我們先看MapperProxy類實現的InvocationHanlder.invoke方法。

 1 //org.apache.ibatis.binding.MapperProxy 2 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 3   if (Object.class.equals(method.getDeclaringClass())) { 4     try { 5       return method.invoke(this, args); 6     } catch (Throwable t) { 7       throw ExceptionUtil.unwrapThrowable(t); 8     } 9   }10   final MapperMethod mapperMethod = cachedMapperMethod(method);11   return mapperMethod.execute(sqlSession, args);12 }

第3行做一個判斷,判斷是否是一個類,如果是一個類,那麼久直接傳遞方法和參數調用即可。但我們知道此時是一個介面(也可以自己實現介面,舊版本通常這樣做)。如果不是一個類的話,就會建立一個MapperMethod方法。見名思意:好像就是這個類在執行我們所調用的每一個介面方法。最後返回的是MapperMethod.execute方法。暫時不予理會MapperProxy類中的cachedMapperMethod方法。

來看看MapperMethod類,這個MapperMethod類就不得了了啊,可以說它是統管所有和資料庫打交道的方法(當然概括起來也只有insert、delete、update、select四個方法)。所以,不管你的dao層有多少方法,歸結起來的sql語句都有且僅有只有insert、delete、update、select,可以預料在MapperMethod的execute方法中首先判斷是何種sql語句。

//org.apache.ibatis.binding.MapperMethodpublic Object execute(SqlSession sqlSession, Object[] args) {    switch (command.getType()) {        case INSERT: {……}        case UPDATE: {……}        case DELETE: {……}        case SELECT: {……}        case FLUSH: {……}    }}

這是MepperMethod.execute方法的刪減,我們可以看到確實在execute方法內部首先判斷是何種sql語句。(注意:在閱讀這部分原始碼時,我們的主線是MyBatis是如何建立出一個代理類,以及實現其方法的,而暫時忽略其中的細節)

我們選擇常見的"SELECT"sql語句來進行解讀,而在"SELECT"語句中又會設計到較多的細節問題:

 1 //org.apache.ibatis.binding 2 case SELECT: 3         if (method.returnsVoid() && method.hasResultHandler()) { 4           executeWithResultHandler(sqlSession, args); 5           result = null; 6         } else if (method.returnsMany()) { 7           result = executeForMany(sqlSession, args); 8         } else if (method.returnsMap()) { 9           result = executeForMap(sqlSession, args);10         } else if (method.returnsCursor()) {11           result = executeForCursor(sqlSession, args);12         } else {13           Object param = method.convertArgsToSqlCommandParam(args);14           result = sqlSession.selectOne(command.getName(), param);15         }16         break;

我們選取第7行中的executeForMany中的方法來解讀試試看。

 1 //org.apache.ibatis.binding.MapperMethod 2 private <E> Object executeForMany(SqlSession sqlSession, Object[] args) { 3   List<E> result; 4   Object param = method.convertArgsToSqlCommandParam(args); 5   if (method.hasRowBounds()) { 6     RowBounds rowBounds = method.extractRowBounds(args); 7     result = sqlSession.<E>selectList(command.getName(), param, rowBounds); 8   } else { 9     result = sqlSession.<E>selectList(command.getName(), param);10   }11   // issue #510 Collections & arrays support12   if (!method.getReturnType().isAssignableFrom(result.getClass())) {13     if (method.getReturnType().isArray()) {14       return convertToArray(result);15     } else {16       return convertToDeclaredCollection(sqlSession.getConfiguration(), result);17     }18   }19   return result;20 }

第7行和第9行代碼就是我們真正執行sql語句的地方,原來兜兜轉轉它又回到了sqlSession的方法中。在下一節,我們再重新回到重要的SqlSession中。

 

MyBatis源碼解讀(3)——MapperMethod

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.