(十一)Mybatis Mapper動態代理
注:程式碼已託管在GitHub上,地址是:https://github.com/Damaer/Mybatis-Learning ,專案是mybatis-07-MapperDynamicProxy,需要自取,需要配置maven環境以及mysql環境,覺得有用可以點個小星星,小菜鳥在此Thanks~
在之前的程式碼中我們的執行過程再梳理一下,首先我們執行Test,呼叫dao介面方法
介面的定義:
呼叫介面的實現類方法:
最後才是呼叫真正的sql:
那麼我們想是不是可以減少一步呢?我們不用自己實現介面,只需要將介面的名字和mapper檔案的namespace對應起來,將接口裡面的方法名與sql語句標籤的id對應起來是不是就可以了呢?事實上,mybatis提供了這樣的做法,這就是mapper動態代理。
mapper動態代理的例子
首先主配置檔案(Mybatis.xml),在裡面配置資料庫連線資訊,註冊需要掃描的mapper檔案:
定義資料庫查詢的介面,裡面每一個介面的名字很重要,需要和mapper裡面每一條sql對應起來:
定義mapper檔案(namespace要寫我們定義的介面,而每條sql的id與我們的介面方法名字對應起來):
那我們在使用的時候:
我們在前面還寫到過一個selectStudentMap方法,但是裡面呼叫的是和SelectList一樣的sql,在介面的實現類裡面我們自己處理了一下,但是現在使用自動實現的話,底層只會呼叫SelectOne或者SelectList方法,所以這個方法會報錯,如果接受型別是list,那麼框架會自動使用selectList方法,否則就會選擇selectOne()這個方法。
在這裡我們使用的是返回的是map,所以自動選擇返回selectOne()方法,那麼就會報錯。如果我們需要使用自動返回map的話,可以自己定一個map,或者返回list之後再處理,這個知識點後面再介紹,有興趣可以訪問:
mapper動態代理的原理
打一個斷點在sqlSession.getMapper()方法上:
我們可以看到執行下面的介面方法(介面SqlSession
的方法)
<T> T getMapper(Class<T> var1);
這是一個介面,我們可以看到實現介面的有兩個類,一個是DefaultSqlSession,一個是SqlSessionManager,我們需要看的是DefaultSqlSession
下面的介面:
public <T> T getMapper(Class<T> type) {
return this.configuration.getMapper(type, this);
}
我們知道,在建立sqlsession的時候,confiiguration這個配置物件已經建立完成。跟進去,這是使用mapper註冊器物件的getMapper方法,將當前的sqlSession物件傳遞進去:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
我們跟進去原始碼,可以發現裡面使用knownMappers.get(type)
來獲取mapper代理工廠,這個konwnMappers是一個hashMap,這個hashMap裡面已經初始化了mapperProxyFactory物件了,獲取到工廠物件之後,再去使用sqlSession例項化:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
例項化的時候,使用了mapper動態代理:
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
從下面的debug結果中我們可以看到,這是動態代理的結果: