首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

springboot环境下mybatis映射器自动注册的原理

先前我们讲了springboot 集成mybatis的过程,只需要通过几步简单的配置,我们就能够通过操作我们的mapper映射器来完成对数据库的操作。不知道大家是否会产生这样的疑惑:我写的映射器只是一个自定义的接口,为啥我就能直接用它来对数据库进行增删改查呢?接下来咱们就来探讨一下这块的原理。

由于mybatis相关的原理涉及的东西较多,所以咱们就对内部的知识点进行逐个的讲解。本篇主要讲解下mybatis starter的作用,以为映射器实例的注册过程。另外,本篇将基于我之前写的《springboot集成mybatis》文章,如果有还没看过的小伙伴,可以先去我的历史文章中查看下。

1、mybatis-spring-boot-starter的自动配置类

我们用springboot集成mybatis时,会在pom文件中引入下面的依赖:

引入之后,maven就会自动帮我们导入mybatis-spring-boot-autoconfigure包。看这个名字,我们能猜想到这是个自动配置包,那我们就免不了要去看下这个jar包里面的META-INF/spring.factories文件了,该文件有如下内容:

MybatisAutoConfiguration这个配置类帮我们以下功能:

(1)往spring容器注册SqlSessionFactory和SqlSessionTemplate

(2)往spring容器注册AutoConfiguredMapperScannerRegistrar,后面需要它来完成标记有@Mapper注解的映射器的注册工作

为方便大家预览,此处呈现MybatisAutoConfiguration的代码如下:

2、为映射器接口生成BeanDefinition对象

MybatisAutoConfiguration的内部类MapperScannerRegistrarNotFoundConfiguration通过@Import的方式,将AutoConfiguredMapperScannerRegistrar注册到了spring容器中,代码如下所示:

AutoConfiguredMapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口。我在之前的文章中说过,通过@Import方式导入的类,都可以被当做一个配置类。AutoConfiguredMapperScannerRegistrar只完成了一件事,就是将MapperScannerConfigurer注册到了spring容器中,当然这时spring中的MapperScannerConfigurer还只是一个BeanDefinition对象。那么MapperScannerConfigurer又是用来干嘛的呢?

2.1、MapperScannerConfigurer映射器扫描器的配置器

MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,在它的postProcessBeanDefinitionRegistry方法中,它通过手动创建ClassPathMapperScanner对象,由ClassPathMapperScanner对象来完成映射器的注册工作。此处简单说下BeanDefinitionRegistryPostProcessor接口,它的作用就是提供一个拓展点,允许我们实现该接口,然后覆盖它的postProcessBeanDefinitionRegistry方法,并在该方法中完成BeanDefinition对象的注册工作。记住哦,是BeanDefinition对象,spring在初始化bean的阶段就会根据BeanDefinition对象来完成bean的初始化工作。

2.2、ClassPathMapperScanner类路径映射器扫描器

它的全路径是org.mybatis.spring.mapper.ClassPathMapperScanner,专门用来扫描类路径下指定包目录下标有@Mapper注解的映射器接口,然后为我们的每一个映射器接口都生成一个BeanDefinition对象,存放到spring容器中。其中每一个映射器接口对应的BeanDefinition对象的bean类型为MyFactoryBean类型,代码如下:

其中mapperFactoryBeanClass是ClassPathMapperScanner的成员变量,它的定义如下:

后续spring在初始化bean的阶段,会调用MyFactoryBean的getObject方法生成我们的映射器接口实例。

3、初始化bean阶段执行的流程

经过前面的步骤,spring容器中已经有了完整的BeanDefinition对象列表,接下来spring会继续执行,根据这些BeanDefinition对象生成实际的bean对象。

3.1、生成SqlSessionFactory的bean实例对象

spring容器中存储着SqlSessionFactoryBean的bean定义信息,spring初始化bean的时候,会根据该bean定义信息,调用它的getObject方法,生成真正的sqlSessionFactory对象。在这过程中,会涉及到下列操作:

(1)生成mybatis的全局Configuration对象,configuration对象可以是根据mybatis的xml配置文件解析而来

(2)如果有自定义的mybatis插件拦截器plugins,则将它们加到configuration的拦截器列表。

(3)如果有自定义的类型处理器typeHandlers,则将它们加到configuration的类型处理器注册中心对象中

(4)根据我们在application.yml文件中配置的mybatis.mapper-locations属性值,使用XMLMapperBuilder解析对应路径下的xml格式的mybatis映射文件,根据映射文件中的命名空间namespace的值,找到对应的类(或接口)的class对象,并将其加入到configuration的映射器注册中心mapperRegistry。而且针对映射文件中的每个sql语句,都会将它解析成一个MappedStatement对象,并将这个MappedStatement对象加入到configuration的mappedStatements(Map类型)对象中,mappedStatements的key就是每个MappedStatement对象的id,而id的值就是映射文件中namespace的值+"."+sql语句的id。我们以orderMapper.xml文件中的findbyId这个映射语句为例,它被解析后生成的MappedStatement对象id就是com.xk.mybatis.springboot.mapper.OrderMapper.findById

核心代码如下:

其中xmlMapperBuilder.parse()的实现如下,详情大家可以自己去看一下:

3.2、生成SqlSessionTemplate的bean实例对象

SqlSessionTemplate实现了SqlSession接口,内部维护了一个sqlSessionProxy对象,而sqlSessionProxy本身是通过jdk动态代理创建的一个SqlSession的代理对象。SqlSessionTemplate的构造方法如下:

其中SqlSessionInterceptor类的实现如下:

当我们在代码中调用sqlSessionTemplate的select方法时,会执行如下的方法:

对sqlSessionTemplate的所有操作,都会由它内部的sqlSessionProxy代理对象完成,而这又会进一步执行上面SqlSessionInterceptor类的invoke方法。invoke内部就是通过sqlSessionFactory对象创建新的sqlsession对象,然后由这个新的sqlSession对象完成对数据库的操作。

3.3、生成映射器接口对应的bean实例对象

前面我们说了,spring容器中存储的关于映射器接口的bean定义信息指定的bean的类型是MyFactoryBean类型,MyFactoryBean是一个工厂bean,spring根据映射器接口的BeanDefinition对象初始化bean的时候,就会调用MyFactoryBean的getObject方法生成真正的映射器接口实例。getObject方法实现如下:

其中getSqlSession()就是返回我们3.2节生成的sqlSessionTemplate对象。

3.3.1、进入SqlSessionTemplate的getMapper方法,

其中getConfiguration()就是3.1节中创建SqlSessionFactory对象时生成的mybatis全局的configuration对象。

3.3.2、进入Configuration类的getMapper方法

3.3.3、进入MapperRegistry的getMapper方法

上面这里会获取MapperProxyFactory对象,MapperProxyFactory通过调用它自身的newInstance方法,创建映射器接口(mapper接口)的代理对象,

3.3.4、进入MapperProxyFactory的newInstance方法

代码如下所示:

到这里,我们自定义的映射器接口的实例对象(代理对象)就通过JDK动态代理的方式创建成功了,接下来spring就会将其注入到容器中,供我们在应用系统层面直接使用,比如在我们的OrderService中直接调用OrderMapper。有一点要说下,其实映射器接口的实例对象的真实类型是org.apache.ibatis.binding.MapperProxy,经过了动态代理这一步,我们可以理解为这个代理对象也已经实现了映射器接口,比如实现了我们的OrderMapper接口,所以spring才能帮我们把代理对象注入到我们的OrderService中。

4、执行映射器接口方法

本部分我们以在orderService中调用orderMapper.findById()方法为例,讲解映射器接口方法的执行流程。

上面3.3.4部分我们提到:spring容器中映射器实例对象的真实类型是org.apache.ibatis.binding.MapperProxy,那么MapperProxy又是个啥呢?

4.1、MapperProxy

MapperProxy实现了InvocationHandler接口,上面3.3.4部分newInstance方法创建MapperProxy对象,就是为了通过JDK自身的Proxy类创建映射器接口的代理对象(这就是jdk的动态代理的实现)。当被代理的映射器接口的方法被调用时,就会执行MapperProxy内部的invoke方法,invoke内部实现如下:

4.2、PlainMethodInvoker

因为我们映射器接口里面一般不会写默认方法,所以上面MapperProxy的invoke方法中cachedInvoker(method)方法一般会返回一个PlainMethodInvoker对象,并且给PlainMethodInvoker传递了一个MapperMethod对象。

PlainMethodInvoker的invoke方法会调用MapperMethod.execute方法,invoke实现如下:

4.3、MapperMethod

MapperMethod是对我们当前调用的映射器接口内部方法的一个包装,上面PlainMethodInvoker通过调用new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())构造器来创建MapperMethod对象,我们看下它的构造方法实现:

构造函数的参数mapperInterface就是我们的映射器接口名,我们观察到构造函数里面创建了一个SqlCommand对象,我们来看下SqlCommand的实现:

SqlCommand是MapperMethod的一个内部类,用来维护映射器接口中的方法的sql操作类型,它会通过获取映射器接口的全限定名+"."+接口方法名来从configuration中获取MappedStatement对象,并把MappedStatement对象的名称赋值给SqlCommand对象的name属性,把MappedStatement对象的sql操作类型(select、update、insert或delete)赋值给SqlCommand对象的type属性。为啥要提这两个属性呢?因为下面要用到。

MapperMethod的execute方法实现如下:

在execute中,我们又看到了一个熟悉的身影:sqlSession,它就是我们前面讲的sqlSessionTemplate对象。我们调用orderMapper.findById方法时,就会执行到下面这里:

前面提到过command.getName()的值就是MappedStatement对象的id值,再细一点说就是:映射器接口的全限定名+"."+接口方法名,而执行sqlSession.selectOne的流程就相当于直接操作sqlSessionTemplate了。

5、总结

本篇主要讲了映射器自动注册的流程,以及调用映射器接口方法时涉及到的一些流程。我们也提到了一个很重要的概念:MappedStatement。映射文件中的每个sql映射语句都对应一个MappedStatement对象,而通过映射器接口的方法名,我们又能找到唯一的MappedStatement对象,进而执行最终的sql映射语句。

结束语

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20230515A07LFU00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券