Mybatis的Mapper代理對(duì)象生成及調(diào)用過(guò)程示例詳解
導(dǎo)讀
你在mapper.xml文件中寫(xiě)的sql語(yǔ)句最終是怎么被執(zhí)行的?我們編寫(xiě)的mapper接口最終是怎么生成代理對(duì)象并被調(diào)用執(zhí)行的?
這部分內(nèi)容應(yīng)該是Mybatis框架中最關(guān)鍵、也是最復(fù)雜的部分,今天文章的主要目標(biāo)是要搞清楚:
- mapper.xml文件是怎么初始化到Mybatis框架中的?
- mapper接口生成動(dòng)態(tài)代理對(duì)象的過(guò)程。
- mapper接口動(dòng)態(tài)代理對(duì)象的執(zhí)行過(guò)程。
掌握了以上3個(gè)問(wèn)題,我們就掌握了Mybatis的核心。
Mapper初始化過(guò)程
指的是mapper.xml文件的解析過(guò)程。
這個(gè)動(dòng)作是在SqlSessionFactory創(chuàng)建的過(guò)程中同步完成的,或者說(shuō)是在SqlSessionFactory被build出來(lái)之前完成。
XMLMapperBuilder負(fù)責(zé)對(duì)mapper.xml文件做解析,SqlSessionFactorBean的buildSqlSessionFactory()方法中會(huì)針對(duì)不同配置情況進(jìn)行解析。其中我們最常用的是在配置文件中指定mapper.xml文件的路徑(就是源碼中的這個(gè)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對(duì)象并調(diào)用parse()方法完成解析。
XMLMapperBuilder#configurationElement()
parse方法會(huì)調(diào)用configurationElement()方法,對(duì)mapper.xml的解析的關(guān)鍵部分就在configurationElement方法中。
我們今天把問(wèn)題聚焦在mapper.xml文件中sql語(yǔ)句的解析,也就是其中的insert、update、delete、select等標(biāo)簽的解析。
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); //二級(jí)緩存Ref解析 cacheRefElement(context.evalNode("cache-ref")); //二級(jí)緩存配置的解析 cacheElement(context.evalNode("cache")); //parameterMap標(biāo)簽的解析 parameterMapElement(context.evalNodes("/mapper/parameterMap")); //resultMap標(biāo)簽的解析 resultMapElements(context.evalNodes("/mapper/resultMap")); //sql標(biāo)簽的解析 sqlElement(context.evalNodes("/mapper/sql")); //關(guān)鍵部分:sql語(yǔ)句的解析 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); } }
對(duì)sql語(yǔ)句解析部分繼續(xù)跟蹤會(huì)發(fā)現(xiàn),最終sql語(yǔ)句解析完成之后會(huì)創(chuàng)建MappedStatement并保存在configuration對(duì)象中(以xml文件中的id為key值的Map中):
MappedStatement statement = statementBuilder.build(); configuration.addMappedStatement(statement); return statement;
這樣我們就明白了,mapper.xml中編寫(xiě)的sql語(yǔ)句被解析后,最終保存在configuration對(duì)象的mappedStatements中了,mappedStatements其實(shí)是一個(gè)以mapper文件中相關(guān)標(biāo)簽的id值為key值的hashMap。
Mapper接口生成動(dòng)態(tài)代理過(guò)程
我們都知道Mapper對(duì)象是通過(guò)SqlSession的getMapper方法獲取到的,其實(shí)Mapper接口的代理對(duì)象也就是在這個(gè)調(diào)用過(guò)程中生成的:
@Override public <T> T getMapper(Class<T> type) { return getConfiguration().getMapper(type, this); }
調(diào)用Configuration的getMapper方法:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); }
調(diào)用mapperRegistry的getMapper方法,首先從knownMappers(以namespace為key值保存mapperProxyFactory的HashMap)中獲取到mapperProxyFactory,mapperProxyFactory人如其名,就是mapper代理對(duì)象工廠,負(fù)責(zé)創(chuàng)建mapper代理對(duì)象。
獲取到mapperProxyFactory之后,調(diào)用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); } }
調(diào)用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動(dòng)態(tài)代理生成邏輯,傳入的回調(diào)參數(shù)為mapperProxy對(duì)象,也就是說(shuō)我們對(duì)Mapper接口的方法調(diào)用,最終通過(guò)生成的動(dòng)態(tài)代理對(duì)象,會(huì)調(diào)用到這個(gè)回調(diào)對(duì)象mapperProxy的invoke方法。
至此,mapper接口動(dòng)態(tài)代理的生成邏輯我們就從源碼的角度分析完畢,現(xiàn)在我們已經(jīng)搞清楚mapper動(dòng)態(tài)代理的生成過(guò)程,最重要的是,我們知道m(xù)apper接口的方法調(diào)用最終會(huì)轉(zhuǎn)換為對(duì)mapperProxy的invoke方法的調(diào)用。
mapper接口動(dòng)態(tài)代理對(duì)象的執(zhí)行過(guò)程
這個(gè)問(wèn)題現(xiàn)在已經(jīng)明確了,其實(shí)就是MapperProxy的invoke方法。
對(duì)MapperProxy稍加研究,我們發(fā)現(xiàn)他的invoke方法最終會(huì)調(diào)用MapperMethod的execute方法:
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable { return mapperMethod.execute(sqlSession, args); }
execute方法根據(jù)調(diào)用方法的sql類(lèi)型分別調(diào)用sqlSession的insert、update、delete、select方法,相應(yīng)的方法會(huì)根據(jù)調(diào)用方法名從configuration中匹配MappedStatement從而執(zhí)行我們?cè)趍apper.xml中配置的sql語(yǔ)句(參考XMLMapperBuilder#configurationElement()部分)。
因此我們也就明白了為什么mapper.xml文件中配置的sql語(yǔ)句的id必須要對(duì)應(yīng)mapper接口中的方法名,因?yàn)镸ybatis要通過(guò)mapper接口中的方法名去匹配sql語(yǔ)句、從而最終執(zhí)行該sql語(yǔ)句!
任務(wù)完成!
其實(shí)雖然我們知道SqlSession默認(rèn)的落地實(shí)現(xiàn)對(duì)象是DefaultSqlSession,我們?cè)趍apper.xml中編寫(xiě)的sql語(yǔ)句其實(shí)是DefaultSqlSession負(fù)責(zé)執(zhí)行的,但是MapperMethod中sqlSession其實(shí)也是代理對(duì)象(DefaultSqlSession的代理對(duì)象),所以說(shuō)Mybatis中到處都是動(dòng)態(tài)代理,這部分我們下次再分析。
以上就是Mybatis的Mapper代理對(duì)象生成及調(diào)用過(guò)程示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Mybatis Mapper代理對(duì)象生成調(diào)用的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
輸出java進(jìn)程的jstack信息示例分享 通過(guò)線程堆棧信息分析java線程
通過(guò)ps到j(luò)ava進(jìn)程號(hào)將進(jìn)程的jstack信息輸出。jstack信息是java進(jìn)程的線程堆棧信息,通過(guò)該信息可以分析java的線程阻塞等問(wèn)題。2014-01-01淺談Spring事務(wù)傳播行為實(shí)戰(zhàn)
這篇文章主要介紹了淺談Spring事務(wù)傳播行為實(shí)戰(zhàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09深入了解Java核心類(lèi)庫(kù)--Objects類(lèi)
這篇文章主要介紹了Java中的Object類(lèi)詳細(xì)介紹,本文講解了Object類(lèi)的作用、Object類(lèi)的主要方法、Object類(lèi)中不能被重寫(xiě)的方法、Object類(lèi)的equals方法重寫(xiě)實(shí)例等內(nèi)容,需要的朋友可以參考下2021-07-07Java常用HASH算法總結(jié)【經(jīng)典實(shí)例】
這篇文章主要介紹了Java常用HASH算法,結(jié)合實(shí)例形式總結(jié)分析了Java常用的Hash算法,包括加法hash、旋轉(zhuǎn)hash、FNV算法、RS算法hash、PJW算法、ELF算法、BKDR算法、SDBM算法、DJB算法、DEK算法、AP算法等,需要的朋友可以參考下2017-09-09java使用任務(wù)架構(gòu)執(zhí)行任務(wù)調(diào)度示例
在Java 5.0之前啟動(dòng)一個(gè)任務(wù)是通過(guò)調(diào)用Thread類(lèi)的start()方法來(lái)實(shí)現(xiàn)的,5.0里提供了一個(gè)新的任務(wù)執(zhí)行架構(gòu)使你可以輕松地調(diào)度和控制任務(wù)的執(zhí)行,并且可以建立一個(gè)類(lèi)似數(shù)據(jù)庫(kù)連接池的線程池來(lái)執(zhí)行任務(wù),下面看一個(gè)示例2014-01-01使用try-with-resource的輸入輸出流自動(dòng)關(guān)閉
這篇文章主要介紹了使用try-with-resource的輸入輸出流自動(dòng)關(guān)閉方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07springsecurity實(shí)現(xiàn)登錄驗(yàn)證以及根據(jù)用戶身份跳轉(zhuǎn)不同頁(yè)面
Spring?Security是一種基于Spring框架的安全技術(shù),用于實(shí)現(xiàn)身份驗(yàn)證和訪問(wèn)控制,本文介紹了如何使用Spring?Security,結(jié)合session和redis來(lái)存儲(chǔ)用戶信息,并通過(guò)編寫(xiě)特定的登錄處理類(lèi)和Web配置,實(shí)現(xiàn)用戶登錄和注銷(xiāo)功能2024-09-09Java經(jīng)典設(shè)計(jì)模式之模板方法模式定義與用法示例
這篇文章主要介紹了Java經(jīng)典設(shè)計(jì)模式之模板方法模式,簡(jiǎn)單說(shuō)明了模板方法模式的原理、定義,并結(jié)合實(shí)例形式分析了java模板方法模式的具體使用方法,需要的朋友可以參考下2017-08-08