MyBatis框架是如何去執行SQL語句?相信不只是你們,筆者也想要知道是如何進行的。相信有上一章的引導大家都知道SqlSession接口的作用。當然默認情況下還是使用DefaultSqlSession類。關於SqlSession接口的用法有很多種。筆者還是比較喜歡用getMapper方法。對於getMapper方法的實現方式。筆者不能下一個定論。筆者只是想表示一下自己的理解而以——動態代理。
筆者把關於getMapper方法的實現方式理解為動態代理。事實上筆者還想說他可以是一個AOP思想的實現。那么具體是一個什么樣子東西。相信筆者說了也不能代表什么。一切還是有大家自己去查看和理解。從源碼上我們可以看到getMapper方法會去調用Configuration類的getMapper方法。好了。一切的開始都在這里了。
DefaultSqlSession類:
public <T> T getMapper(Class<T> type) { return configuration.<T>getMapper(type, this); }
對於Configuration類上一章里面就說明他里面存放了所有關於XML文件的配置信息。從參數上我們可以看到他要我們傳入一個Class<T>類型。這已經可以看到后面一定要用到反射機制和動態生成相應的類實例。讓我們進一步查看一下源碼。
Configuration類:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); }
當筆者點擊進來發現他又調用MapperRegistry類的getMapper方法的時候,心里面有一種又恨又愛的沖動——這就是構架之美和復雜之恨。MapperRegistry類筆者把他理解存放動態代理工廠(MapperProxyFactory類)的庫存。當然我們還是進去看一看源碼吧。
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的類型上我們可以判斷出來是一個類一個動態代理工廠。筆者看到這里的時候都會去點擊一個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 }
還好。代碼不是很多,理解起來也不是很復雜。略看一下源碼,筆者做了一個很大膽的猜測——一個類,一個動態代理工廠,多個方法代理。我們先把猜測放在這里,然后讓我們回到上面部分吧。我們發現MapperRegistry類的getMapper方法里面最后會去調用MapperProxyFactory類的newInstance方法。這個時候我們又看到他實例化了一個MapperProxy類。MapperProxy類是什么。這個就關系到Proxy類的用法了。所以讀者們自己去查看相關資料了。意思明顯每執行一次XxxMapper(例如:筆者例子里面的IProductMapper接口)的方法都會創建一個MapperProxy類。方法執行之前都會先去調用相應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 }
從源碼的意思:從緩存中獲得執行方法對應的MapperMethod類實例。如果MapperMethod類實例不存在的情況,創建加入緩存並返回相關的實例。最后調用MapperMethod類的execute方法。
到這里筆者小結一下,上面講到筆者例子里面用到的getMapper方法。getMapper方法就是用來獲得相關的數據操作類接口。而事實數據操作類邦定了動態代理。所以操據操作類執行方法的時候,會觸動每個方法相應的MapperProxy類的invoke方法。所以invoke方法返回的結果就是操據操作類執行方法的結果。這樣子我們就知道最后的任務交給了MapperMethod類實例。
MapperMethod類里面有倆個成員:SqlCommand類和MethodSignature類。從名字上我們大概的能想到一個可能跟SQL語句有關系,一個可能跟要執行的方法有關系。事實也是如此。筆者查看了SqlCommand類的源碼。確切來講這一部分的內容跟XxxMapper的XML配置文件里面的select節點、delete節點等有關。我們都會知道節點上有id屬性值。那么MyBatis框架會把每一個節點(如:select節點、delete節點)生成一個MappedStatement類。要找到MappedStatement類就必須通過id來獲得。有一個細節要注意:代碼用到的id = 當前接口類 + XML文件的節點的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類。上面之所以可以執行是建立在XML配置信息都被加載進來了。所以MappedStatement類也一定是在加載配置的時候就進行的。請讀者們自行查看一下MapperBuilderAssistant類的addMappedStatement方法——加深理解。SqlCommand類的name成員和type成員我們還是關注一下。name成員就是節點的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 }
重點部分就是這里,我們會發現我們轉了一圈,最后還是要回到SqlSession接口實例上。完美的一圈!筆者用紅色標出來了。
看到了這里我們就清楚調頭去看一下SqlSession接口實例吧。