Mybatis的Mapper代理對象生成及調用過程示例詳解
導讀
你在mapper.xml文件中寫的sql語句最終是怎么被執(zhí)行的?我們編寫的mapper接口最終是怎么生成代理對象并被調用執(zhí)行的?
這部分內容應該是Mybatis框架中最關鍵、也是最復雜的部分,今天文章的主要目標是要搞清楚:
- mapper.xml文件是怎么初始化到Mybatis框架中的?
- mapper接口生成動態(tài)代理對象的過程。
- mapper接口動態(tài)代理對象的執(zhí)行過程。
掌握了以上3個問題,我們就掌握了Mybatis的核心。
Mapper初始化過程
指的是mapper.xml文件的解析過程。
這個動作是在SqlSessionFactory創(chuàng)建的過程中同步完成的,或者說是在SqlSessionFactory被build出來之前完成。
XMLMapperBuilder負責對mapper.xml文件做解析,SqlSessionFactorBean的buildSqlSessionFactory()方法中會針對不同配置情況進行解析。其中我們最常用的是在配置文件中指定mapper.xml文件的路徑(就是源碼中的這個mapperLocations):
if (this.mapperLocations != null) {
if (this.mapperLocations.length == 0) {
LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
} else {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
}
}
} else {
LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
}
return this.sqlSessionFactoryBuilder.build(targetConfiguration);創(chuàng)建XMLMapperBuilder對象并調用parse()方法完成解析。
XMLMapperBuilder#configurationElement()
parse方法會調用configurationElement()方法,對mapper.xml的解析的關鍵部分就在configurationElement方法中。
我們今天把問題聚焦在mapper.xml文件中sql語句的解析,也就是其中的insert、update、delete、select等標簽的解析。
private void configurationElement(XNode context) {
try {
//獲取namespace
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.isEmpty()) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
//二級緩存Ref解析
cacheRefElement(context.evalNode("cache-ref"));
//二級緩存配置的解析
cacheElement(context.evalNode("cache"));
//parameterMap標簽的解析
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//resultMap標簽的解析
resultMapElements(context.evalNodes("/mapper/resultMap"));
//sql標簽的解析
sqlElement(context.evalNodes("/mapper/sql"));
//關鍵部分:sql語句的解析
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}對sql語句解析部分繼續(xù)跟蹤會發(fā)現(xiàn),最終sql語句解析完成之后會創(chuàng)建MappedStatement并保存在configuration對象中(以xml文件中的id為key值的Map中):
MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);
return statement;這樣我們就明白了,mapper.xml中編寫的sql語句被解析后,最終保存在configuration對象的mappedStatements中了,mappedStatements其實是一個以mapper文件中相關標簽的id值為key值的hashMap。
Mapper接口生成動態(tài)代理過程
我們都知道Mapper對象是通過SqlSession的getMapper方法獲取到的,其實Mapper接口的代理對象也就是在這個調用過程中生成的:
@Override
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}調用Configuration的getMapper方法:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}調用mapperRegistry的getMapper方法,首先從knownMappers(以namespace為key值保存mapperProxyFactory的HashMap)中獲取到mapperProxyFactory,mapperProxyFactory人如其名,就是mapper代理對象工廠,負責創(chuàng)建mapper代理對象。
獲取到mapperProxyFactory之后,調用newInstance方法:
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);
}
}調用MapperProxy的newInstance方法:
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}典型的JDK動態(tài)代理生成邏輯,傳入的回調參數(shù)為mapperProxy對象,也就是說我們對Mapper接口的方法調用,最終通過生成的動態(tài)代理對象,會調用到這個回調對象mapperProxy的invoke方法。
至此,mapper接口動態(tài)代理的生成邏輯我們就從源碼的角度分析完畢,現(xiàn)在我們已經(jīng)搞清楚mapper動態(tài)代理的生成過程,最重要的是,我們知道m(xù)apper接口的方法調用最終會轉換為對mapperProxy的invoke方法的調用。
mapper接口動態(tài)代理對象的執(zhí)行過程
這個問題現(xiàn)在已經(jīng)明確了,其實就是MapperProxy的invoke方法。
對MapperProxy稍加研究,我們發(fā)現(xiàn)他的invoke方法最終會調用MapperMethod的execute方法:
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return mapperMethod.execute(sqlSession, args);
}execute方法根據(jù)調用方法的sql類型分別調用sqlSession的insert、update、delete、select方法,相應的方法會根據(jù)調用方法名從configuration中匹配MappedStatement從而執(zhí)行我們在mapper.xml中配置的sql語句(參考XMLMapperBuilder#configurationElement()部分)。
因此我們也就明白了為什么mapper.xml文件中配置的sql語句的id必須要對應mapper接口中的方法名,因為Mybatis要通過mapper接口中的方法名去匹配sql語句、從而最終執(zhí)行該sql語句!
任務完成!
其實雖然我們知道SqlSession默認的落地實現(xiàn)對象是DefaultSqlSession,我們在mapper.xml中編寫的sql語句其實是DefaultSqlSession負責執(zhí)行的,但是MapperMethod中sqlSession其實也是代理對象(DefaultSqlSession的代理對象),所以說Mybatis中到處都是動態(tài)代理,這部分我們下次再分析。
以上就是Mybatis的Mapper代理對象生成及調用過程示例詳解的詳細內容,更多關于Mybatis Mapper代理對象生成調用的資料請關注腳本之家其它相關文章!
相關文章
輸出java進程的jstack信息示例分享 通過線程堆棧信息分析java線程
通過ps到java進程號將進程的jstack信息輸出。jstack信息是java進程的線程堆棧信息,通過該信息可以分析java的線程阻塞等問題。2014-01-01
springsecurity實現(xiàn)登錄驗證以及根據(jù)用戶身份跳轉不同頁面
Spring?Security是一種基于Spring框架的安全技術,用于實現(xiàn)身份驗證和訪問控制,本文介紹了如何使用Spring?Security,結合session和redis來存儲用戶信息,并通過編寫特定的登錄處理類和Web配置,實現(xiàn)用戶登錄和注銷功能2024-09-09
Java經(jīng)典設計模式之模板方法模式定義與用法示例
這篇文章主要介紹了Java經(jīng)典設計模式之模板方法模式,簡單說明了模板方法模式的原理、定義,并結合實例形式分析了java模板方法模式的具體使用方法,需要的朋友可以參考下2017-08-08

