在上一篇博客中提到MyBatis是如何實現代理類MapperProxy,並拋出了一個問題——是怎么執行一個具體的sql語句的,在文末中提到了MapperMethod的execute采用命令模式來判斷是何種sql語句,並將具體語句的執行交由SqlSession處理。所以此篇博客正是要講到SqlSession。
在SqlSession接口中包含了所有可能執行的sql語句在這里不一一列舉,請參考org.apache.ibatis.session.SqlSession源碼。DefaultSqlSession是SqlSession的實現類,所以我們重點關注DefaultSqlSession類。在上一篇博客中提到了我們將選擇MapperProxy的executeForMany方法。在此方法中調用了SqlSession.<E>selectList方法。所以我們來看看org.apache.ibatis.session.default.DefaultSqlSession類的selectList方法。
1 //org.apache.ibatis.session.defaults.DefaultSqlSession 2 …… 3 @Override 4 public <E> List<E> selectList(String statement) { 5 return this.selectList(statement, null); 6 } 7 8 @Override 9 public <E> List<E> selectList(String statement, Object parameter) { 10 return this.selectList(statement, parameter, RowBounds.DEFAULT); 11 } 12 13 @Override 14 public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { 15 try { 16 MappedStatement ms = configuration.getMappedStatement(statement); 17 return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); 18 } catch (Exception e) { 19 throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); 20 } finally { 21 ErrorContext.instance().reset(); 22 } 23 } 24 ……
我們看到關於selectList有三個重載方法,最后調用的都是public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds)。在此方法中第一個參數為String類型且命名為statement,第二個參數為Object命名為parameter,回到MapperMethod。
1 //org.apache.ibatis.binding.MapperMethod 2 private <E> Object executeForMany(SqlSession sqlSession, Object[] args) { 3 List<E> result; 4 Object param = method.convertArgsToSqlCommandParam(args); 5 if (method.hasRowBounds()) { 6 RowBounds rowBounds = method.extractRowBounds(args); 7 result = sqlSession.<E>selectList(command.getName(), param, rowBounds); //注意傳遞了什么參數進來 8 } else { 9 result = sqlSession.<E>selectList(command.getName(), param); 10 } 11 // issue #510 Collections & arrays support 12 if (!method.getReturnType().isAssignableFrom(result.getClass())) { 13 if (method.getReturnType().isArray()) { 14 return convertToArray(result); 15 } else { 16 return convertToDeclaredCollection(sqlSession.getConfiguration(), result); 17 } 18 } 19 return result; 20 }
第7行代碼可以看到傳遞了什么參數進來,跟蹤command.getName()是怎么來的。
private final SqlCommand command;
看來是一個叫做SqlCommand的變量,進而我們可以發現這個SqlCommand是MapperMethod的靜態內部類。
1 //org.apache.ibatis.binding.MapperMethod 2 public static class SqlCommand { 3 4 private final String name; 5 private final SqlCommandType type; 6 7 public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) { 8 String statementName = mapperInterface.getName() + "." + method.getName(); //獲取接口+方法名 9 MappedStatement ms = null; //定義一個MappedStatement,這個MapperedStatement稍后介紹 10 if (configuration.hasStatement(statementName)) { //從Configuration對象查找是否有這個方法的全限定名稱 11 ms = configuration.getMappedStatement(statementName); //有,則根據方法的全限定名稱獲取MappedStatement實例 12 } else if (!mapperInterface.equals(method.getDeclaringClass())) { // issue #35//如果沒有在Configuration對象中找到這個方法,則向上父類中獲取全限定方法名 13 String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName(); 14 if (configuration.hasStatement(parentStatementName)) { //在向上的父類查找是否有這個全限定方法名 15 ms = configuration.getMappedStatement(parentStatementName); //有,則根據方法的全限定名稱獲取MappedStatement實例 16 } 17 } 18 if (ms == null) { 19 if(method.getAnnotation(Flush.class) != null){ 20 name = null; 21 type = SqlCommandType.FLUSH; 22 } else { 23 throw new BindingException("Invalid bound statement (not found): " + statementName); 24 } 25 } else { 26 name = ms.getId(); //這個ms.getId,其實就是我們在mapper.xml配置文件中配置一條sql語句時開頭的<select id="……"……,即是接口的該方法名全限定名稱 27 type = ms.getSqlCommandType(); //顯然這是將sql是何種類型(insert、update、delete、select)賦給type 28 if (type == SqlCommandType.UNKNOWN) { 29 throw new BindingException("Unknown execution method for: " + name); 30 } 31 } 32 } 33 34 public String getName() { 35 return name; 36 } 37 38 public SqlCommandType getType() { 39 return type; 40 } 41 }
大致對MapperMethod的解讀到此,再次回到DefaultSqlSession中,到這里可能稍微有點混亂。畫個圖來理解這一段,希望能幫助理解。
這次又一次回到了DefaultSqlSession.selectList。此處不再貼出selectList所有方法,而是只貼出一句,根據接口方法的全限定名稱獲取MappedStatement。
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
在獲取到MappedStatement后,碰到了第一個SqlSession下的四大對象之一:Executor執行器。看來還是沒有講到SqlSession,再放到下一節吧。可以思考下這個MappedStatement是什么意思,望文生義就能猜出個大概。
這是一個能給程序員加buff的公眾號