MyBatis框架是如何去執(zhí)行SQL語句?相信不只是你們,筆者也想要知道是如何進行的。相信有上一章的引導(dǎo)大家都知道SqlSession接口的作用。當(dāng)然默認(rèn)情況下還是使用DefaultSqlSession類。關(guān)于SqlSession接口的用法有很多種。筆者還是比較喜歡用getMapper方法。對于getMapper方法的實現(xiàn)方式。筆者不能下一個定論。筆者只是想表示一下自己的理解而以——動態(tài)代理。
筆者把關(guān)于getMapper方法的實現(xiàn)方式理解為動態(tài)代理。事實上筆者還想說他可以是一個AOP思想的實現(xiàn)。那么具體是一個什么樣子?xùn)|西。相信筆者說了也不能代表什么。一切還是有大家自己去查看和理解。從源碼上我們可以看到getMapper方法會去調(diào)用Configuration類的getMapper方法。好了。一切的開始都在這里了。
DefaultSqlSession類:
public <T> T getMapper(Class<T> type) { return configuration.<T>getMapper(type, this); }
對于Configuration類上一章里面就說明他里面存放了所有關(guān)于XML文件的配置信息。從參數(shù)上我們可以看到他要我們傳入一個Class<T>類型。這已經(jīng)可以看到后面一定要用到反射機制和動態(tài)生成相應(yīng)的類實例。讓我們進一步查看一下源碼。
Configuration類:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); }
當(dāng)筆者點擊進來發(fā)現(xiàn)他又調(diào)用MapperRegistry類的getMapper方法的時候,心里面有一種又恨又愛的沖動——這就是構(gòu)架之美和復(fù)雜之恨。MapperRegistry類筆者把他理解存放動態(tài)代理工廠(MapperProxyFactory類)的庫存。當(dāng)然我們還是進去看一看源碼吧。
MapperRegistry類:
1 public <T> T getMapper(Class<T> type, SqlSession sqlSession) { 2 final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); 3 if (mapperProxyFactory == null) { 4 throw new BindingException("Type " + type + " is not known to the MapperRegistry."); 5 } 6 try { 7 return mapperProxyFactory.newInstance(sqlSession); 8 } catch (Exception e) { 9 throw new BindingException("Error getting mapper instance. Cause: " + e, e);10 }11 }
好了。筆者相信大家看到這一段代碼的時候都明白——MapperRegistry類就是用來存放MapperProxyFactory類的。我們還是在看一下knownMappers成員是一個什么要樣子的集合類型。
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
knownMappers是一個字典類型。從Key的類型上我們可以判斷出來是一個類一個動態(tài)代理工廠。筆者看到這里的時候都會去點擊一個MapperProxyFactory類的源碼。看看他里面又是一些什么東東。
1 public class MapperProxyFactory<T> { 2 3 private final Class<T> mapperInterface; 4 private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>(); 5 6 public MapperProxyFactory(Class<T> mapperInterface) { 7 this.mapperInterface = mapperInterface; 8 } 9 10 public Class<T> getMapperInterface() {11 return mapperInterface;12 }13 14 public Map<Method, MapperMethod> getMethodCache() {15 return methodCache;16 }17 18 @SuppressWarnings("unchecked")19 protected T newInstance(MapperProxy<T> mapperProxy) {20 return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);21 }22 23 public T newInstance(SqlSession sqlSession) {24 final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);25 return newInstance(mapperProxy);26 }27 28 }
還好。代碼不是很多,理解起來也不是很復(fù)雜。略看一下源碼,筆者做了一個很大膽的猜測——一個類,一個動態(tài)代理工廠,多個方法代理。我們先把猜測放在這里,然后讓我們回到上面部分吧。我們發(fā)現(xiàn)MapperRegistry類的getMapper方法里面最后會去調(diào)用MapperProxyFactory類的newInstance方法。這個時候我們又看到他實例化了一個MapperProxy類。MapperProxy類是什么。這個就關(guān)系到Proxy類的用法了。所以讀者們自己去查看相關(guān)資料了。意思明顯每執(zhí)行一次XxxMapper(例如:筆者例子里面的IProductMapper接口)的方法都會創(chuàng)建一個MapperProxy類。方法執(zhí)行之前都會先去調(diào)用相應(yīng)MapperProxy類里面的invoke方法。如下
MapperProxy類:
1 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 2 if (Object.class.equals(method.getDeclaringClass())) { 3 try { 4 return method.invoke(this, args); 5 } catch (Throwable t) { 6 throw ExceptionUtil.unwrapThrowable(t); 7 } 8 } 9 final MapperMethod mapperMethod = cachedMapperMethod(method);10 return mapperMethod.execute(sqlSession, args);11 }
從源碼的意思:從緩存中獲得執(zhí)行方法對應(yīng)的MapperMethod類實例。如果MapperMethod類實例不存在的情況,創(chuàng)建加入緩存并返回相關(guān)的實例。最后調(diào)用MapperMethod類的execute方法。
到這里筆者小結(jié)一下,上面講到筆者例子里面用到的getMapper方法。getMapper方法就是用來獲得相關(guān)的數(shù)據(jù)操作類接口。而事實數(shù)據(jù)操作類邦定了動態(tài)代理。所以操據(jù)操作類執(zhí)行方法的時候,會觸動每個方法相應(yīng)的MapperProxy類的invoke方法。所以invoke方法返回的結(jié)果就是操據(jù)操作類執(zhí)行方法的結(jié)果。這樣子我們就知道最后的任務(wù)交給了MapperMethod類實例。
MapperMethod類里面有倆個成員:SqlCommand類和MethodSignature類。從名字上我們大概的能想到一個可能跟SQL語句有關(guān)系,一個可能跟要執(zhí)行的方法有關(guān)系。事實也是如此。筆者查看了SqlCommand類的源碼。確切來講這一部分的內(nèi)容跟XxxMapper的XML配置文件里面的select節(jié)點、delete節(jié)點等有關(guān)。我們都會知道節(jié)點上有id屬性值。那么MyBatis框架會把每一個節(jié)點(如:select節(jié)點、delete節(jié)點)生成一個MappedStatement類。要找到MappedStatement類就必須通過id來獲得。有一個細(xì)節(jié)要注意:代碼用到的id = 當(dāng)前接口類 + XML文件的節(jié)點的ID屬性。如下
1 public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) { 2 String statementName = mapperInterface.getName() + "." + method.getName(); 3 MappedStatement ms = null; 4 if (configuration.hasStatement(statementName)) { 5 ms = configuration.getMappedStatement(statementName); 6 } else if (!mapperInterface.equals(method.getDeclaringClass())) { // issue #35 7 String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName(); 8 if (configuration.hasStatement(parentStatementName)) { 9 ms = configuration.getMappedStatement(parentStatementName);10 }11 }12 if (ms == null) {13 if(method.getAnnotation(Flush.class) != null){14 name = null;15 type = SqlCommandType.FLUSH;16 } else {17 throw new BindingException("Invalid bound statement (not found): " + statementName);18 }19 } else {20 name = ms.getId();21 type = ms.getSqlCommandType();22 if (type == SqlCommandType.UNKNOWN) {23 throw new BindingException("Unknown execution method for: " + name);24 }25 }26 }
看到這里的時候,我們就可以回頭去找一找在什么時候增加了MappedStatement類。上面之所以可以執(zhí)行是建立在XML配置信息都被加載進來了。所以MappedStatement類也一定是在加載配置的時候就進行的。請讀者們自行查看一下MapperBuilderAssistant類的addMappedStatement方法——加深理解。SqlCommand類的name成員和type成員我們還是關(guān)注一下。name成員就是節(jié)點的ID,type成員表示查尋還是更新或是刪除。至于MethodSignature類呢。他用于說明方法的一些信息,主要有返回信息。
筆者上面講了這多一點主要是為了查看execute方法源碼容易一點。因為execute方法都要用到SqlCommand類和MethodSignature類。
1 public Object execute(SqlSession sqlSession, Object[] args) { 2 Object result; 3 switch (command.getType()) { 4 case INSERT: { 5 Object param = method.convertArgsToSqlCommandParam(args); 6 result = rowCountResult(sqlSession.insert(command.getName(), param)); 7 break; 8 } 9 case UPDATE: {10 Object param = method.convertArgsToSqlCommandParam(args);11 result = rowCountResult(sqlSession.update(command.getName(), param));12 break;13 }14 case DELETE: {15 Object param = method.convertArgsToSqlCommandParam(args);16 result = rowCountResult(sqlSession.delete(command.getName(), param));17 break;18 }19 case SELECT:20 if (method.returnsVoid() && method.hasResultHandler()) {21 executeWithResultHandler(sqlSession, args);22 result = null;23 } else if (method.returnsMany()) {24 result = executeForMany(sqlSession, args);25 } else if (method.returnsMap()) {26 result = executeForMap(sqlSession, args);27 } else if (method.returnsCursor()) {28 result = executeForCursor(sqlSession, args);29 } else {30 Object param = method.convertArgsToSqlCommandParam(args);31 result = sqlSession.selectOne(command.getName(), param);32 }33 break;34 case FLUSH:35 result = sqlSession.flushStatements();36 break;37 default:38 throw new BindingException("Unknown execution method for: " + command.getName());39 }40 if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {41 throw new BindingException("Mapper method '" + command.getName() 42 + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");43 }44 return result;45 }
重點部分就是這里,我們會發(fā)現(xiàn)我們轉(zhuǎn)了一圈,最后還是要回到SqlSession接口實例上。完美的一圈!筆者用紅色標(biāo)出來了。
看到了這里我們就清楚調(diào)頭去看一下SqlSession接口實例吧。
http://www.cnblogs.com/hayasi/p/6361067.html