文章目录
- springboot 中 mybatis-plus 加载流程
- MapperScannerConfigurer
- 扫描并注册 Mapper BeanDefinition
- MapperFactoryBean
- SqlSessionFactory
- 解析 mapper.xml
- MybatisMapperRegistry 中注册 mapper 代理工厂
- 注入 curd 动态 sql
- SqlSessionTemplate
- sqlSession 代理
- 获取 Mapper 对象
- 总结
之前一直在用 mybatis-plus(mybatis 的增强版, 简化开发),只觉得 mybatis-plus 用起来很方便,但一直没了解其实现原理。于是最近开始学习一下。
平时使用 mybatisplus 时都是定义一个 Mapper 接口继承下 BaseMapper 就直接使用,并没有实现类。
那么现在就开始探究一下 mybatisPlus 是怎么实现的, 首先来了解 mybatisPlus 的加载流程。
版本:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
springboot 中 mybatis-plus 加载流程
首先是了解 mybatis-plus 的加载流程。
从 mybatis-plus 源码中的 spring.factories 文件中我们可以了解到,其加载入口为 MybatisPlusAutoConfiguration
。
点进去这个类可以发现里面有几个核心的 bean:
- SqlSessionFactory
- SqlSessionTemplate
- MapperScannerConfigurer
/*
* MybatisPlusAutoConfiguration.java
*/
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
// mybatisplus 重写的 SqlSessionFactoryBean
MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
factory.setDataSource(dataSource);
applyConfiguration(factory); // setConfiguration
// set :interceptors,typeHandlers,
// mapperLocations,typeEnumsPackage,globalConfig 等
// ...
return factory.getObject();
}
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
ExecutorType executorType = this.properties.getExecutorType();
if (executorType != null) {
return new SqlSessionTemplate(sqlSessionFactory, executorType);
} else {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
// 如果不存在 MapperScannerConfigurer bean (对应没有使用 @MapperScan 注解) 则自动注册一个
@Configuration
@Import(AutoConfiguredMapperScannerRegistrar.class)
@ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class})
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
//....
}
// 自动注册一个 MapperScannerConfigurer beanDefinition
public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar {
// ...
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 定义 MapperScannerConfigurer
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
// addPropertyValue: basePackage等
// ...
registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
}
}
MapperScannerConfigurer
首先看 MapperScannerConfigurer 这个 bean,这是一个扫描 mapper 的配置类,内部使用 ClassPathMapperScanner
根据配置扫描 mapper 接口并将接口注册为 MapperFactoryBean。
创建 MapperScannerConfigurer bean 的方式一般有两种:(当然也可以自己手动声明创建)
- 一种是默认的使用
AutoConfiguredMapperScannerRegistrar
(如上 AutoConfig 中手动注册的 beanDefinition)扫描 spring 默认包下的带@Mapper
注解的接口。 - 一种是
MapperScannerRegistrar
通过解析@MapperScan
注解的属性在 spring 中注册MapperScannerConfigurer
的 beanDefinition。
- 关于
@MapperScan
注解:默认扫描注解的类所在的包下所有接口。主要提供一下功能
- 配置扫描规则:指定扫描的包,指定扫描携带的注解,指定继承的接口等。
- 指定 sqlSessionFactory 等
- 指定 自定义的 MapperFactoryBean
扫描并注册 Mapper BeanDefinition
MapperScannerConfigurer#postProcessBeanDefinitionRegistry
- 创建
ClassPathMapperScanner
对象(继承ClassPathBeanDefinitionScanner
),设置相关属性(参考MapperScan
属性),根据设置的属性注册扫描过滤器,开始执行扫描
// MapperScannerConfigurer.java
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// set 相关属性 扫描过滤规则、指定 sqlSessionxxx。。
...
// 根据 set 的属性,注册扫描过滤器
scanner.registerFilters();
// 扫描 mapper 接口,并注册 beanDefinition, 底层是 doScan
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
ClassPathMapperScanner#doScan
- 根据配置的过滤器扫描相关接口,并注册 beanDifinition
- 修改扫描到的 beanDefinition 类型为
MapperFactoryBean
(或@MapperScan
指定的自定义类型)
// ClassPathMapperScanner.java
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
// 调用父类 ClassPathBeanDefinitionScanner 方法扫描并注册 beanDifinition
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
+ "' package. Please check your configuration.");
} else {
// 对 mapper 的 beanDefinition 进行处理: 修改 beanClass 类型等。
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
...
// 构造方法中传入原 mapper bean 类型
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
// 修改 bean 类型为 MapperFactoryBean
definition.setBeanClass(this.mapperFactoryBeanClass);
... // set sqlSessionFactory, sqlSessionTemplate (如果存在的话)
// 设置懒加载
definition.setLazyInit(lazyInitialization);
}
}
MapperFactoryBean
前面可知,扫描 Mapper 后会将其 beanDefinition.beanClass 修改为 MapperFactoryBean
. 所以在后续 spring 将 mapper bean 初始化时,会通过调用 MapperFactoryBean.getObject
获取其对象。
查看源码可以发现,最终是调用 sqlSession 的 getMapper 方法获取 mapper 对象。具体实现后面再看。
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
SqlSessionFactory
这里的 sqlSessionFactory 是 mybatis 的用来获取 sqlSession (用来执行 sql 管理事务的对象)的工厂类. 里面主要提供 openSession 的一些列重载方法。
创建 sqlSessionFactory 的逻辑在 MybatisSqlSessionFactoryBean#buildSqlSessionFactory
方法中,里面主要是
- 设置前面配置的属性:globalConfig,typeHandlers,interceptor…
- 解析 mapper.xml 并保存到 configuration 中
- 创建 sqlSessionFactory
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
final Configuration targetConfiguration;
if (this.configuration != null) { // AutoConfig 里面 set 的 configuration
targetConfiguration = this.configuration;
...
}
...
// 加载前面的配置 并 set 到 targetConfiguration 中,
// 如:set globalConfig, register typeHandlers, addInterceptor, parse xmlMapper
...
// 解析 mapper.xml, 并将解析的结果存在 targetConfiguration 中
for (Resource mapperLocation : this.mapperLocations) {
...
Builder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
xmlMapperBuilder.parse();
...
}
...
// 构建 SqlSessionFactory, 实现类为 DefaultSqlSessionFactory
SqlSessionFactory sqlSessionFactory = new MybatisSqlSessionFactoryBuilder().build(targetConfiguration);
、
return sqlSessionFactory;
}
解析 mapper.xml
XMLMapperBuilder 主要用于解析 mapper.xml 文件,入口为 XMLMapperBuilder#parse
, XMLMapperBuilder 会解析 mapper.xml 文件中配置的 statements、resultMap、parameter 等信息, 并将其存放于 Configuration 的 对应 map 中。
// XMLMapperBuilder.java
public void parse() {
// resource: file /../xxxMapper.xml 判断是否加载过
if (!configuration.isResourceLoaded(resource)) {
// 1 解析 mapper.xml 文件
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
// 2 注册 Mapper
// 底层调用 MybatisMapperRegistry#addMapper 注册 Mapper
bindMapperForNamespace();
}
// 前面没解析完成的 继续解析, 看代码主要是针对前面解析出错的
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
解析 mapper.xml 后 Configuration 对象示例:
当前只包含 mapper.xml 中的 sql 语句。
<–, 里面解析 注解 sql 和 mybatisPlus 的默认 sql–>
MybatisMapperRegistry 中注册 mapper 代理工厂
前面 XMLMapperBuilder 解析完 xml 文件后,就会在 MybatisMapperRegistry
中添加对应的 Mapper 代理工厂 MybatisMapperProxyFactory
。同时使用 MybatisMapperAnnotationBuilder
解析 mapper 接口中使用注解写的 sql 语句。
这里的 MybatisMapperProxyFactory
就是用来获取 Mapper 代理(MybatisMapperProxy
)的工厂类。 在调用 Mapper 中方法的时候其实就是调用的 MybatisMapperProxy#invoke
方法。
备注:MybatisMapperRegistry
同时还有另一个方法 getMapper(Class<T> type, SqlSession sqlSession)
, 用来获取 Mapper 代理,后面讲。
// MybatisMapperRegistry.java
// type 为 mapper 接口
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
// TODO 如果之前注入 直接返回
return;
}
boolean loadCompleted = false;
try {
// 注册 Mapper 的代理工厂
knownMappers.put(type, new MybatisMapperProxyFactory<>(type));
// 再解析 Mapper 接口中使用注解写的 sql 语句
MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);
// 里面会继续调用 AbstractSqlInjector#inspectInject 注入 mybatisPlus 的动态 curd 方法
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
<–##### MybatisMapperAnnotationBuilder 解析注解 sql & 注入 动态 sql–>
注入 curd 动态 sql
另 MybatisMapperAnnotationBuilder
在解析完注解 sql 后,会注入 mybatis-plus 的的 curd 动态 sql。平时调用 mybatis-plus 的 BaseMapper 的方法就是使用的这里注入的动态 sql。
// MybatisMapperAnnotationBuilder.java
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
...
// 遍历Mapper 接口中的方法,解析方法上的注解 sql(如果存在的话)
for (Method method : type.getMethods()) {
...
parseStatement(method);
...
}
...
// 如果继承了 baseMapper,就注入 CURD 动态 SQL
if (GlobalConfigUtils.isSupperMapperChildren(configuration, type)) {
parserInjector();
}
...
}
parsePendingMethods();
}
// 注入动态 sql, AbstractSqlInjector
void parserInjector() {
GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type);
}
AbstractSqlInjector 注入的 sql 默认为 DefaultSqlInjector 中的方法(也就是 BaseMapper 中的方法)。 注入的方式为:
- 根据不同方法的模板构建对应的 sql 脚本(同 xml),并填充对应的数据表和实体类的字段
备注:这里的数据表字段是通过 TableInfoHelper
根据实体类字段和配置推断出来的,如根据 @TableField
/@TableId
注解指定字段名,或根据驼峰下划线转换规则推断。
注入的详细代码参考: AbstractSqlInjector#inspectInject
这里贴一段注入的 update 的 sql script。 (其中 et
为更新的实体类,ew
为查询的条件。)
<script>
UPDATE user <set>
<if test="et != null">
<if test="et['name'] != null">name=#{et.name},</if>
<if test="et['phone'] != null">phone=#{et.phone},</if>
<if test="et['age'] != null">age=#{et.age},</if>
</if>
<if test="ew != null and ew.sqlSet != null">${ew.sqlSet}</if>
</set>
<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['phone'] != null"> AND phone=#{ew.entity.phone}</if>
<if test="ew.entity['age'] != null"> AND age=#{ew.entity.age}</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>
SqlSessionTemplate
线程安全、Spring管理的、g,以确保实际使用的SqlSession是与当前Spring事务关联的。此外,它还管理会话生命周期,包括根据Spring事务配置在必要时关闭、提交或回滚会话。
sqlSession 代理
SqlSessionTemplate
实现了 SqlSession
接口,但是内部持有一个 sqlSessionProxy 代理对象,最后都是调用的代理对象的方法。 而代理对象中最终调用的 sqlSession 也是通过 sqlSessionFactory.openSession
来获取的。只不过在 openSession 前会从 spring 事务同步管理器中获取一遍,不存在才创建一个新的 sqlSession,并且再执行完成后关闭或释放(引用数量-1)。
这样就可以在需要使用 sqlSession 时直接使用 sqlSessionTemplate,而不是需要每次都通过 sqlSessinFactory 获取 sqlSession,也不需要考虑 sqlSesion 的关闭。同时保证了在同一个 spring 事务中使用同一个 sqlSession 对象。
相关代码如下:
public class SqlSessionTemplate implements SqlSession, DisposableBean {
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
// ...
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
// 创建一个 sqlSession 的代理, 实现为一个内部类
this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class }, new SqlSessionInterceptor());
}
// 数据库的相关操作均由代理对象实现, 但是不支持 commit,因为代理对象中户实现
@Override
public <T> T selectOne(String statement) {
return this.sqlSessionProxy.selectOne(statement);
}
...
// 前面提到的 getMapper 方法,最终到 Configuration 中获取
@Override
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
/*
* sqlSession 的代理实现
*/
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 获取真正的 sqlSession,逻辑参考:SqlSessionUtils#getSqlSession)
// 从 spring 的 TransactionSynchronizationManager 事务同步管理器 (中的 threadlocal) 获取 sqlSession
// 不存在则通过 sqlSessionFactory.openSession 创建新的 sqlSession 对象,并保存到 spring 中
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
try {
// 使用 sqlSession 执行对应方法
Object result = method.invoke(sqlSession, args);
// ...
return result;
} catch (Throwable t) {
// unwrapThrowable
// SqlSessionUtils.closeSqlSession
throw unwrapped;
} finally {
// 如果是 spring 管理的则 release(引用数量-1), 不是则 close
// SqlSessionUtils.closeSqlSession
}
}
}
}
- SqlSessionUtils
处理MyBatis SqlSession生命周期,可以在 spring (TransactionSynchronizationManager)注册和获取 sqlSession。 - TransactionSynchronizationManager
spring 管理每个线程的资源和事务同步的中央委托。里面使用维护了一些 ThreadLocal 用户保存想成相关的事务信息。
public abstract class TransactionSynchronizationManager {
private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources");
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal<>("Transaction synchronizations");
private static final ThreadLocal<String> currentTransactionName = new NamedThreadLocal<>("Current transaction name");
private static final ThreadLocal<Boolean> currentTransactionReadOnly = new NamedThreadLocal<>("Current transaction read-only status");
private static final ThreadLocal<Integer> currentTransactionIsolationLevel = new NamedThreadLocal<>("Current transaction isolation level");
private static final ThreadLocal<Boolean> actualTransactionActive = new NamedThreadLocal<>("Actual transaction active");
// ...
}
获取 Mapper 对象
源码中可以看到, 前面提到的 sqlSessionTempalte 的 getMapper 方法中也是调用 configuration.getMapper
方法来获取 mapper 对象的. 而configuration 里面又是通过 MybatisMapperRegistry
获取 mapper。
前面解析 mapper 时提到了 MybatisMapperRegistry
提供了一个 getMapper 方法用来获取 mapper 代理对象。
在这个 getMapper 方法中之前注册的代理工厂 MybatisMapperProxyFactory
使用 通过 MybatisMapperProxy
生成了 mapper 的代理对象。
// sqlSessionTempalte
@Override
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
// MybatisConfiguration
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mybatisMapperRegistry.getMapper(type, sqlSession);
}
// MybatisMapperRegistry
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// knownMappers 为前面解析 mapper 后注册的 mapper 代理工厂对象
final MybatisMapperProxyFactory<T> mapperProxyFactory = (MybatisMapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MybatisPlusMapperRegistry.");
}
try {
// 传入 sqlSession 创建代理对象
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
// MybatisMapperProxyFactory
public T newInstance(SqlSession sqlSession) {
final MybatisMapperProxy<T> mapperProxy = new MybatisMapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
总结
mybatisPlus 的整个加载过程概括如下:
-
MapperScannerConfigurer
扫描 mapper 接口,并在 spring 中注册 deanDefinition,类型为MapperFactoryBean
-
SqlSessionFactory
解析 mapper.xml 和 mapper 接口 中的 sql 语句保存到 Configuration 中,同时加入 mybatisPlus 提供的动态 sql。 最后注册对应 mapper 的MybatisMapperProxyFactory
。 -
SqlSessionTemplate
使用SqlSessionInterceptor
代理实现一个线程安全的 spring 管理的 SqlSession,并最终通过MybatisMapperProxyFactory
获取 mapper 的代理对象MybatisMapperProxy
.