第1篇-从Mapper接口到SQL执行的奇幻漂流
约 1781 字大约 6 分钟
2026-05-24
四、getSqlSession():如何获取 SqlSession?
4.1 方法源码解析
// 类路径:org.mybatis.spring.SqlSessionUtils
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
// 【核心】第一步:从 Spring 的事务管理器中获取 SqlSessionHolder
SqlSessionHolder sessionHolder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
// 【核心】如果找到了 SqlSessionHolder,就返回它持有的 SqlSession
if (sessionHolder != null && sessionHolder.isSynchronizedWithTransaction()) {
return sessionHolder.getSqlSession();
}
// 【核心】第二步:如果没找到,就创建新的 SqlSession
SqlSession session = sessionFactory.openSession(executorType);
// 【核心】第三步:如果当前有事务,就把 SqlSession 绑定到事务中
if (TransactionSynchronizationManager.isActive()) {
// 【注意】把 SqlSession 绑定到当前事务
TransactionSynchronizationManager.bindResource(sessionFactory, new SqlSessionHolder(session));
}
return session;
}白话文解释:
- 从事务管理器获取:
TransactionSynchronizationManager.getResource(sessionFactory)会根据SqlSessionFactory,从当前线程的ThreadLocal中获取SqlSessionHolder。 - 如果找到了:说明当前线程已经有一个
SqlSession(并且它在事务中),就直接返回它。这保证了:同一个事务中,所有的 Mapper 方法都使用同一个SqlSession。 - 如果没找到:就调用
sessionFactory.openSession(executorType)创建新的SqlSession。 - 绑定到事务:如果当前有事务,就把
SqlSession绑定到事务中(这样,事务提交/回滚时,Spring 会自动关闭SqlSession)。
你可能会问:为什么要把 SqlSession 绑定到事务中?因为这样可以保证:同一个事务中的所有 SQL,都使用同一个 SqlSession(进而使用同一个 JDBC 连接)。这是实现事务原子性的基础!
五、MybatisAutoConfiguration:Spring Boot 的自动装配
5.1 自动配置类的作用
在 Spring Boot 中,如果你引入了 mybatis-spring-boot-starter 依赖,MyBatis 会自动配置:
// 类路径:org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
@Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnSingleCandidate(DataSource.class) // 【注意】要求容器中只有一个 DataSource
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
public class MybatisAutoConfiguration implements InitializingBean {
// 【核心】第一步:创建 SqlSessionFactory
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
// 【注意】设置数据源(重要!这就是 Druid 连接池!)
factory.setDataSource(dataSource);
// 【注意】设置 Mapper.xml 文件的位置
factory.setMapperLocations("classpath*:mapper/**/*.xml");
return factory.getObject();
}
// 【核心】第二步:创建 SqlSessionTemplate
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
// 【核心】第三步:扫描 Mapper 接口,并为它们创建代理对象
@Bean
public MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer scanner = new MapperScannerConfigurer();
// 【注意】设置 Mapper 接口所在的包
scanner.setBasePackage("com.example.mapper");
return scanner;
}
}5.2 MapperScannerConfigurer:扫描 Mapper 接口的"猎手"
// 类路径:org.mybatis.spring.mapper.MapperScannerConfigurer
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
// 【核心】扫描 Mapper 接口,并为它们创建代理对象
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
// 【注意】创建 ClassPathMapperScanner
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// 【注意】扫描指定包下的所有接口
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
}白话文解释:
- SqlSessionFactory:这是 MyBatis 的"工厂类",用于创建
SqlSession。在创建时,它会设置数据源(这就是 Druid 连接池!)。 - SqlSessionTemplate:这是 Spring 整合 MyBatis 的"桥梁"。它会注册到 Spring 容器中,这样你就可以在任何地方
@Autowired一个SqlSessionTemplate。 - MapperScannerConfigurer:它会扫描指定包下的所有接口(比如
UserMapper、OrderMapper等),然后为它们创建代理对象,并注册到 Spring 容器中。 - 代理对象:当你
@Autowired UserMapper userMapper时,Spring 实际注入的是MapperProxy代理对象。当你调用userMapper.selectById()时,实际上是调用了MapperProxy.invoke()方法。
六、埋下伏笔:Executor 执行器的"三种形态"
说到这里,你可能会问:SqlSession 是如何执行 SQL 的?它内部使用了什么对象?
这个问题,我们会在下一篇文章(系列三第2篇)中详细解答。这里先预告一下核心流程:
- SqlSession 内部使用了
Executor(执行器)。 Executor有三种形态**:SimpleExecutor、ReuseExecutor、BatchExecutor。Executor在执行 SQL 前,会通过SpringManagedTransaction获取数据库连接。SpringManagedTransaction会通过DataSourceUtils.getConnection()获取连接。DataSourceUtils.getConnection()最终调用dataSource.getConnection(),也就是 Druid 的getConnection()方法。
下一篇文章的预告:《MyBatis 系列(二):Executor 执行器的三种形态》
七、总结与思考#
今天我们一起深入了 MyBatis 的"从 Mapper 接口到 SQL 执行"的全过程,了解了 MapperProxy、MapperMethod、SqlSessionTemplate 的核心实现,以及 Spring Boot 是如何自动装配 MyBatis 的。
让我们用几个问题来检验一下今天的学习成果:
Mapper 接口是如何变成"可执行"的对象的?
- MyBatis 使用 JDK 的动态代理机制,为 Mapper 接口生成代理对象(
MapperProxy)。 - 当调用 Mapper 接口的方法时,实际上是调用了
MapperProxy.invoke()方法。
- MyBatis 使用 JDK 的动态代理机制,为 Mapper 接口生成代理对象(
MapperMethod的作用是什么?- 根据 "接口名.方法名",找到对应的
MappedStatement(包含了 SQL 语句、参数映射、结果映射等)。 - 根据
SqlCommandType,调用SqlSession的对应方法(比如insert()、update()、selectOne()等)。
- 根据 "接口名.方法名",找到对应的
SqlSessionTemplate的作用是什么?- 它是 Spring 整合 MyBatis 的"桥梁"。
- 它实现了
SqlSession接口,但在内部使用了代理模式(SqlSessionInterceptor)。 - 它保证了:每次调用方法时,都从 Spring 的事务管理器中获取
SqlSession,从而避免线程安全问题。
Spring Boot 是如何自动装配 MyBatis 的?
MybatisAutoConfiguration会自动创建SqlSessionFactory、SqlSessionTemplate。MapperScannerConfigurer会扫描指定包下的所有 Mapper 接口,并为它们创建代理对象。
八、扩展阅读#
【扩展点】自定义 Mapper 扫描规则
你可以自定义 Mapper 扫描规则,比如:只扫描标注了 @MyMapper 注解的接口。
// 【扩展点】自定义 Mapper 扫描规则
@Configuration
public class MyMapperScanConfig {
@Bean
public MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer scanner = new MapperScannerConfigurer();
// 【注意】只扫描标注了 @MyMapper 注解的接口
scanner.setAnnotationClass(MyMapper.class);
// 【注意】设置扫描的包
scanner.setBasePackage("com.example.mapper");
return scanner;
}
}
// 【扩展点】自定义注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyMapper {
}
// 【扩展点】使用自定义注解
@MyMapper
public interface UserMapper {
User selectById(Long id);
}场景:你可以定义自己的 Mapper 注解,用于区分"系统 Mapper"和"业务 Mapper",或者用于多数据源场景(不同的 Mapper 使用不同的数据源)。
好了,今天的文章就到这里。下一篇,我们将深入 Executor 执行器的三种形态,看看 MyBatis 是如何执行 SQL 的。敬请期待!
如果你觉得这篇文章对你有帮助,欢迎点赞、收藏、转发!你的支持是我继续创作的动力!
系列文章目录:
- 系列一:Spring 事务系列(已完成)
- 第1篇:@Transactional 注解的"前世今生"
- 第2篇:AOP 代理 —— 事务的"隐身衣"
- 第3篇:事务传播机制源码深度解析
- 第4篇:事务隔离级别的实现原理
- 第5篇:事务管理器的设计哲学
- 第6篇:事务资源的绑定与传递
- 第7篇:提交、回滚与异常规则"
- 系列二:数据源系列(已完成)
- 第1篇:JDBC 规范与 DriverManager 的 legacy
- 第2篇:Druid 连接池的整体架构
- 第3篇:连接的借与还(getConnection 全流程)
- 第4篇:Druid 过滤链与监控统计
- 第5篇:MySQL 驱动的 JDBC 实现
- 第6篇:连接的生命周期与事务边界
- 系列三:MyBatis 系列(进行中)
- 第1篇:从 Mapper 接口到 SQL 执行的奇幻漂流(本文)
- 第2篇:Executor 执行器的三种形态
- 第3篇:事务管理 —— SpringManagedTransaction 的秘密
- 第4篇:SQL 执行的全链路(StatementHandler)
- 第5篇:插件机制与责任链
- 系列四:总结系列(待开始)