mybatis源碼解析8---執行mapper接口方法到執行mapper.xml的sql的過程


上一篇文章分析到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方法的呢?下一篇繼續分析......


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM