MyBatis-Plus核心流程与源码分析

LD is tigger forever,CG are not brothers forever, throw the pot and shine forever.
Modesty is not false, solid is not naive, treacherous but not deceitful, stay with good people, and stay away from poor people.
talk is cheap, show others the code,Keep progress,make a better result.
Survive during the day and develop at night。

目录

概 述

MyBatis-plus

MyBatis-plus是完全基于MyBatis开发的一个增强工具,是在MyBatis的基础上做增强的框架,为简化开发、提高效率而生。它在MyBatis原本的框架上增加了很多实用性功能,比如乐观锁插件、字段自动填充功能、分页插件、条件构造器、sql 注入器等等。使用 MyBatis-plus 可以完全不写任何 XML 文件,直接使用继承了BaseMapper 接口的类对象完成对数据库的映射操作

基于映射的原理,MyBatis-plus 必然要实现 Mapper中的方法与 SQL 语句的对应转化,以下即为 MyBatis-plus 重要流程图例

  1. Mapper 对象方法映射为 SQL 语句
    在 MyBatis-plus 中, MybatisPlusAutoConfiguration 自动配置类的 sqlSessionFactory()方法为 Spring提供创建 sqlSession的工厂类对象,对 sqlSessionFactory 进行定义的定义类变为了 MybatisSqlSessionFactoryBean。在 sqlSessionFactory()方法中,除了注入 MyBatis本身的组件,还会注入MyBatis-plus 的 主键生成器、SQL 注入器等组件,最后通过 MybatisSqlSessionFactoryBean#getObject() 方法获取到 sqlSessionFactory 对象
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
     // TODO 使用 MybatisSqlSessionFactoryBean 而不是 SqlSessionFactoryBean
     MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
     factory.setDataSource(dataSource);
     factory.setVfs(SpringBootVFS.class);
     if (StringUtils.hasText(this.properties.getConfigLocation())) {
         factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
     }
     applyConfiguration(factory);
     if (this.properties.getConfigurationProperties() != null) {
         factory.setConfigurationProperties(this.properties.getConfigurationProperties());
     }
     
    ......
    
     // TODO 自定义枚举包
     if (StringUtils.hasLength(this.properties.getTypeEnumsPackage())) {
         factory.setTypeEnumsPackage(this.properties.getTypeEnumsPackage());
     }
     // TODO 此处必为非 NULL
     GlobalConfig globalConfig = this.properties.getGlobalConfig();
     // TODO 注入填充器
     if (this.applicationContext.getBeanNamesForType(MetaObjectHandler.class,
         false, false).length > 0) {
         MetaObjectHandler metaObjectHandler = this.applicationContext.getBean(MetaObjectHandler.class);
         globalConfig.setMetaObjectHandler(metaObjectHandler);
     }
     // TODO 注入主键生成器
     if (this.applicationContext.getBeanNamesForType(IKeyGenerator.class, false,
         false).length > 0) {
         IKeyGenerator keyGenerator = this.applicationContext.getBean(IKeyGenerator.class);
         globalConfig.getDbConfig().setKeyGenerator(keyGenerator);
     }
     // TODO 注入sql注入器
     if (this.applicationContext.getBeanNamesForType(ISqlInjector.class, false,
         false).length > 0) {
         ISqlInjector iSqlInjector = this.applicationContext.getBean(ISqlInjector.class);
         globalConfig.setSqlInjector(iSqlInjector);
     }
     // TODO 设置 GlobalConfig 到 MybatisSqlSessionFactoryBean
     factory.setGlobalConfig(globalConfig);
     return factory.getObject();
 }

MybatisSqlSessionFactoryBean#getObject() 执行懒加载策略,最后通过buildSqlSessionFactory() 方法创建 SqlSessionFactory 工厂类对象。

1.创建 MybatisXMLConfigBuilder 对象,调用其 parse() 方法去解析 XML 配置文件及 Mapper

2.解析获得的信息存储在 targetConfiguration 对象中,根据其信息创建 SqlSessionFactory 对象

3.MybatisXMLConfigBuilder#parse() 会去解析配置文件,最后会调用到其内部方法 mapperElement()。这个方法完成解析 Mapper工作,并将其添加到配置类 MybatisConfiguration 中

4.MybatisConfiguration#addMapper()方法其实是去调用 MybatisMapperRegistry#addMapper() 方法,其核心是MybatisMapperAnnotationBuilder#parse()

5.MybatisMapperAnnotationBuilder#parse() 方法真正开始完成 Mapper 接口中的方法与 SQL 语句的映射,其中 parseStatement()方法是解析 @Select/@Update 等注解写入的 SQL语句,而代码GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type)
通过 MaBatis-plus的 SQL 注入器完成 Mapper 方法与 SQL 语句的转化

@Override
 public void parse() {
     String resource = type.toString();
     if (!configuration.isResourceLoaded(resource)) {
         loadXmlResource();
         configuration.addLoadedResource(resource);
         final String typeName = type.getName();
         assistant.setCurrentNamespace(typeName);
         parseCache();
         parseCacheRef();
         SqlParserHelper.initSqlParserInfoCache(type);
         Method[] methods = type.getMethods();
         for (Method method : methods) {
             try {
                 // issue #237
                 if (!method.isBridge()) {
                 // 解析 @Select 注解写入的 SQL
                     parseStatement(method);
                     SqlParserHelper.initSqlParserInfoCache(typeName, method);
                 }
             } catch (IncompleteElementException e) {
                 // TODO 使用 MybatisMethodResolver 而不是 MethodResolver
                 configuration.addIncompleteMethod(new MybatisMethodResolver(this, method));
             }
         }
         // TODO 注入 CURD 动态 SQL , 放在在最后, because 可能会有人会用注解重写sql
         if (GlobalConfigUtils.isSupperMapperChildren(configuration, type)) {
             GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type);
         }
     }
     parsePendingMethods();
 }

6.AbstractSqlInjector#inspectInject() 会完成 BaseMapper 接口中提供的 17 个默认方法与 SQL 语句的转化,这部分主要通过 AbstractMethod#inject()方法完成

@Override
 public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {
     Class<?> modelClass = extractModelClass(mapperClass);
     if (modelClass != null) {
         String className = mapperClass.toString();
         Set<String> mapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration());
         if (!mapperRegistryCache.contains(className)) {
             List<AbstractMethod> methodList = this.getMethodList(mapperClass);
             if (CollectionUtils.isNotEmpty(methodList)) {
                 TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass);
                 // 循环注入自定义方法
                 methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo));
             } else {
                 logger.debug(mapperClass.toString() + ", No effective injection method was found.");
             }
             mapperRegistryCache.add(className);
         }
     }
 }

7.AbstractMethod#inject()方法并没有什么特别的操作,只是调用其子类实现 injectMappedStatement()方法。以 Insert#injectMappedStatement() 为例,其 SQL 语句的核心在于 SqlMethod 类,这个枚举类中缓存了可以动态拼接的 SQL 语句脚本,只需要填上参数 format 就可以得到 SQL 语句的执行脚本。以上过程结束,只需要将所有信息通过 addInsertMappedStatement()方法封装成 MappedStatement对象并将其加入到容器中,这样 Mapper接口方法调用时,就可以通过 动态代理 的方式找到其对应执行的 SQL 脚本,至此 SQL 语句准备及配置解析就完成了。最后拼接的 SQL 语句 脚本形式如下示例,实际执行数据库操作时会解析这个脚本完成变量替换,从而得到可执行的 SQL 语句

<script>
<choose>
 <when test="ew != null and ew.sqlFirst != null">
     ${ew.sqlFirst}
 </when>
 <otherwise></otherwise>
</choose>
SELECT
<choose>
 <when test="ew != null and ew.sqlSelect != null">
     ${ew.sqlSelect}
 </when>
 <otherwise>id,name,type</otherwise>
</choose>
FROM node
<if test="ew != null">
 <where>
     <if test="ew.entity != null">
         <if test="ew.entity.id != null">id=#{ew.entity.id}</if>
         <if test="ew.entity['name'] != null">AND name=#{ew.entity.name}</if>
         <if test="ew.entity['type'] != null">AND type=#{ew.entity.type}</if>
     </if>
     <if test="ew.sqlSegment != null and ew.sqlSegment != '' and ew.nonEmptyOfWhere">
         <if test="ew.nonEmptyOfEntity and ew.nonEmptyOfNormal">AND</if>
         ${ew.sqlSegment}
     </if>
 </where>
 <if test="ew.sqlSegment != null and ew.sqlSegment != '' and ew.emptyOfWhere">
     ${ew.sqlSegment}
 </if>
</if>
<choose>
 <when test="ew != null and ew.sqlComment != null">
     ${ew.sqlComment}
 </when>
 <otherwise></otherwise>
</choose>
</script>

SqlSessionFactory对象的创建需要回到

MybatisSqlSessionFactoryBean#buildSqlSessionFactory()方法中,很容易追踪到 MybatisSqlSessionFactoryBuilder#build()方法,最后其实是通过 SqlSessionFactoryBuilder#build()方法创建了一个 DefaultSqlSessionFactory 对象返回

Mapper 操作数据库的流程
MapperScan 注解通过 @Import(MapperScannerRegistrar.class) 引入扫描注册的类MapperScannerRegistrar,该类实现了ImportBeanDefinitionRegistrar接口并重写registerBeanDefinitions()方法,在该方法中注册了 MapperScannerConfigurer 类

2.MapperScannerConfigurer 是 Mapper接口的扫描配置类,实现了 BeanDefinitionRegistryPostProcessor 接口,其 postProcessBeanDefinitionRegistry()方法会在容器启动过程中被回调,通过 ClassPathMapperScanner#scan()方法完成 Mapper 的扫描注册

3.ClassPathMapperScanner#processBeanDefinitions() 将扫描到的 Mapper接口生成的对应 BeanDefinition 的 beanClass 属性替换为 MapperFactoryBean,这样每次获取 Mapper 实例实际是通过 MapperFactoryBean 的实例去获取

4.@Autowired 自动注入 Mapper 触发容器获取 bean 的方法,调用到 MapperFactoryBean#getObject()方法,最终调用到 sqlSessionTemplate#getMapper()方法

@Override
public <T> T getMapper(Class<T> type) {
    return getConfiguration().getMapper(type, this);
}

5.在这里插入代码片

MyBatis-plus 使用的配置类是MybatisConfiguration,最终调用到 MybatisMapperRegistry#getMapper()方法,这里就进入了动态代理获取 MapperProxy 实例的流程

6.MybatisMapperProxyFactory#newInstance()方法给自动注入返回一个 MybatisMapperProxy 代理对象

protected T newInstance(MybatisMapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
}

7.调用 Mapper 接口的方法触发代理对象的 MybatisMapperProxy#invoke(),此时根据 Mapper 对象被调用的方法生成 MybatisMapperMethod 对象,通过MybatisMapperMethod#execute()去真正地执行 SQL 语句,从而完成数据库操作。

@Override
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
     try {
         if (Object.class.equals(method.getDeclaringClass())) {
             return method.invoke(this, args);
         } else if (method.isDefault()) {
             return invokeDefaultMethod(proxy, method, args);
         }
     } catch (Throwable t) {
         throw ExceptionUtil.unwrapThrowable(t);
     }
     final MybatisMapperMethod mapperMethod = cachedMapperMethod(method);
     return mapperMethod.execute(sqlSession, args);
 }

总结:

相关工具如下:

分析:

小结:

主要讲述了接下来@Insert详解,请大家指正~

参考资料和推荐阅读

1.链接: 参考资料.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

执于代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值