面试官问:Mybatis Plus 是如何实现动态 SQL 语句的?原理你懂吗?

发布于 2021-11-24 13:12 ,所属分类:2021面试经验技巧分享

gongzhong号,学习更多 Java 干货!

来源:juejin.cn/post/6883081187103866894

Mybatis-Plus(简称MP)是一个 Mybatis 的增强工具,那么它是怎么增强的呢?其实就是它已经封装好了一些crud方法,开发就不需要再写xml了,直接调用这些方法就行,就类似于JPA。

那么这篇文章就来阅读以下MP的具体实现,看看是怎样实现这些增强的。

入口类:MybatisSqlSessionFactoryBuilder

通过在入口类 MybatisSqlSessionFactoryBuilder#build方法中, 应用启动时, 将mybatis plus(简称MP)自定义的动态配置xml文件注入到Mybatis中。

publicclassMybatisSqlSessionFactoryBuilderextendsSqlSessionFactoryBuilder{
publicSqlSessionFactorybuild(Configurationconfiguration){
//...省略若干行
if(globalConfig.isEnableSqlRunner()){
newSqlRunnerInjector().inject(configuration);
}
//...省略若干行
returnsqlSessionFactory;
}
}

这里涉及到2个MP2个功能类

  • 扩展继承自Mybatis的MybatisConfiguration类: MP动态脚本构建,注册,及其它逻辑判断。
  • SqlRunnerInjector: MP默认插入一些动态方法的xml 脚本方法。

MybatisConfiguration类

这里我们重点剖析MybatisConfiguration类,在MybatisConfiguration中,MP初始化了其自身的MybatisMapperRegistry,而MybatisMapperRegistry是MP加载自定义的SQL方法的注册器。

MybatisConfiguration中很多方法是使用MybatisMapperRegistry进行重写实现

其中有3个重载方法addMapper实现了注册MP动态脚本的功能。推荐一个 Spring Boot 基础教程及实战示例:https://github.com/javastacks/spring-boot-best-practice

publicclassMybatisConfigurationextendsConfiguration{
/**
*Mapper注册
*/
protectedfinalMybatisMapperRegistrymybatisMapperRegistry=newMybatisMapperRegistry(this);
//....

/**
*初始化调用
*/
publicMybatisConfiguration(){
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>调整后的 SQL优先级:XmlSql > sqlProvider > CurdSql </p>
*/
@Override
publicvoidaddMappedStatement(MappedStatementms){
//...
}

//...省略若干行
/**
*使用自己的MybatisMapperRegistry
*/
@Override
public<T>voidaddMapper(Class<T>type){
mybatisMapperRegistry.addMapper(type);
}
//....省略若干行
}

在MybatisMapperRegistry中,MP将mybatis的MapperAnnotationBuilder替换为MP自己的MybatisMapperAnnotationBuilder

publicclassMybatisMapperRegistryextendsMapperRegistry{
@Override
public<T>voidaddMapper(Class<T>type){
//...省略若干行
MybatisMapperAnnotationBuilderparser=newMybatisMapperAnnotationBuilder(config,type);
parser.parse();
//...省略若干行
}
}

在MybatisMapperRegistry类的addMapper方法中,真正进入到MP的核心类MybatisMapperAnnotationBuilder,MybatisMapperAnnotationBuilder这个类是MP实现动态脚本的关键类。

MybatisMapperAnnotationBuilder动态构造

在MP的核心类MybatisMapperAnnotationBuilder的parser方法中,MP逐一遍历要加载的Mapper类,加载的方法包括下面几个

publicclassMybatisMapperAnnotationBuilderextendsMapperAnnotationBuilder{
@Override
publicvoidparse(){
//...省略若干行
for(Methodmethod:type.getMethods()){
/**for循环代码,MP判断method方法是否是@Select@Insert等mybatis注解方法**/
parseStatement(method);
InterceptorIgnoreHelper.initSqlParserInfoCache(cache,mapperName,method);
SqlParserHelper.initSqlParserInfoCache(mapperName,method);
}
/**这2行代码,MP注入默认的方法列表**/
if(GlobalConfigUtils.isSupperMapperChildren(configuration,type)){
GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant,type);
}
//...省略若干行
}

@Override
publicvoidinspectInject(MapperBuilderAssistantbuilderAssistant,Class<?>mapperClass){
Class<?>modelClass=extractModelClass(mapperClass);
//...省略若干行
List<AbstractMethod>methodList=this.getMethodList(mapperClass);
TableInfotableInfo=TableInfoHelper.initTableInfo(builderAssistant,modelClass);
//循环注入自定义方法
methodList.forEach(m->m.inject(builderAssistant,mapperClass,modelClass,tableInfo));
mapperRegistryCache.add(className);
}
}
publicclassDefaultSqlInjectorextendsAbstractSqlInjector{

@Override
publicList<AbstractMethod>getMethodList(Class<?>mapperClass){
returnStream.of(
newInsert(),
//...省略若干行
newSelectPage()
).collect(toList());
}
}

在MybatisMapperAnnotationBuilder中,MP真正将框架自定义的动态SQL语句注册到Mybatis引擎中。而AbstractMethod则履行了具体方法的SQL语句构造。

gongzhong号,学习更多 Java 干货!

具体的AbstractMethod实例类,构造具体的方法SQL语句

以 SelectById 这个类为例说明下

/**
*根据ID查询一条数据
*/
publicclassSelectByIdextendsAbstractMethod{
@Override
publicMappedStatementinjectMappedStatement(Class<?>mapperClass,Class<?>modelClass,TableInfotableInfo){
/**定义mybatisxmlmethodid,对应<id="xyz">**/
SqlMethodsqlMethod=SqlMethod.SELECT_BY_ID;
/**构造id对应的具体xml片段**/
SqlSourcesqlSource=newRawSqlSource(configuration,String.format(sqlMethod.getSql(),
sqlSelectColumns(tableInfo,false),
tableInfo.getTableName(),tableInfo.getKeyColumn(),tableInfo.getKeyProperty(),
tableInfo.getLogicDeleteSql(true,true)),Object.class);
/**将xmlmethod方法添加到mybatis的MappedStatement中**/
returnthis.addSelectMappedStatementForTable(mapperClass,getMethod(sqlMethod),sqlSource,tableInfo);
}
}

至此,MP完成了在启动时加载自定义的方法xml配置的过程,后面的就是mybatis ${变量} #{变量}的动态替换和预编译,已经进入mybatis自有功能。最新面试题整理好了,点击Java面试库小程序在线刷题。

总结一下

MP总共改写和替换了mybatis的十多个类,主要如下图所示:

总体上来说,MP实现mybatis的增强,手段略显繁琐和不够直观,其实根据MybatisMapperAnnotationBuilder构造出自定义方法的xml文件,将其转换为mybatis的Resource资源,可以只继承重写一个Mybatis类:SqlSessionFactoryBean 比如如下:

publicclassYourSqlSessionFactoryBeanextendsSqlSessionFactoryBeanimplementsApplicationContextAware{

privateResource[]mapperLocations;

@Override
publicvoidsetMapperLocations(Resource...mapperLocations){
super.setMapperLocations(mapperLocations);
/**暂存使用mybatis原生定义的mapperxml文件路径**/
this.mapperLocations=mapperLocations;
}

/**
*{@inheritDoc}
*/
@Override
publicvoidafterPropertiesSet()throwsException{
ConfigurableListableBeanFactorybeanFactory=getBeanFactory();
/**只需要通过将自定义的方法构造成xmlresource和原生定义的Resource一起注入到mybatis中即可,这样就可以实现MP的自定义动态SQL和原生SQL的共生关系**/
this.setMapperLocations(InjectMapper.getMapperResource(this.dbType,beanFactory,this.mapperLocations));
super.afterPropertiesSet();
}
}

在这边文章中,简单介绍了MP实现动态语句的实现过程,并且给出一个可能的更便捷方法。


(完)


最近热文:
1.Spring Boot 学习笔记,这个太全了!
2.面试官:MySQL 中的 3 大日志是指哪些?
3.@bean 和 @component 注解有什么区别?
4.3 行代码写出 8 个接口,牛逼!!
5.面试官:GET 请求能上传图片吗?

——长按Java大后端——

获取一份 Spring Boot 资料!

相关资源