MyBatis接口是如何执行SQL的?- 总结篇

672 阅读3分钟

本文对MyBatis接口是如何执行SQL做一个总结,其实我们是带着这几个问题来追踪答案的:

  1. mapper接口是如何被spring发现的?
  2. mapper接口是如何可以被自动注入的?
  3. mapper接口的方法是为什么能执行到对应sql的?

整体流程

简单整理下流程,mapper接口到执行sql可以分为2个大步骤:

  1. mapper代理对象的创建。
  2. 代理的mapper的执行。

mapper代理对象的创建

  • 首先,springboot默认只会扫描配置的包或者默认包下的类文件,并配置@Configuration@Component等这些注解的类。既然mybatis的mapper接口也能被注入,那么一定有扫描了这些mapper接口。
    • @MapperScan入口,发现该注解@Import(MapperScannerRegistrar.class)一个ImportBeanDefinitionRegistrar配置类。
    • 该配置类MapperScannerRegistrar.class作用是向Spring的BeanDefinitionRegistry注册MapperScannerConfigurer类。
    • MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,所以在其初始化之前,会调用postProcessBeanDefinitionRegistry,在该方法里面创建了ClassPathMapperScanner对象,并由其扫描了mapper接口并生成BeanDefinition注册到BeanDefinitionRegistry里面去。
    • 至此,Mapper的BeanDefinition加载工作完成。总结来说:@MapperScan的作用就是扫描mapper接口并创建对应class为MapperFactoryBeanBeanDefinition
  • 下面,我们在使用service引用mapper接口的时候,就会去BeanDefinition找对应的来进行初始化工作,因为在MapperScannerConfigurer的扫描并注册BeanDefinition工作中,设置这些mapper接口的class是MapperFactoryBean,所以这些bean的工作是交给其getObject()方法生成的。
  • Spring会通过MapperFactoryBean来获取对应的mapper接口的代理类:获取方式是通过sqlSession.getMapper()获取到的
    • getMapper的过程就是创建动态代理对象MapperProxy的过程。

代理的mapper的执行

  • 我们创建好的MapperProxy对象,内部都持有一个sqlSession对象。

    • sqlSession对象是我们自动配置装载的SqlSessionFactory同时创建的,在该创建过程会将sql以及mybatis相关的一些配置信息装载到一个configuration属性中。
    • 这个configuration属性内部又有2个很重要的属性:mappedStatementsmapperRegistry
      • mapperRegistry:内部包含一个knownMappers的map:key为接口类,value为MapperProxyFactory对象,该工厂对象作用是通过JDK动态代理创建MapperProxy对象。
      • mappedStatements:也是一个map:key为接口类+方法名/方法名,value为对应的sql的信息封装的MappedStatement对象。
  • 除了SqlSession对象外,还有一个MapperMethodInvoker的map,用来缓存mapper接口的所有的方法。这个是懒加载的,每次执行mapper方法的时候,才会封装对应的Invoker并放入缓存。

  • 当调用mapper的方法的时候,会先去MapperMethodInvoker缓存中获取,如果没有则调用cachedInvoker创建,一般创建的Invoker是PlainMethodInvoker的实例。每个invoker内部都包含一个MapperMethod对象,所以mapper的方法都被封装在这个对象里面,Invoker只是一个壳子。

  • 执行invoker的invoke方法,即执行mapperMethod的execute方法,该方法会依据sql的特性,调用SqlSession对应的方法,以selectList为例:

    MappedStatement ms = configuration.getMappedStatement(statement);
    return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    

    可以看到,我们还是从SqlSession的configuration中拿取到sql的相关信息,然后交个Executor去执行。

    这一步就是将接口的和sql对应关系找到的关键一步!

  • 后面Executor的执行过程,就是对jdbc的调用和结果的封装过程了,就不做赘述了。

图例

下面通过2个图例来展示这2个过程:

mapper代理对象的获取

image-20210812145723007.png

mapper代理对象执行sql

image-20210812152456771.png

总结

简单汇总下,mapper接口是如何调用sql的?

  • @MapperScan会扫描所有的mapper接口并注册类为MapperFactoryBean的BeanDefinition。
  • Spring进行自动注入的时候,会注入MapperFactoryBean#getObject返回的Bean。
  • getObject返回的是从sqlSession.getMapper获取的代理MapperProxy对象。
  • 此时重点Spring容器中的2个东西:mapper的代理bean,sqlSession的bean,这2个是紧密耦合的。
  • MapperProxy的调用就是从SqlSession中的configuration里面的MappedStatement的map获取sql信息,然后交给executor去执行。
  • executor最终会一步步的走到jdbc的代码逻辑里面。