接着深入源碼分析mybatis查詢原理(二)繼續討論。
private static void t1() {
// TODO Auto-generated method stub
// 加載配置
try {
Reader reader = Resources.getResourceAsReader("mybatisconfig.xml");
// 創建
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
// 解析資源
SqlSessionFactory factory = builder.build(reader);
Configuration configuration = factory.getConfiguration();
// 打開session
SqlSession session = factory.openSession();
// 2,用接口映射的形式進行查詢,官方推薦
empmapper empmapper = session.getMapper(empmapper.class);
List<Emp> list = empmapper.queryall();
for (Emp emp : list) {
System.out.println(emp);
}
List<Emp> list2 = empmapper.queryall();
System.out.println(list2);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
還是這段代碼繼續分析,看到List<Emp> list = empmapper.queryall();這麼一句代碼,那麼看看mybatis是如何根據接口方法查詢數據庫,返回結果的呢,我們一步步分析。
斷點進入方法,會執行MapperProxy.invoke()方法。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
調用invoke方法,參數proxy就是Mapper的代理對象,method是Mapper接口的信息,args是調用Mapper方法的參數列表,調用方法empmapper.queryall();,那麼args就是null。
執行invoke方法,先判斷被代理是不是一個類,是一個類,那麼執行該類的方法,顯然我們這裏不滿足,則執行
final MapperMethod mapperMethod = cachedMapperMethod(method);這個方法是獲取MapperMethod,那麼MapperMethod是怎麼獲取的呢?MapperMethod又是什麼呢?
跟蹤代碼進入cachedMapperMethod方法,可以看到先是從緩存中獲取,如果緩存中沒有,則創建MapperMethod,其實是當mapper方法被調用的時候對應的MapperProxy會生成相應的MapperMethod並且會緩存起來,這樣當多次調用同一個mapper方法時候只會生成一個MapperMethod,提高了時間和內存效率。每一個MapperMethod對應了一個mapper文件中配置的一個sql語句或FLUSH配置。
來看看MapperMethod的創建
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
可以看出,創建了 SqlCommand 和 MethodSignature,這兩個類都是MapperMethod的內部類,這兩個類作用什麼呢?先看SqlCommand ,源碼看SqlCommand 創建時都做了什麼。
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
String statementName = mapperInterface.getName() + "." + method.getName();
MappedStatement ms = null;
if (configuration.hasStatement(statementName)) {
ms = configuration.getMappedStatement(statementName);
} else if (!mapperInterface.equals(method.getDeclaringClass())) { // issue #35
String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName();
if (configuration.hasStatement(parentStatementName)) {
ms = configuration.getMappedStatement(parentStatementName);
}
}
if (ms == null) {
if(method.getAnnotation(Flush.class) != null){
name = null;
type = SqlCommandType.FLUSH;
} else {
throw new BindingException("Invalid bound statement (not found): " + statementName);
}
} else {
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
這個mapperInterface是mapper接口的全限定名,那麼這裏的statementName就是全限定類名+方法名,對應於這個查詢就是:
com.shandian.mapper.empmapper.queryall,那麼接下來就是通過statementName獲取一個MappedStatement,這又是什麼東東呢?
那麼我們來看看MappedStatement類,
public final class MappedStatement {
private String resource;
private Configuration configuration;
private String id;
private Integer fetchSize;
private Integer timeout;
private StatementType statementType;
private ResultSetType resultSetType;
private SqlSource sqlSource;
private Cache cache;
private ParameterMap parameterMap;
private List<ResultMap> resultMaps;
private boolean flushCacheRequired;
private boolean useCache;
private boolean resultOrdered;
private SqlCommandType sqlCommandType;
private KeyGenerator keyGenerator;
private String[] keyProperties;
private String[] keyColumns;
private boolean hasNestedResultMaps;
private String databaseId;
private Log statementLog;
private LanguageDriver lang;
private String[] resultSets;
private MappedStatement() {
// constructor disabled
}
//......其他略
}
看到這些類的字段是不是有些莫名的熟悉,想的起來嗎?沒事,我再截一張圖。
這下知道了吧,就是Sql語句的信息,把xml配置解析成MappedStatement。
好,接着看創建SqlCommand,獲取MappedStatement之後,接着就是把MappedStatement的id屬性設置到SqlCommand的name屬性中去,我們這裏就是com.shandian.mapper.empmapper.queryall,其實她就是我們在mapper.xml中配置的<select>namespace + id,而type就是select.至此SqlCommand創建完成。
接下來繼續看MethodSignature的創建過程,
public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
if (resolvedReturnType instanceof Class<?>) {
this.returnType = (Class<?>) resolvedReturnType;
} else if (resolvedReturnType instanceof ParameterizedType) {
this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
} else {
this.returnType = method.getReturnType();
}
this.returnsVoid = void.class.equals(this.returnType);
this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray());
this.returnsCursor = Cursor.class.equals(this.returnType);
this.mapKey = getMapKey(method);
this.returnsMap = (this.mapKey != null);
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
this.paramNameResolver = new ParamNameResolver(configuration, method);
}
這裏是得到這個方法的返回類型,參數類型等信息,然後依次判斷是否返回Void,是否返回多條結果,是否返回Map,rowBound類型參數位置,參數信息等等。
至此 MapperMethod 創建獲取完畢,獲取MapperMethod時涉及到很多對象,寫的也比較亂,那現在總結一下,MapperMethod是和數據庫打交道的重要一個對象,它統領了和數據庫打交道的方法。MapperMethod內部維護着SqlCommand對象和MethodSignature對象,這兩個是它的內部類。SqlCommand,主要是封裝了SQL標籤的類型,和要執行那個SQL的id,而MethodSignature主要是封裝了方法的參數信息和具體返回值類型信息,所以有了這個對象就可以知道本次對數據庫的操作是一種什麼樣的操作(SELECT,INSERT,UPDATE,DELETE),能夠找到某某mapper.xml的哪個SQL,可以知道執行這段SQL傳入的參數信息,還有執行完這段SQL後的返回值是什麼。
這一篇主要分析了 MapperMethod 的作用 以及 MapperMethod 的創建過程。下一篇我們接着討論查詢過程。
如果有讀者發現我說的有不對的地方,還望大家多多指教!