Spring自定义Mybatis数据源,到底发生了什么?
在之前的文章中,我们为了在 Spring
中使用多数据源,引入了如下的数据源代码 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class PostgreSQLConfig {
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
public SqlSessionFactory sqlSessionFactory( DataSource dataSource) throws Exception {
MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setPlugins(mybatisConfig.pageInterceptor());
GlobalConfig globalConfig = GlobalConfigUtils.defaults();
globalConfig.setMetaObjectHandler(mybatisConfig.MyMetaObjectHandler());
bean.setGlobalConfig(globalConfig);
return bean.getObject();
}
public SqlSessionTemplate sqlSessionTemplate( SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
public DataSourceTransactionManager dataSourceTransactionManager( DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
它到底是怎么样生效的呢?发生了什么?结合这篇能对之前的文章有更好的了解。
以
PostgreSQLConfig 为例:完整走一遍 MyBatis-Plus
的启动与运行流程
这个配置类非常典型,它把我们前面讨论的所有概念都串起来了。下面以它为线索,从 Spring 启动到运行时,完整走一遍。
一、先看这个配置类做了什么
它做了四件事:
- 创建 PostgreSQL 的
DataSource(连接池)。 - 创建
SqlSessionFactory,用MybatisSqlSessionFactoryBean(不是原生 MyBatis 的SqlSessionFactoryBean),并绑定 PostgreSQL 数据源。 - 创建
SqlSessionTemplate,绑定上一步的SqlSessionFactory。 - 创建
DataSourceTransactionManager,绑定 PostgreSQL 数据源。
同时,@MapperScan 告诉 Spring:指定包下的 Mapper
接口,使用名为 pgsqlSqlSessionTemplate 的
Template。
二、启动阶段:Spring 容器的处理顺序
第 1 步:解析
@MapperScan
Spring 在解析配置类时,发现 PostgreSQLConfig 上有
@MapperScan,于是通过 @Import 机制引入
MapperScannerRegistrar:
1 | ConfigurationClassPostProcessor 解析 PostgreSQLConfig |
此时还没有任何 Mapper 被扫描,也没有任何 SQL 被解析。
第 2
步:MapperScannerConfigurer 扫描 Mapper
MapperScannerConfigurer 是
BeanDefinitionRegistryPostProcessor,在所有 BeanDefinition
加载完成后触发:
1 | MapperScannerConfigurer.postProcessBeanDefinitionRegistry() |
关键点:因为 @MapperScan 里指定了
sqlSessionTemplateRef,所以每个
MapperFactoryBean 的 BeanDefinition
里都会写入这条属性。它不会走”只指定 Factory 时自己 new
Template”的路径,而是直接用容器里共享的
pgsqlSqlSessionTemplate。
第 3 步:创建
pgsqlDataSource
Spring 开始实例化 Bean。先创建 pgsqlDataSource:
1 | DataSourceBuilder.create().build() |
这一步产出的是一个 PostgreSQL 连接池。
第 4 步:创建
pgsqlSqlSessionFactory(核心)
1 | MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean(); |
bean.getObject() 内部会执行
buildSqlSessionFactory(),这是整个 MyBatis-Plus
增强机制真正生效的地方:
1 | MybatisSqlSessionFactoryBean.buildSqlSessionFactory() |
注意:这一步完成时,pgsqlSqlSessionFactory
内部的 MybatisConfiguration 里已经存好了 XML 中定义的
SQL,但 BaseMapper 的通用方法还没注册,注解中的 SQL
也没注册。
第 5 步:创建
pgsqlSqlSessionTemplate
1 | return new SqlSessionTemplate(pgsqlSqlSessionFactory); |
创建出来的 SqlSessionTemplate 内部持有
pgsqlSqlSessionFactory,并通过它间接持有
MybatisConfiguration。
这个 Template 是共享单例的,后面所有指定包下的 Mapper 都用它。
第 6
步:创建每个 MapperFactoryBean,触发 SQL 解析
Spring 开始实例化每个 Mapper 对应的
MapperFactoryBean:
1 | 实例化 MapperFactoryBean(以 UserMapper 为例) |
这一步是 @MapperScan 和
MybatisMapperRegistry 的连接点。 没有
@MapperScan,就不会有 MapperFactoryBean;没有
MapperFactoryBean.checkDaoConfig(),就不会触发
addMapper(),也就不会触发 SQL 注入。
第 7
步:MapperFactoryBean.getObject() 创建代理对象
依赖注入阶段,Service 需要 UserMapper:
1 | Spring 调用 MapperFactoryBean.getObject() |
代理对象内部持有的 SqlSessionTemplate 就是
pgsqlSqlSessionTemplate。
这个绑定关系在代理对象创建时就固定了,后续无论谁调用,都会走这个
Template。
三、运行时:一次查询的完整链路
假设 Service 中注入了 UserMapper,现在调用:
1 | User user = userMapper.selectById(1L); |
执行链:
1 | UserMapper 代理对象.selectById(1L) |
关键点:整条链路操作的是 PostgreSQL 数据库,因为
pgsqlSqlSessionTemplate 绑定的是
pgsqlSqlSessionFactory,而
pgsqlSqlSessionFactory 绑定的是
pgsqlDataSource。
四、回到你的配置,回答几个关键问题
Q1:为什么只指定
sqlSessionTemplateRef 就够了?
因为 pgsqlSqlSessionTemplate 内部已经持有
pgsqlSqlSessionFactory,而
pgsqlSqlSessionFactory 内部持有
MybatisConfiguration 和
pgsqlDataSource。指定 Template 就等于指定了
Factory、Configuration 和 DataSource。
Q2:如果没有
sqlSessionTemplateRef 会怎样?
每个 MapperFactoryBean 会用注入的
pgsqlSqlSessionFactory 自己 new 一个私有的
SqlSessionTemplate。功能上能跑,但:
- 每个 Mapper 持有一个私有 Template,浪费内存;
- 无法统一配置
ExecutorType; - 如果容器里还有其他
SqlSessionTemplate,可能因类型不唯一而报错。
Q3:@MapperScan
和 MybatisMapperRegistry 是怎么关联的?
连接点是 MapperFactoryBean.checkDaoConfig()
中的这一行:
1 | configuration.addMapper(this.mapperInterface); |
@MapperScan注册MapperFactoryBean;MapperFactoryBean初始化时调用addMapper();addMapper()进入MybatisMapperRegistry,触发 SQL 注入。
两者缺一不可。
Q4:SQL 解析是在
getObject() 时做的吗?
不是。SQL 解析发生在 MapperFactoryBean.checkDaoConfig()
中,这个方法的调用时机是 Spring 的
afterPropertiesSet(),比 getObject()
早。
- XML 中的 SQL:在
pgsqlSqlSessionFactory创建时(buildSqlSessionFactory())解析; - 注解 SQL 和
BaseMapper通用方法:在MapperFactoryBean.checkDaoConfig()中解析; getObject()只负责从已经解析好的Configuration中创建代理对象。
Q5:Configuration
绑定在 SqlSessionTemplate 上吗?
不是直接绑定。持有链是:
1 | pgsqlSqlSessionTemplate |
SqlSessionTemplate.getConfiguration() 是转调
sqlSessionFactory.getConfiguration(),自己并不持有
Configuration。
Q6:MybatisPlusAutoConfiguration
在这个配置里还起作用吗?
起作用,但它创建的 SqlSessionFactory 和
SqlSessionTemplate 因为 @Primary 和
@ConditionalOnMissingBean 被跳过了。它剩下的作用:
- 绑定
mybatis-plus.*配置项; - 提供
ConfigurationCustomizer、MybatisPlusPropertiesCustomizer等扩展点; - 如果你的项目里没有手动配置
MybatisPlusInterceptor,它也可以注册(但你的配置里手动传了mybatisConfig.pageInterceptor(),所以自动配置的会被跳过)。
你没有排除它,也不需要排除它。
五、完整时序图
1 | ┌─────────────────────────────────────────────────────────────────────┐ |
六、一句话总结
你的 PostgreSQLConfig
做了一件很清晰的事:定义了一套 PostgreSQL 的持久层基础设施(DataSource →
SqlSessionFactory → SqlSessionTemplate → TransactionManager),然后用
@MapperScan 把指定包下的 Mapper 绑定到
pgsqlSqlSessionTemplate 上。
启动时,MapperFactoryBean 在
checkDaoConfig() 中调用
configuration.addMapper(),触发 MyBatis-Plus 的
MybatisMapperRegistry,完成注解 SQL 和
BaseMapper 通用方法的注入;运行时,代理对象通过
pgsqlSqlSessionTemplate 找到
pgsqlSqlSessionFactory,最终从 pgsqlDataSource
获取 PostgreSQL 连接执行 SQL。
整个链路是:@MapperScan →
MapperFactoryBean → SqlSessionTemplate →
SqlSessionFactory → Configuration →
DataSource → Connection →
PostgreSQL。