MyBatis框架是如何去執(zhí)行SQL語句?相信不只是你們,筆者也想要知道是如何進(jìn)行的。相信有上一章的引導(dǎo)大家都知道SqlSession接口的作用。當(dāng)然默認(rèn)情況下還是使用DefaultSqlSession類。關(guān)于SqlSession接口的用法有很多種。筆者還是比較喜歡用getMapper方法。對于getMapper方法的實(shí)現(xiàn)方式。筆者不能下一個(gè)定論。筆者只是想表示一下自己的理解而以——?jiǎng)討B(tài)代理。

筆者把關(guān)于getMapper方法的實(shí)現(xiàn)方式理解為動(dòng)態(tài)代理。事實(shí)上筆者還想說他可以是一個(gè)AOP思想的實(shí)現(xiàn)。那么具體是一個(gè)什么樣子?xùn)|西。相信筆者說了也不能代表什么。一切還是有大家自己去查看和理解。從源碼上我們可以看到getMapper方法會(huì)去調(diào)用Configuration類的getMapper方法。好了。一切的開始都在這里了。

DefaultSqlSession類:

 public <T> T getMapper(Class<T> type) {    return configuration.<T>getMapper(type, this);
  }

對于Configuration類上一章里面就說明他里面存放了所有關(guān)于XML文件的配置信息。從參數(shù)上我們可以看到他要我們傳入一個(gè)Class<T>類型。這已經(jīng)可以看到后面一定要用到反射機(jī)制和動(dòng)態(tài)生成相應(yīng)的類實(shí)例。讓我們進(jìn)一步查看一下源碼。

Configuration類:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {    return mapperRegistry.getMapper(type, sqlSession);
  }

當(dāng)筆者點(diǎn)擊進(jìn)來發(fā)現(xiàn)他又調(diào)用MapperRegistry類的getMapper方法的時(shí)候,心里面有一種又恨又愛的沖動(dòng)——這就是構(gòu)架之美和復(fù)雜之恨。MapperRegistry類筆者把他理解存放動(dòng)態(tài)代理工廠(MapperProxyFactory類)的庫存。當(dāng)然我們還是進(jìn)去看一看源碼吧。

MapperRegistry類:

萬碼學(xué)堂,電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

 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   }

萬碼學(xué)堂,電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

好了。筆者相信大家看到這一段代碼的時(shí)候都明白——MapperRegistry類就是用來存放MapperProxyFactory類的。我們還是在看一下knownMappers成員是一個(gè)什么要樣子的集合類型。

private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();

knownMappers是一個(gè)字典類型。從Key的類型上我們可以判斷出來是一個(gè)類一個(gè)動(dòng)態(tài)代理工廠。筆者看到這里的時(shí)候都會(huì)去點(diǎn)擊一個(gè)MapperProxyFactory類的源碼。看看他里面又是一些什么東東。

萬碼學(xué)堂,電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

 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 }

萬碼學(xué)堂,電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

還好。代碼不是很多,理解起來也不是很復(fù)雜。略看一下源碼,筆者做了一個(gè)很大膽的猜測——一個(gè)類,一個(gè)動(dòng)態(tài)代理工廠,多個(gè)方法代理。我們先把猜測放在這里,然后讓我們回到上面部分吧。我們發(fā)現(xiàn)MapperRegistry類的getMapper方法里面最后會(huì)去調(diào)用MapperProxyFactory類的newInstance方法。這個(gè)時(shí)候我們又看到他實(shí)例化了一個(gè)MapperProxy類。MapperProxy類是什么。這個(gè)就關(guān)系到Proxy類的用法了。所以讀者們自己去查看相關(guān)資料了。意思明顯每執(zhí)行一次XxxMapper(例如:筆者例子里面的IProductMapper接口)的方法都會(huì)創(chuàng)建一個(gè)MapperProxy類。方法執(zhí)行之前都會(huì)先去調(diào)用相應(yīng)MapperProxy類里面的invoke方法。如下

MapperProxy類:

萬碼學(xué)堂,電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

 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   }

萬碼學(xué)堂,電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

從源碼的意思:從緩存中獲得執(zhí)行方法對應(yīng)的MapperMethod類實(shí)例。如果MapperMethod類實(shí)例不存在的情況,創(chuàng)建加入緩存并返回相關(guān)的實(shí)例。最后調(diào)用MapperMethod類的execute方法。

到這里筆者小結(jié)一下,上面講到筆者例子里面用到的getMapper方法。getMapper方法就是用來獲得相關(guān)的數(shù)據(jù)操作類接口。而事實(shí)數(shù)據(jù)操作類邦定了動(dòng)態(tài)代理。所以操據(jù)操作類執(zhí)行方法的時(shí)候,會(huì)觸動(dòng)每個(gè)方法相應(yīng)的MapperProxy類的invoke方法。所以invoke方法返回的結(jié)果就是操據(jù)操作類執(zhí)行方法的結(jié)果。這樣子我們就知道最后的任務(wù)交給了MapperMethod類實(shí)例。

MapperMethod類里面有倆個(gè)成員:SqlCommand類和MethodSignature類。從名字上我們大概的能想到一個(gè)可能跟SQL語句有關(guān)系,一個(gè)可能跟要執(zhí)行的方法有關(guān)系。事實(shí)也是如此。筆者查看了SqlCommand類的源碼。確切來講這一部分的內(nèi)容跟XxxMapper的XML配置文件里面的select節(jié)點(diǎn)、delete節(jié)點(diǎn)等有關(guān)。我們都會(huì)知道節(jié)點(diǎn)上有id屬性值。那么MyBatis框架會(huì)把每一個(gè)節(jié)點(diǎn)(如:select節(jié)點(diǎn)、delete節(jié)點(diǎn))生成一個(gè)MappedStatement類。要找到MappedStatement類就必須通過id來獲得。有一個(gè)細(xì)節(jié)要注意:代碼用到的id = 當(dāng)前接口類 + XML文件的節(jié)點(diǎn)的ID屬性。如下

萬碼學(xué)堂,電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

 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     }

萬碼學(xué)堂,電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

看到這里的時(shí)候,我們就可以回頭去找一找在什么時(shí)候增加了MappedStatement類。上面之所以可以執(zhí)行是建立在XML配置信息都被加載進(jìn)來了。所以MappedStatement類也一定是在加載配置的時(shí)候就進(jìn)行的。請讀者們自行查看一下MapperBuilderAssistant類的addMappedStatement方法——加深理解。SqlCommand類的name成員和type成員我們還是關(guān)注一下。name成員就是節(jié)點(diǎn)的ID,type成員表示查尋還是更新或是刪除。至于MethodSignature類呢。他用于說明方法的一些信息,主要有返回信息。

筆者上面講了這多一點(diǎn)主要是為了查看execute方法源碼容易一點(diǎn)。因?yàn)閑xecute方法都要用到SqlCommand類和MethodSignature類。

萬碼學(xué)堂,電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

 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   }

萬碼學(xué)堂,電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

重點(diǎn)部分就是這里,我們會(huì)發(fā)現(xiàn)我們轉(zhuǎn)了一圈,最后還是要回到SqlSession接口實(shí)例上。完美的一圈!筆者用紅色標(biāo)出來了。

看到了這里我們就清楚調(diào)頭去看一下SqlSession接口實(shí)例吧。