Mybatis Plus實(shí)現(xiàn)動(dòng)態(tài)SQL語(yǔ)句的原理,你知道嗎?
點(diǎn)擊關(guān)注公眾號(hào),Java干貨及時(shí)送達(dá)
牛逼!又發(fā)現(xiàn)了一款面試題庫(kù),太全了??!
(點(diǎn)擊查看)
Mybatis-Plus(簡(jiǎn)稱(chēng)MP)是一個(gè) Mybatis 的增強(qiáng)工具,那么它是怎么增強(qiáng)的呢?其實(shí)就是它已經(jīng)封裝好了一些crud方法,開(kāi)發(fā)就不需要再寫(xiě)xml了,直接調(diào)用這些方法就行,就類(lèi)似于JPA。那么這篇文章就來(lái)閱讀以下MP的具體實(shí)現(xiàn),看看是怎樣實(shí)現(xiàn)這些增強(qiáng)的。

入口類(lèi):MybatisSqlSessionFactoryBuilder
通過(guò)在入口類(lèi) MybatisSqlSessionFactoryBuilder#build方法中, 在應(yīng)用啟動(dòng)時(shí), 將mybatis plus(簡(jiǎn)稱(chēng)MP)自定義的動(dòng)態(tài)配置xml文件注入到Mybatis中。
public class MybatisSqlSessionFactoryBuilder extends SqlSessionFactoryBuilder {
public SqlSessionFactory build(Configuration configuration) {
// ... 省略若干行
if (globalConfig.isEnableSqlRunner()) {
new SqlRunnerInjector().inject(configuration);
}
// ... 省略若干行
return sqlSessionFactory;
}
}
這里涉及到2個(gè)MP2個(gè)功能類(lèi)
擴(kuò)展繼承自Mybatis的MybatisConfiguration類(lèi): MP動(dòng)態(tài)腳本構(gòu)建,注冊(cè),及其它邏輯判斷。 SqlRunnerInjector: MP默認(rèn)插入一些動(dòng)態(tài)方法的xml 腳本方法。
MybatisConfiguration類(lèi)
這里我們重點(diǎn)剖析MybatisConfiguration類(lèi),在MybatisConfiguration中,MP初始化了其自身的MybatisMapperRegistry,而MybatisMapperRegistry是MP加載自定義的SQL方法的注冊(cè)器。
MybatisConfiguration中很多方法是使用MybatisMapperRegistry進(jìn)行重寫(xiě)實(shí)現(xiàn)
其中有3個(gè)重載方法addMapper實(shí)現(xiàn)了注冊(cè)MP動(dòng)態(tài)腳本的功能。
public class MybatisConfiguration extends Configuration {
/**
* Mapper 注冊(cè)
*/
protected final MybatisMapperRegistry mybatisMapperRegistry = new MybatisMapperRegistry(this);
// ....
/**
* 初始化調(diào)用
*/
public MybatisConfiguration() {
super();
this.mapUnderscoreToCamelCase = true;
languageRegistry.setDefaultDriverClass(MybatisXMLLanguageDriver.class);
}
/**
* MybatisPlus 加載 SQL 順序:
* <p> 1、加載 XML中的 SQL </p>
* <p> 2、加載 SqlProvider 中的 SQL </p>
* <p> 3、XmlSql 與 SqlProvider不能包含相同的 SQL </p>
* <p>調(diào)整后的 SQL優(yōu)先級(jí):XmlSql > sqlProvider > CurdSql </p>
*/
@Override
public void addMappedStatement(MappedStatement ms) {
// ...
}
// ... 省略若干行
/**
* 使用自己的 MybatisMapperRegistry
*/
@Override
public <T> void addMapper(Class<T> type) {
mybatisMapperRegistry.addMapper(type);
}
// .... 省略若干行
}
在MybatisMapperRegistry中,MP將mybatis的MapperAnnotationBuilder替換為MP自己的MybatisMapperAnnotationBuilder
public class MybatisMapperRegistry extends MapperRegistry {
@Override
public <T> void addMapper(Class<T> type) {
// ... 省略若干行
MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);
parser.parse();
// ... 省略若干行
}
}
在MybatisMapperRegistry類(lèi)的addMapper方法中,真正進(jìn)入到MP的核心類(lèi)MybatisMapperAnnotationBuilder,MybatisMapperAnnotationBuilder這個(gè)類(lèi)是MP實(shí)現(xiàn)動(dòng)態(tài)腳本的關(guān)鍵類(lèi)。
MybatisMapperAnnotationBuilder動(dòng)態(tài)構(gòu)造
在MP的核心類(lèi)MybatisMapperAnnotationBuilder的parser方法中,MP逐一遍歷要加載的Mapper類(lèi),加載的方法包括下面幾個(gè)
public class MybatisMapperAnnotationBuilder extends MapperAnnotationBuilder {
@Override
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);
}
//... 省略若干行
}
@Override
public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {
Class<?> modelClass = extractModelClass(mapperClass);
//... 省略若干行
List<AbstractMethod> 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 {
@Override
public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
return Stream.of(
new Insert(),
//... 省略若干行
new SelectPage()
).collect(toList());
}
}
在MybatisMapperAnnotationBuilder中,MP真正將框架自定義的動(dòng)態(tài)SQL語(yǔ)句注冊(cè)到Mybatis引擎中。而AbstractMethod則履行了具體方法的SQL語(yǔ)句構(gòu)造。
具體的AbstractMethod實(shí)例類(lèi),構(gòu)造具體的方法SQL語(yǔ)句
以 SelectById 這個(gè)類(lèi)為例說(shuō)明下
/**
* 根據(jù)ID 查詢(xún)一條數(shù)據(jù)
*/
public class SelectById extends AbstractMethod {
@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
/** 定義 mybatis xml method id, 對(duì)應(yīng) <id="xyz"> **/
SqlMethod sqlMethod = SqlMethod.SELECT_BY_ID;
/** 構(gòu)造id對(duì)應(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完成了在啟動(dòng)時(shí)加載自定義的方法xml配置的過(guò)程,后面的就是mybatis ${變量} #{變量}的動(dòng)態(tài)替換和預(yù)編譯,已經(jīng)進(jìn)入mybatis自有功能。
總結(jié)一下
MP總共改寫(xiě)和替換了mybatis的十多個(gè)類(lèi),主要如下圖所示:

總體上來(lái)說(shuō),MP實(shí)現(xiàn)mybatis的增強(qiáng),手段略顯繁瑣和不夠直觀(guān),其實(shí)根據(jù)MybatisMapperAnnotationBuilder構(gòu)造出自定義方法的xml文件,將其轉(zhuǎn)換為mybatis的Resource資源,可以只繼承重寫(xiě)一個(gè)Mybatis類(lèi):SqlSessionFactoryBean 比如如下:
public class YourSqlSessionFactoryBean extends SqlSessionFactoryBean implements ApplicationContextAware {
private Resource[] mapperLocations;
@Override
public void setMapperLocations(Resource... mapperLocations) {
super.setMapperLocations(mapperLocations);
/** 暫存使用mybatis原生定義的mapper xml文件路徑**/
this.mapperLocations = mapperLocations;
}
/**
* {@inheritDoc}
*/
@Override
public void afterPropertiesSet() throws Exception {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
/** 只需要通過(guò)將自定義的方法構(gòu)造成xml resource和原生定義的Resource一起注入到mybatis中即可, 這樣就可以實(shí)現(xiàn)MP的自定義動(dòng)態(tài)SQL和原生SQL的共生關(guān)系**/
this.setMapperLocations(InjectMapper.getMapperResource(this.dbType, beanFactory, this.mapperLocations));
super.afterPropertiesSet();
}
}
在這邊文章中,簡(jiǎn)單介紹了MP實(shí)現(xiàn)動(dòng)態(tài)語(yǔ)句的實(shí)現(xiàn)過(guò)程,并且給出一個(gè)可能的更便捷方法。
如有文章對(duì)你有幫助,
歡迎關(guān)注??、點(diǎn)贊??、轉(zhuǎn)發(fā)??!
推薦, Java面試題庫(kù),詳情點(diǎn)擊: 牛逼!又發(fā)現(xiàn)了一款牛逼的Java面試題庫(kù),史上最強(qiáng)! 點(diǎn)擊文末“閱讀原文”可直達(dá)


