上一篇文章分析到mapper.xml中的sql標簽對應的MappedStatement是如何初始化的,而之前也分析了Mapper接口是如何被加載的,那么問題來了,這兩個是分別加載的到Configuration中的,那么問題來了,在使用過程中MappedStatement又是如何和加載的mapper接口進行關聯的呢?本文將進行分析。
首先還是SqlSession接口的一個方法說起,也就是
<T> T getMapper(Class<T> type);
很顯然這個方法是更加Class名獲取該類的一個實例,而Mapper接口只定義了接口沒有實現類,那么猜想可知返回的應該就是更加mapper.xml生成的實例了。具體是如何實現的呢, 先看下這個方法是如何實現的?
DefaultSqlSession實現該方法的代碼如下:
1 @Override 2 public <T> T getMapper(Class<T> type) { 3 return configuration.<T>getMapper(type, this); 4 }
方法很簡單,調用了Configuration對象的getMapper方法,那么接下來再看下Configuration里面是如何實現的。代碼如下:
1 public <T> T getMapper(Class<T> type, SqlSession sqlSession) { 2 return mapperRegistry.getMapper(type, sqlSession); 3 }
調用了mapperRegistry的getMapper方法,參數分別是Class對象和sqlSession,再繼續看MapperRegistry的實現,代碼如下:
1 @SuppressWarnings("unchecked") 2 public <T> T getMapper(Class<T> type, SqlSession sqlSession) 3 { 4 // 從konwMappers獲取MapperProxyFactory對象 5 final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>)knownMappers.get(type); 6 if (mapperProxyFactory == null) 7 { 8 throw new BindingException("Type " + type + " is not known to the MapperRegistry."); 9 } 10 try 11 { 12 // 通過mapper代理工廠創建新實例 13 return mapperProxyFactory.newInstance(sqlSession); 14 } 15 catch (Exception e) 16 { 17 throw new BindingException("Error getting mapper instance. Cause: " + e, e); 18 } 19 }
可以看出是根據type從knowMappers集合中獲取該mapper的代理工廠類,如何通過該代理工廠新建一個實例。再看下代理工廠是如何創建實例的,代碼如下:
1 @SuppressWarnings("unchecked") 2 protected T newInstance(MapperProxy<T> mapperProxy) { 3 //通過代理獲取mapper接口的新實例 4 return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); 5 } 6 7 public T newInstance(SqlSession sqlSession) { 8 //創建mapper代理對象,調用newInstance方法 9 final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache); 10 return newInstance(mapperProxy); 11 }
那么現在我們就知道是如何根據Mapper.class來獲取Mapper接口的實例的了,不過,到目前為止貌似還是沒有看到和MappedStatement產生聯系啊,別急,再往下看通過代理產生的mapper實例執行具體的方法是如何進行的。代碼如下:
1 @Override 2 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 3 //判斷執行的方法是否是來自父類Object類的方法,也就是如toString、hashCode等方法 4 //如果是則直接通過反射執行該方法,如果不是Object的方法則再往下走,如果不加這個判斷會發生什么呢? 5 //由於mapper接口除了定義的接口方法還包括繼承於Object的方法,如果不加判斷則會繼續往下走,而下面的執行過程是從mapper.xml尋找對應的實現方法, 6 //由於mapper.xml只實現了mapper中的接口方法,而沒有toString和hashCode方法,從而就會導致這些方法無法被實現。 7 if (Object.class.equals(method.getDeclaringClass())) { 8 try { 9 return method.invoke(this, args); 10 } catch (Throwable t) { 11 throw ExceptionUtil.unwrapThrowable(t); 12 } 13 } 14 final MapperMethod mapperMethod = cachedMapperMethod(method); 15 //執行mapperMethod對象的execute方法 16 return mapperMethod.execute(sqlSession, args); 17 } 18 19 private MapperMethod cachedMapperMethod(Method method) { 20 //從緩存中根據method對象獲取MapperMethod對象 21 MapperMethod mapperMethod = methodCache.get(method); 22 if (mapperMethod == null) { 23 //如果mapperMethod為空則新建MapperMethod方法 24 mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()); 25 methodCache.put(method, mapperMethod); 26 } 27 return mapperMethod; 28 }
可以看出代理執行mapper接口的方法會先創建一個MapperMethod對象,然后執行execute方法,代碼如下:
1 public Object execute(SqlSession sqlSession, Object[] args) { 2 Object result; 3 if (SqlCommandType.INSERT == command.getType()) {//如果執行insert命令 4 Object param = method.convertArgsToSqlCommandParam(args);//構建參數 5 result = rowCountResult(sqlSession.insert(command.getName(), param));//調用sqlSession的insert方法 6 } else if (SqlCommandType.UPDATE == command.getType()) {//如果執行update命令 7 Object param = method.convertArgsToSqlCommandParam(args);//構建參數 8 result = rowCountResult(sqlSession.update(command.getName(), param));//調用sqlSession的update方法 9 } else if (SqlCommandType.DELETE == command.getType()) {//如果執行delete命令 10 Object param = method.convertArgsToSqlCommandParam(args);//構建參數 11 result = rowCountResult(sqlSession.delete(command.getName(), param));//調用sqlSession的delete方法 12 } else if (SqlCommandType.SELECT == command.getType()) {//如果執行select命令 13 if (method.returnsVoid() && method.hasResultHandler()) {//判斷接口返回類型,更加返回數據類型執行對應的select語句 14 executeWithResultHandler(sqlSession, args); 15 result = null; 16 } else if (method.returnsMany()) { 17 result = executeForMany(sqlSession, args); 18 } else if (method.returnsMap()) { 19 result = executeForMap(sqlSession, args); 20 } else if (method.returnsCursor()) { 21 result = executeForCursor(sqlSession, args); 22 } else { 23 Object param = method.convertArgsToSqlCommandParam(args); 24 result = sqlSession.selectOne(command.getName(), param); 25 } 26 } else if (SqlCommandType.FLUSH == command.getType()) { 27 result = sqlSession.flushStatements(); 28 } else { 29 throw new BindingException("Unknown execution method for: " + command.getName()); 30 } 31 if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) { 32 throw new BindingException("Mapper method '" + command.getName() 33 + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ")."); 34 } 35 return result; 36 }
可以看出大致的執行過程就是更加MapperMethod的方法類型,然后構建對應的參數,然后執行sqlSession的方法。到現在還是沒有MappedStatement的影子,再看看MapperMethod是被創建的。
1 private final SqlCommand command; 2 private final MethodSignature method; 3 4 public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) { 5 this.command = new SqlCommand(config, mapperInterface, method); 6 this.method = new MethodSignature(config, mapperInterface, method); 7 }
這里涉及到了兩個類SqlCommand和MethodSignature,先從SqlCommand看起。
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 //從configuration中根據接口名獲取MappedStatement對象 6 ms = configuration.getMappedStatement(statementName); 7 } else if (!mapperInterface.equals(method.getDeclaringClass())) { // issue #35 8 //如果該方法不是該mapper接口的方法,則從mapper的父類中找尋該接口對應的MappedStatement對象 9 String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName(); 10 if (configuration.hasStatement(parentStatementName)) { 11 ms = configuration.getMappedStatement(parentStatementName); 12 } 13 } 14 if (ms == null) { 15 if(method.getAnnotation(Flush.class) != null){ 16 name = null; 17 type = SqlCommandType.FLUSH; 18 } else { 19 throw new BindingException("Invalid bound statement (not found): " + statementName); 20 } 21 } else { 22 name = ms.getId();//設置name為MappedStatement的id,而id的值就是xml中對應的sql語句 23 type = ms.getSqlCommandType();//設置type為MappedStatement的sql類型 24 if (type == SqlCommandType.UNKNOWN) { 25 throw new BindingException("Unknown execution method for: " + name); 26 } 27 } 28 }
到這里終於是看到了MappedStatement的身影,根據mapper的Class對象和method對象從Configuration對象中獲取指定的MappedStatement對象,然后根據MappedStatement對象的值初始化SqlCommand對象的屬性。而MethodSignature則是sql語句的簽名,主要作用就是對sql參數與返回結果類型的判斷。
總結:
1、sqlSession調用configuration對象的getMapper方法,configuration調用mapperRegistry的getMapper方法
2、mapperRegistry根據mapper獲取對應的Mapper代理工廠
3、通過mapper代理工廠創建mapper的代理
4、執行mapper方法時,通過代理調用,創建該mapper方法的MapperMethod對象
5、MapperMethod對象的創建是通過從configuration對象中獲取指定的MappedStatement對象來獲取具體的sql語句以及參數和返回結果類型
6、調用sqlSession對應的insert、update、delete和select方法執行mapper的方法
到目前為止知道了mapper接口和mapper.xml是如何進行關聯的了,也知道mapper接口是如何獲取實例的了,也知道了mapper方法最終會調用SqlSession的方法,那么SqlSession又是如何具體去執行每個Sql方法的呢?下一篇繼續分析......