面試官問:Mybatis Plus 是如何實(shí)現(xiàn)動態(tài) SQL 語句的?
不點(diǎn)藍(lán)字關(guān)注,我們哪來故事?
?正文如下?
來源:juejin.cn/post/6883081187103866894
Mybatis-Plus(簡稱MP)是一個 Mybatis 的增強(qiáng)工具,那么它是怎么增強(qiáng)的呢?其實(shí)就是它已經(jīng)封裝好了一些crud方法,開發(fā)就不需要再寫xml了,直接調(diào)用這些方法就行,就類似于JPA。
那么這篇文章就來閱讀以下MP的具體實(shí)現(xiàn),看看是怎樣實(shí)現(xiàn)這些增強(qiáng)的。
# 入口類:MybatisSqlSessionFactoryBuilder
通過在入口類 MybatisSqlSessionFactoryBuilder#build方法中, 在應(yīng)用啟動時(shí), 將mybatis plus(簡稱MP)自定義的動態(tài)配置xml文件注入到Mybatis中。
public?class?MybatisSqlSessionFactoryBuilder?extends?SqlSessionFactoryBuilder?{????public?SqlSessionFactory?build(Configuration?configuration)?{????????????//?...?省略若干行????????????if?(globalConfig.isEnableSqlRunner())?{????????????????new?SqlRunnerInjector().inject(configuration);????????????}????????????//?...?省略若干行????????????return?sqlSessionFactory;????????}}
這里涉及到2個MP2個功能類
擴(kuò)展繼承自Mybatis的MybatisConfiguration類: MP動態(tài)腳本構(gòu)建,注冊,及其它邏輯判斷。
SqlRunnerInjector: MP默認(rèn)插入一些動態(tài)方法的xml 腳本方法。
# MybatisConfiguration類
這里我們重點(diǎn)剖析MybatisConfiguration類,在MybatisConfiguration中,MP初始化了其自身的MybatisMapperRegistry,而MybatisMapperRegistry是MP加載自定義的SQL方法的注冊器。
MybatisConfiguration中很多方法是使用MybatisMapperRegistry進(jìn)行重寫實(shí)現(xiàn)。
其中有3個重載方法addMapper實(shí)現(xiàn)了注冊MP動態(tài)腳本的功能。
public?class?MybatisConfiguration?extends?Configuration?{????/**?????*?Mapper?注冊?????*/????protected?final?MybatisMapperRegistry?mybatisMapperRegistry?=?new?MybatisMapperRegistry(this);// ....????/**?????*?初始化調(diào)用?????*/????public?MybatisConfiguration()?{????????super();????????this.mapUnderscoreToCamelCase?=?true;????????languageRegistry.setDefaultDriverClass(MybatisXMLLanguageDriver.class);}????/**?????* MybatisPlus 加載 SQL 順序:?????*??1、加載?XML中的?SQL?
?????*??2、加載?SqlProvider?中的?SQL?
?????*??3、XmlSql?與?SqlProvider不能包含相同的?SQL?
?????*?調(diào)整后的 SQL優(yōu)先級:XmlSql > sqlProvider > CurdSql
?????*/????????public?void?addMappedStatement(MappedStatement?ms)?{????????//?...}????//?...?省略若干行????/**?????*?使用自己的?MybatisMapperRegistry?????*/????????public??void?addMapper(Class ?type)?{ ????????mybatisMapperRegistry.addMapper(type);????}????//?....?省略若干行}
在MybatisMapperRegistry中,MP將mybatis的MapperAnnotationBuilder替換為MP自己的MybatisMapperAnnotationBuilder
public?class?MybatisMapperRegistry?extends?MapperRegistry?{????????public??void?addMapper(Class ?type) ?{????????//?...?省略若干行????????MybatisMapperAnnotationBuilder?parser?=?new?MybatisMapperAnnotationBuilder(config,?type);????????parser.parse();????????//?...?省略若干行????}}
在MybatisMapperRegistry類的addMapper方法中,真正進(jìn)入到MP的核心類MybatisMapperAnnotationBuilder,MybatisMapperAnnotationBuilder這個類是MP實(shí)現(xiàn)動態(tài)腳本的關(guān)鍵類。
# MybatisMapperAnnotationBuilder動態(tài)構(gòu)造
在MP的核心類MybatisMapperAnnotationBuilder的parser方法中,MP逐一遍歷要加載的Mapper類,加載的方法包括下面幾個
public?class?MybatisMapperAnnotationBuilder?extends?MapperAnnotationBuilder?{????????public?void?parse()?{????????//...?省略若干行????????for?(Method?method?:?type.getMethods())?{????????????/**?for循環(huán)代碼,?MP判斷method方法是否是@Select?@Insert等mybatis注解方法**/????????????parseStatement(method);????????????InterceptorIgnoreHelper.initSqlParserInfoCache(cache,?mapperName,?method);????????????SqlParserHelper.initSqlParserInfoCache(mapperName,?method);????????}????????/**?這2行代碼,?MP注入默認(rèn)的方法列表**/????????if?(GlobalConfigUtils.isSupperMapperChildren(configuration,?type))?{????????????GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant,?type);????????}????????//...?省略若干行}????????public?void?inspectInject(MapperBuilderAssistant?builderAssistant,?Class>?mapperClass)?{????????Class>?modelClass?=?extractModelClass(mapperClass);????????//...?省略若干行????????List?methodList?=?this.getMethodList(mapperClass); ????????TableInfo?tableInfo?=?TableInfoHelper.initTableInfo(builderAssistant,?modelClass);????????//?循環(huán)注入自定義方法????????methodList.forEach(m?->?m.inject(builderAssistant,?mapperClass,?modelClass,?tableInfo));????????mapperRegistryCache.add(className);????}}public class DefaultSqlInjector extends AbstractSqlInjector {????????public?List?getMethodList(Class>?mapperClass)? {????????return?Stream.of(????????????new?Insert(),????????????//...?省略若干行????????????new?SelectPage()????????).collect(toList());????}}
在MybatisMapperAnnotationBuilder中,MP真正將框架自定義的動態(tài)SQL語句注冊到Mybatis引擎中。而AbstractMethod則履行了具體方法的SQL語句構(gòu)造。
# 具體的AbstractMethod實(shí)例類,構(gòu)造具體的方法SQL語句
以 SelectById 這個類為例說明下
/**?*?根據(jù)ID?查詢一條數(shù)據(jù)?*/public?class?SelectById?extends?AbstractMethod?{????????public?MappedStatement?injectMappedStatement(Class>?mapperClass,?Class>?modelClass,?TableInfo?tableInfo)?{????????/**?定義?mybatis?xml?method?id,?對應(yīng)??**/ SqlMethod sqlMethod = SqlMethod.SELECT_BY_ID;????????/**?構(gòu)造id對應(yīng)的具體xml片段?**/????????SqlSource?sqlSource?=?new?RawSqlSource(configuration,?String.format(sqlMethod.getSql(),????????????sqlSelectColumns(tableInfo,?false),????????????tableInfo.getTableName(),?tableInfo.getKeyColumn(),?tableInfo.getKeyProperty(),????????????tableInfo.getLogicDeleteSql(true,?true)),?Object.class);????????/**?將xml?method方法添加到mybatis的MappedStatement中?**/????????return?this.addSelectMappedStatementForTable(mapperClass,?getMethod(sqlMethod),?sqlSource,?tableInfo);????}}
至此,MP完成了在啟動時(shí)加載自定義的方法xml配置的過程,后面的就是mybatis ${變量} #{變量}的動態(tài)替換和預(yù)編譯,已經(jīng)進(jìn)入mybatis自有功能。
# 總結(jié)一下
MP總共改寫和替換了mybatis的十多個類,主要如下圖所示:
總體上來說,MP實(shí)現(xiàn)mybatis的增強(qiáng),手段略顯繁瑣和不夠直觀,其實(shí)根據(jù)MybatisMapperAnnotationBuilder構(gòu)造出自定義方法的xml文件,將其轉(zhuǎn)換為mybatis的Resource資源,可以只繼承重寫一個Mybatis類:SqlSessionFactoryBean 比如如下:
public class YourSqlSessionFactoryBean extends SqlSessionFactoryBean implements ApplicationContextAware {????private?Resource[]?mapperLocations;????????????public?void?setMapperLocations(Resource...?mapperLocations)?{????????super.setMapperLocations(mapperLocations);????????/**?存使用mybatis原生定義的mapper?xml文件路徑**/????????this.mapperLocations?=?mapperLocations;}????/**?????*?{@inheritDoc}?????*/????????public?void?afterPropertiesSet()?throws?Exception?{????????ConfigurableListableBeanFactory?beanFactory?=?getBeanFactory();????????/**?只需要通過將自定義的方法構(gòu)造成xml?resource和原生定義的Resource一起注入到mybatis中即可,?這樣就可以實(shí)現(xiàn)MP的自定義動態(tài)SQL和原生SQL的共生關(guān)系**/????????this.setMapperLocations(InjectMapper.getMapperResource(this.dbType,?beanFactory,?this.mapperLocations));????????super.afterPropertiesSet();????}}
在這篇文章中,簡單介紹了MP實(shí)現(xiàn)動態(tài)語句的實(shí)現(xiàn)過程,并且給出一個可能的更便捷方法。
往期推薦
↓ 或加泥瓦匠微信,交流更多技術(shù)?↓




