多言数穷,不如守中。
1 前言
Mybatis 在日常的开发中是十分常用的 orm 框架,开发者通常在 mapper 包下定义接口,然后在 xml 文件中定义接口实现的 sql 语句,我们都知道 mapper 是一个接口,在 spring 容器中是如何管理这些mapper 接口并执行的呢?本文将结合实践操作来揭示其作用原理和运行机制。
2 spring 的扩展点
Spring
在创建一个 Bean
的时候,会通过注解或者配置的方式读取,并将其注册成一个BeanDefination
。但是 spring
注册时会排除抽象类或者接口, 所以对于 Mapper
接口的注入面临的第一个问题就是如何注入到 spring
容器中。
- 扩展点
ClassPathBeanDefinitionScanner#isCandidateComponent
通常情况下 Spring
通过 BeanDefinitionRegistryPostProcessor
来实现 Bean
的注册, 通常在 spring
容器初始化时实现这个接口,通过实现其 postProcessBeanDefinitionRegistry
方法来扫描需要注册的 Bean
。 但是在 mybatis
中, mapper
的扫描和注册实现方式有所不同,其实现通过 ClassPathMapperScanner
来处理, 它继承了 ClassPathBeanDefinitionScanner
(其父类ClassPathScanningCandidateComponentProvider
,其中有 isCandidateComponent
方法) 类,重写了其父类中的 isCandidateComponent
方法,使得 Mapper
接口的注入得以实现,如下图所示:
- 扩展点
FactoryBean#getObject
首先我们需要了解一下 BeanFactory
和 FactoryBean
的区别,可以看之前的文章介绍。在 mybatis
的 SqlSessionFactoryBean
中,其通过实现 FactoryBean
接口,在 getObject
方法中创建 jdk
动态代理,其创建的对象是 SqlSessionFactory ,通过 SqlSessionFactory
可以获取 SqlSession
, 即获取到了一个操作数据库的连接。具体如下图所示:
如下图所示,即 SqlSessionFactoryBean
和 SqlSessionFactoryBuilder
。
3 mybatis 如何获取配置
如前文所示,配置文件的加载可以通过 SqlSessionFactoryBuilder.build
来实现配置的解析和加载。配置文件默认是 xml 格式,通过 XMLConfigBuilder.parseConfiguration
方法可以实现配置的解析,通常配置文件包括数据库的连接,实体类的配置,环境信息以及 mapper
的配置。
SqlSessionFactory
本身是一个接口,DefaultSqlSessionFactory
则是实现了 SqlSessionFactory
的实现类,保存好 configuration
之后返回,就得到了我们开头需要的 SqlSessionFactory
实例。
解析配置文件如下图所示,parse
方法返回一个 Configuration
对象。其中parsed
是一个布尔类型成员变量,默认值是 false
,其目的是为了防止多线程情况下该方法被二次调用。
4 mybatis 中获取 mapper
mapperElement
方法用来解析 mappers
文件,通常 mapper
有四种方式来配置,即 package、resources、url、class
。在 MapperRegistry
中的 addMapper
方法中,通过 MapperAnnotationBuilder.parse
用来解析 mapper
文件中的方法、注解等信息。
综上,mybatis
添加 mapper
的流程如下图所示:
5 mybatis 执行sql流程
之前已经介绍了 mapper
的获取过程,那么具体的 sql
要怎么去执行呢? 经过之前的流程,我们已经获取到了 SqlSessionFactory
,我们可以通过 openSession
方法从工厂中获得 SqlSession
的实例对象。如下图所示,mybatis
一共有四种执行器,分别是 simple、reuse、batch
, 默认使用的是 simple
执行器,如果使用了二级缓存,则会创建 CacheExecuter
执行器。
通过以上的操作,可以获得一个 DefaultSqlSession
对象。通过它就可以执行 sql
语句了。SqlSession
提供了在数据库执行 SQL
命令所需的所有方法。通过SqlSession
实例来直接执行在 xml
文件中的语句,以 selectOne
方法为例,进入该方法后发现,最终会调用到 selectList
方法。
如下图所示,在 configuration
中可以通过 getMappedStatement
方法获取 MappedStatement
对象,然后再调用 executor.query
方法,调用 query 方法之前会执行 wrapCollection
方法,用于保存 sql
语句中传入的参数。
通过 getBoundSql
方法可以获取要执行的 sql
, 如果开启缓存,那么 Cachekey
就需要执行缓存策略,通过 id, offset, limit,sq
l 组成一个唯一的 key
,然后调用 query
方法,执行完查询后,最后调用 ResultSetHandler.handleCursorResultSets
来封装结果集并返回。其具体的执行流程如下图所示:
6 总结
在本文中,主要介绍了 spring
如何使用其扩展点来注入 mapper
,以及 myabtis
的配置文件加载流程以及 sql
的执行流程, 加深了对 mybatis
的架构理解。