开始
时间有限,能力有限,如有不正确的地方,欢迎指正。
说到 Mybatis-Plus
,想要了解它的源码,就要知道Mybatis-Plus
在项目中做了什么。这个框架还是很好用的, 很简单,而且也比较火,所以这里就从MyBatis-Plus简介里复制一下它的特性。
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
- 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
- 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
- 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 – Sequence),可自由配置,完美解决主键问题
- 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
- 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
- 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
- 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
- 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
- 内置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
- 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作
总结一下,
Mybatis-Plus
在项目中做了三件事。
1、 代码生成器。
2、 启动时操作(数据库配置,Mapper扫描等)。
3、 项目中CRUD操作。
这里主要分析一下启动时,和在项目中CRUD时,Mybatis-Plus
是如何工作的。
启动时
如果想要在项目启动时,配置自动生效,就需要知道如何写一个SpringBoot Starter
,刚好之前有自己写过一个Starter。 在 mybatis-plus-boot-starter
的resources
文件夹下有一个META-INF
文件夹
additional-spring-configuration-metadata.json
用于 我们在properties或者yml文件中,写Mybatis—plus相关配置时提示。spring.factories
在项目启动时,进行自动配置。里面配置的自动启动类# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration
MybatisPlusAutoConfiguration
@Configuration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})//系统中有指定的类
@ConditionalOnSingleCandidate(DataSource.class)//容器中只有一个指定的Bean,或者这个Bean是首选Bean
@EnableConfigurationProperties(MybatisPlusProperties.class)//为带有@ConfigurationProperties注解的Bean提供有效的支持
@AutoConfigureAfter(DataSourceAutoConfiguration.class)//将一个配置类在另一个配置类之后加载
public class MybatisPlusAutoConfiguration implements InitializingBean {
这个类由于实现了InitializingBean
接口,得到了afterPropertiesSet
方法,在Bean初始化后,会自动调用。 还有三个标注了 @ConditionalOnMissingBean
注解的方法。
这个方法在没有配置SqlSessionFactory
时会由SpringBoot创建Bean,并且保存到容器中。
这个方法在没有配置SqlSessionTemplate
时会由SpringBoot创建Bean,并且保存到容器中。
这个方法在没有配置MapperFactoryBean
时会由SpringBoot创建Bean,并且保存到容器中。
一进方法 com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration#sqlSessionFactory
就有一句 MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
MybatisSqlSessionFactoryBean
这个类实现了三个接口FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent>
1、 FactoryBean
:说明用到了工厂模式
2、 InitializingBean
: afterPropertiesSet
在属性设置完成时调用(在Bean创建完成时)调用
3、 ApplicationListener
是一个监听器,监听的是 ApplicationContext
初始化或者刷新事件,当初始化或者刷新时调用。 Parses all the unprocessed statement nodes in the cache. It is recommended to call this method once all the mappers are added as it provides fail-fast statement validation. 用来刷新 MappedStatement
然后看com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean#afterPropertiesSet
这个方法, 在这个方法里,调用了com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean#buildSqlSessionFactory
buildSqlSessionFactory
简单的说就是 创建一个SqlSessionFactory
实例,其实在这个方法里干了很多事情。
1、 首先解析Mybatis的配置。
2、 无配置启动 相关配置
3、 初始化 id-work 以及 打印 Banner
4、 设置元数据相关 如果用户没有配置 dbType 则自动获取
5、 自定义枚举类扫描处理
6、 扫描别名包
7、 添加拦截器插件
8、 如果是xml配置,解析xml配置文件
9、 根据 mapperLocations
解析 Mapper
文件
10、 最后是创建 SqlSessionFactory
,并返回。
这里有一个问题,在配置类里配置了,数据库类型为Mysql,但是还是为Other。
@Bean
public GlobalConfig globalConfig() {
GlobalConfig conf = new GlobalConfig();
DbConfig dbConfig = new DbConfig();
dbConfig.setDbType(DbType.MYSQL);
conf.setDbConfig(dbConfig);
return conf;
}
因为这个对象是new出来的,没有被Spring管理。如果改为Resource
或者Autowired
注入,启动报错。 但是没有关系,因为它会根据Connection的MetaData自动判断数据库类型。
接下来看这个方法的这里。 重要的是这一句
xmlMapperBuilder.parse();
这个parse
方法用来解析xml文件。 在框起来的地方,解析接口文件。
org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#parseStatement
因为是null,所以没有进入,这个方法主要用于解析使用Insert
、Select
、Update
、Delete
和 SelectProvider
、InsertProvider
、UpdateProvider
、DeleteProvider
这八个注解的方法,因为项目中没有,所以是null 真正注入是这里
com.baomidou.mybatisplus.core.injector.AbstractSqlInjector#inspectInject
this.getMethodList(mapperClass)
调用的是下面的注入器
com.baomidou.mybatisplus.core.injector.DefaultSqlInjector#getMethodList
com.baomidou.mybatisplus.core.injector.AbstractMethod#inject
com.baomidou.mybatisplus.core.injector.AbstractMethod#injectMappedStatement
接下来看一个deleteById,
Mybatis-Plus
自动注入基本 CURD的秘密就在这里 当然,最后还有一次校验
org.springframework.dao.support.DaoSupport
org.mybatis.spring.mapper.MapperFactoryBean#checkDaoConfig
项目中CRUD操作
在项目中使用Mybatis-Plus
,除去基本的CRUD操作,基本都是使用Lambda表达式拼装SQL。Mybatis-Plus
的AR模式,暂时不讨论。
下面就从一个简单的查询SQL来看看,Mybatis-Plus
是如何拼装SQL的。 这个是
Mybatis-Plus
项目的单元测试
com.baomidou.mybatisplus.test.MybatisTest#test
调用的是这个方法
com.baomidou.mybatisplus.core.mapper.BaseMapper#selectCount
说到Mybatis的CRUD操作,就不得不说两个类。
com.baomidou.mybatisplus.core.override.MybatisMapperProxy
com.baomidou.mybatisplus.core.override.MybatisMapperProxyFactory
MybatisMapperProxy
实现了InvocationHandler
接口,采用了JDK动态代理。 而MybatisMapperProxyFactory
这个类把所有生成的Mapper都绑定了代理。 在org.mybatis.spring.SqlSessionTemplate#getMapper
调用方法获取的,都是JDK动态代理生产的类。
断点调试
先获取Mapper 获取代理类
获取SQL
com.baomidou.mybatisplus.core.conditions.AbstractWrapper#doIt
com.baomidou.mybatisplus.core.conditions.segments.MergeSegments#add
com.baomidou.mybatisplus.core.override.MybatisMapperMethod#MybatisMapperMethod
org.apache.ibatis.binding.MapperMethod.SqlCommand#SqlCommand
org.apache.ibatis.binding.MapperMethod.SqlCommand#resolveMappedStatement
可以看出来,查询语句是先拼接好了前面一部分,然后根据lambda表达式,动态拼接查询条件。所以Mybatis-Plus
如果要实现使用lambda多表联接查询,难度不小。
由于这个方法是BaseMapper有的,在启动时就添加到了Configuration中,所以直接可以找到。
在这个方法中 org.apache.ibatis.binding.MapperMethod.MethodSignature#MethodSignature
设置方法签名。
执行SQLcom.baomidou.mybatisplus.core.override.MybatisMapperMethod#execute
可能
sqlSegment
为””,columnMap
为null。因为我后来断点到这里,都是空的。(具体原因,暂时不清楚。)
SQL执行流程
这里可以看到,Mybatis的一级缓存是怎么回事,每次查询都会 createCacheKey
,第二次查询如果命中,就走缓存,如果没有命中,才会走基本查询。而且每次查询先调用的是CachingExecutor
,没有命中缓存之后才是BaseExecutor
。
org.apache.ibatis.session.defaults.DefaultSqlSession#selectOne(java.lang.String, java.lang.Object)
org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object)
org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object, org.apache.ibatis.session.RowBounds)
org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler)
org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)
org.apache.ibatis.executor.BaseExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)
org.apache.ibatis.executor.BaseExecutor#queryFromDatabase
总结
为什么想到看源码呢?其实有两个原因,一是好奇为什么不用写基础CRUD,一个BaseMapper就可以通用。二是lambda表达式写SQL很爽,想看看能不能改造一下支持多表联接。 虽然说是Mybatis-Plus
源码阅读,其实有大部分是Mybatis
的源码,阅读源码确实有很多好处,至少现在知道了SQL
是如何注入的,Mybatis
的一级缓存是什么,JDK动态代理等等。对Mybatis-Plus
也有了更深入的了解。
构思:如果要支持多表联接查询,就要新增加一个基础方法,SQL语句只有一个
SELECT
,然后加入leftJoin,rightJoin,join,on,using,union等方法,从表到参数,全部动态生成。或许可行。
虽然分析了很多,可是这只是大概的主流程,很多细节没有分析到,肯定有遗漏的地方。比如com.baomidou.mybatisplus.core.enums.SqlMethod
。更多的知识点还是大家一起去发现吧。 再次声明,时间有限,能力有限,如有错误地方,欢迎指正。
转载请标明。
本文使用 tech.souyunku.com 排版