专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

MybatisPlus源码详解

开始

时间有限,能力有限,如有不正确的地方,欢迎指正。

说到 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-starterresources文件夹下有一个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

89_1.png

@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,并且保存到容器中。

89_2.png
89_3.png

这个方法在没有配置SqlSessionTemplate时会由SpringBoot创建Bean,并且保存到容器中。

89_4.png

这个方法在没有配置MapperFactoryBean时会由SpringBoot创建Bean,并且保存到容器中。 89_5.png

一进方法 com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration#sqlSessionFactory 就有一句 MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();

MybatisSqlSessionFactoryBean

这个类实现了三个接口FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> 89_6.png

1、 FactoryBean:说明用到了工厂模式
2、 InitializingBeanafterPropertiesSet 在属性设置完成时调用(在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

89_7.png

然后看com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean#afterPropertiesSet这个方法, 在这个方法里,调用了com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean#buildSqlSessionFactory 89_8.png

buildSqlSessionFactory

简单的说就是 创建一个SqlSessionFactory实例,其实在这个方法里干了很多事情。

1、 首先解析Mybatis的配置。
2、 无配置启动 相关配置
3、 初始化 id-work 以及 打印 Banner
4、 设置元数据相关 如果用户没有配置 dbType 则自动获取
5、 自定义枚举类扫描处理
6、 扫描别名包
7、 添加拦截器插件
8、 如果是xml配置,解析xml配置文件
9、 根据 mapperLocations解析 Mapper文件
10、 最后是创建 SqlSessionFactory ,并返回。 89_9.png
89_10.png

这里有一个问题,在配置类里配置了,数据库类型为Mysql,但是还是为Other。 89_11.png

  @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注入,启动报错。 89_12.png 但是没有关系,因为它会根据Connection的MetaData自动判断数据库类型。

接下来看这个方法的这里。 89_13.png 重要的是这一句xmlMapperBuilder.parse();

这个parse方法用来解析xml文件。 89_14.png 在框起来的地方,解析接口文件。 89_15.png
89_16.png
89_17.png

org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#parseStatement 因为是null,所以没有进入,这个方法主要用于解析使用InsertSelectUpdateDeleteSelectProviderInsertProviderUpdateProviderDeleteProvider这八个注解的方法,因为项目中没有,所以是null 89_18.png 真正注入是这里
com.baomidou.mybatisplus.core.injector.AbstractSqlInjector#inspectInject 89_19.png this.getMethodList(mapperClass)调用的是下面的注入器
com.baomidou.mybatisplus.core.injector.DefaultSqlInjector#getMethodList 89_20.png

com.baomidou.mybatisplus.core.injector.AbstractMethod#inject
com.baomidou.mybatisplus.core.injector.AbstractMethod#injectMappedStatement 89_21.png 接下来看一个deleteById,Mybatis-Plus自动注入基本 CURD的秘密就在这里 89_22.png 当然,最后还有一次校验 org.springframework.dao.support.DaoSupport 89_23.png org.mybatis.spring.mapper.MapperFactoryBean#checkDaoConfig 89_24.png

项目中CRUD操作

在项目中使用Mybatis-Plus,除去基本的CRUD操作,基本都是使用Lambda表达式拼装SQL。Mybatis-Plus的AR模式,暂时不讨论。
下面就从一个简单的查询SQL来看看,Mybatis-Plus是如何拼装SQL的。 89_25.png 这个是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 89_26.png

MybatisMapperProxy实现了InvocationHandler接口,采用了JDK动态代理。 而MybatisMapperProxyFactory这个类把所有生成的Mapper都绑定了代理。 在org.mybatis.spring.SqlSessionTemplate#getMapper调用方法获取的,都是JDK动态代理生产的类。

89_27.png

断点调试

先获取Mapper 89_28.png 获取代理类 89_29.png
89_30.png
89_31.png

获取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

89_32.png
89_33.png
89_34.png

可以看出来,查询语句是先拼接好了前面一部分,然后根据lambda表达式,动态拼接查询条件。所以Mybatis-Plus如果要实现使用lambda多表联接查询,难度不小。

89_35.png 由于这个方法是BaseMapper有的,在启动时就添加到了Configuration中,所以直接可以找到。 89_36.png

在这个方法中 org.apache.ibatis.binding.MapperMethod.MethodSignature#MethodSignature 设置方法签名。 89_37.png

执行SQLcom.baomidou.mybatisplus.core.override.MybatisMapperMethod#execute 89_38.png 可能sqlSegment为””,columnMap为null。因为我后来断点到这里,都是空的。(具体原因,暂时不清楚。) 89_39.png

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

89_40.png
89_41.png
89_42.png

总结

为什么想到看源码呢?其实有两个原因,一是好奇为什么不用写基础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 排版

文章永久链接:https://tech.souyunku.com/33758

未经允许不得转载:搜云库技术团队 » MybatisPlus源码详解

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们