本文对MyBatis接口是如何执行SQL做一个总结,其实我们是带着这几个问题来追踪答案的:
- mapper接口是如何被spring发现的?
- mapper接口是如何可以被自动注入的?
- mapper接口的方法是为什么能执行到对应sql的?
整体流程
简单整理下流程,mapper接口到执行sql可以分为2个大步骤:
- mapper代理对象的创建。
- 代理的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为MapperFactoryBean
的BeanDefinition
。
- 从
- 下面,我们在使用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个很重要的属性:
mappedStatements
和mapperRegistry
- mapperRegistry:内部包含一个
knownMappers
的map:key为接口类,value为MapperProxyFactory
对象,该工厂对象作用是通过JDK动态代理创建MapperProxy
对象。 - mappedStatements:也是一个map:key为接口类+方法名/方法名,value为对应的sql的信息封装的
MappedStatement
对象。
- mapperRegistry:内部包含一个
-
除了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代理对象的获取
mapper代理对象执行sql
总结
简单汇总下,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的代码逻辑里面。