关键词:MyBatis, Spring, SqlSessionFactoryBean, MapperScannerConfigurer, SqlSessionTemplate, 整合原理
在 Java 企业级开发中,MyBatis 和 Spring 是两个不可或缺的框架。MyBatis 负责数据持久层,Spring 负责 IoC 容器和事务管理。那么,当我们将 MyBatis 整合到 Spring 中时,背后的原理是什么?SqlSession 和 Mapper 代理对象是如何自动注入的?本文将从实战配置出发,深入源码剖析整合的核心原理,帮助你彻底理解这一过程。
📑 目录
- 一、MyBatis 整合 Spring 快速入门
- 二、SqlSessionFactory 的创建原理
- 三、SqlSession 的线程安全问题与解决方案
- 四、Mapper 接口代理对象的创建
- 五、设计模式总结
- 六、核心对象生命周期总结
一、MyBatis 整合 Spring 快速入门
1.1 添加相关依赖
首先,在 pom.xml 中添加 MyBatis-Spring 和 Spring 相关依赖:
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.14</version>
</dependency>
1.2 配置文件
将 MyBatis 整合到 Spring 中后,MyBatis 的配置文件可以简化甚至为空:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- MyBatis 配置可完全迁移到 Spring 配置文件中 -->
</configuration>
Spring 配置文件 applicationContext.xml:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
<!-- 关联数据属性文件 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 开启扫描 -->
<context:component-scan base-package="com.bobo"/>
<!-- 配置数据源 -->
<bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 整合 MyBatis:配置 SqlSessionFactory -->
<bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sqlSessionFactoryBean">
<!-- 关联数据源 -->
<property name="dataSource" ref="dataSource"/>
<!-- 关联 MyBatis 配置文件 -->
<property name="configLocation" value="classpath:mybatis-config-spring.xml"/>
<!-- 指定映射文件的位置 -->
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
<!-- 添加别名 -->
<property name="typeAliasesPackage" value="com.bobo.domain"/>
</bean>
<!-- 扫描 Mapper 接口 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.bobo.mapper"/>
</bean>
</beans>
1.3 单元测试
整合完成后,通过 Spring 的单元测试验证:
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
@RunWith(value = SpringJUnit4ClassRunner.class)
public class MyBatisSpringTest {
@Autowired
private UserMapper userMapper;
@Test
public void testQuery() {
List<User> users = userMapper.selectUserList();
for (User user : users) {
System.out.println(user);
}
}
}
关键发现:整合到 Spring 后,原来需要手动操作的 SqlSessionFactory、SqlSession、getMapper() 都不见了,开发更加简洁!
二、SqlSessionFactory 的创建原理
把 MyBatis 集成到 Spring 中,并没有替换 MyBatis 的核心对象,只是做了一些包装或桥梁的工作。核心对象 SqlSessionFactory、SqlSession、MapperProxy 仍然由 MyBatis jar 包提供。
理解整合原理,需要搞清楚三个问题:
- SqlSessionFactory 在哪创建的?
- SqlSession 在哪创建的?
- 代理类在哪创建的?
2.1 SqlSessionFactoryBean 的核心作用
Spring 配置文件中配置的 org.mybatis.spring.SqlSessionFactoryBean 是关键入口。它实现了三个重要接口:
| 接口 | 方法 | 作用 |
|---|---|---|
| FactoryBean | getObject() | 返回由 FactoryBean 创建的 Bean 实例 |
| InitializingBean | afterPropertiesSet() | Bean 属性初始化完成后执行操作 |
| ApplicationListener | onApplicationEvent() | 监听应用事件(如上下文刷新) |
2.2 afterPropertiesSet():创建 SqlSessionFactory
public void afterPropertiesSet() throws Exception {
Assert.notNull(this.dataSource, "Property 'dataSource' is required");
Assert.notNull(this.sqlSessionFactoryBuilder,
"Property 'sqlSessionFactoryBuilder' is required");
// 核心方法:构建 SqlSessionFactory
this.sqlSessionFactory = this.buildSqlSessionFactory();
}
buildSqlSessionFactory() 方法完成了 SqlSessionFactory 的创建和配置文件解析:
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
// 1. 创建 Configuration 对象
Configuration targetConfiguration;
// 2. 根据配置文件路径构建 XMLConfigBuilder
if (this.configLocation != null) {
xmlConfigBuilder = new XMLConfigBuilder(
this.configLocation.getInputStream(),
null,
this.configurationProperties
);
targetConfiguration = xmlConfigBuilder.getConfiguration();
} else {
targetConfiguration = new Configuration();
}
// 3. 设置 Configuration 属性
// - 设置 objectFactory、objectWrapperFactory
// - 扫描并注册 typeAliasesPackage 下的别名
// - 注册 plugins(拦截器)
// - 注册 typeHandlers(类型处理器)
// 4. 解析全局配置文件
if (xmlConfigBuilder != null) {
xmlConfigBuilder.parse();
}
// 5. 设置 Environment(包含数据源和事务工厂)
targetConfiguration.setEnvironment(new Environment(
this.environment,
this.transactionFactory == null
? new SpringManagedTransactionFactory()
: this.transactionFactory,
this.dataSource
));
// 6. 解析 Mapper.xml 文件
if (this.mapperLocations != null) {
for (Resource mapperLocation : this.mapperLocations) {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(
mapperLocation.getInputStream(),
targetConfiguration,
mapperLocation.toString(),
targetConfiguration.getSqlFragments()
);
xmlMapperBuilder.parse(); // 解析 Mapper 文件
}
}
// 7. 创建并返回 DefaultSqlSessionFactory
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}
方法小结:
- 通过实现 InitializingBean 接口的
afterPropertiesSet()方法 - 在 Spring 初始化 Bean 属性后,自动完成配置解析和 SqlSessionFactory 创建
2.3 getObject():返回 SqlSessionFactory
SqlSessionFactoryBean 实现了 FactoryBean 接口,这意味着从 Spring 容器获取该 Bean 时,实际返回的是 getObject() 的结果:
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
this.afterPropertiesSet(); // 懒加载机制
}
return this.sqlSessionFactory;
}
2.4 onApplicationEvent():快速失败检测
实现 ApplicationListener 接口可以监听应用事件。这里监听 ContextRefreshedEvent(上下文刷新事件),用于快速检测 MappedStatement 是否加载完毕:
public void onApplicationEvent(ApplicationEvent event) {
if (this.failFast && event instanceof ContextRefreshedEvent) {
this.sqlSessionFactory.getConfiguration().getMappedStatementNames();
}
}
三、SqlSession 的线程安全问题与解决方案
3.1 DefaultSqlSession 的线程安全问题
在使用原生 MyBatis 时,通过 SqlSessionFactory.openSession() 获取的是 DefaultSqlSession。
⚠️ 重要问题:DefaultSqlSession 是线程不安全的!
在 MyBatis 官方文档中明确指出:
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此不能被共享。
因此,使用原生 MyBatis 时需要这样写:
try (SqlSession session = sqlSessionFactory.openSession()) {
// 应用逻辑代码
}
// 或者
SqlSession session = null;
try {
session = sqlSessionFactory.openSession();
// 应用逻辑代码
} finally {
session.close();
}
3.2 SqlSessionTemplate:线程安全的解决方案
在 MyBatis-Spring 中,提供了线程安全的 SqlSession 包装类:SqlSessionTemplate。
特点:
- 线程安全,可以在所有 DAO 层共享一个实例(默认单例)
- 实现了 SqlSession 接口,提供相同的 API
- 内部使用代理模式管理 SqlSession 的生命周期
内部实现:
public class SqlSessionTemplate implements SqlSession {
private final SqlSessionFactory sqlSessionFactory;
private final ExecutorType executorType;
private final SqlSession sqlSessionProxy; // 代理对象
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory,
ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
// 创建 SqlSession 接口的代理对象
this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor() // InvocationHandler
);
}
// 所有方法都委托给 sqlSessionProxy
@Override
public <T> T selectOne(String statement) {
return this.sqlSessionProxy.selectOne(statement);
}
// ... 其他方法
}
3.3 SqlSessionInterceptor:代理逻辑
SqlSessionInterceptor 是 SqlSessionTemplate 的内部类,实现了 InvocationHandler:
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 1. 获取 SqlSession(从 Spring 事务管理器或创建新的)
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator
);
try {
// 2. 执行目标方法
Object result = method.invoke(sqlSession, args);
// 3. 检查是否被 Spring 管理事务
if (!isSqlSessionTransactional(sqlSession,
SqlSessionTemplate.this.sqlSessionFactory)) {
// 如果没有被 Spring 管理,强制提交
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
// 异常处理...
throw t;
} finally {
// 4. 关闭 SqlSession(如果没有被 Spring 管理)
if (!isSqlSessionTransactional(sqlSession,
SqlSessionTemplate.this.sqlSessionFactory)) {
closeSqlSession(sqlSession,
SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
核心逻辑:
调用 SqlSessionTemplate.selectOne()
↓
代理到 SqlSessionInterceptor.invoke()
↓
获取 SqlSession(检查 Spring 事务上下文)
↓
执行目标方法
↓
提交/回滚(如果非 Spring 管理事务)
↓
关闭 SqlSession(如果非 Spring 管理事务)
总结:因为 DefaultSqlSession 自己做不到每次请求调用产生一个新的实例,MyBatis-Spring 通过代理模式,在任何一个方法被调用时都先创建一个 DefaultSqlSession 实例,再调用被代理对象的相应方法。
3.4 SqlSessionDaoSupport:简化 DAO 开发
为了让 DAO 层更方便地获取 SqlSessionTemplate,MyBatis-Spring 提供了 SqlSessionDaoSupport 抽象类:
public abstract class SqlSessionDaoSupport extends DaoSupport {
private SqlSessionTemplate sqlSessionTemplate;
public SqlSession getSqlSession() {
return this.sqlSessionTemplate;
}
@Autowired(required = false)
public final void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (!this.externalSqlSession) {
this.sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory);
}
}
// ...
}
使用方式:
@Repository
public class UserDaoImpl extends SqlSessionDaoSupport implements UserMapper {
@Autowired
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
super.setSqlSessionFactory(sqlSessionFactory);
}
@Override
public User selectById(Integer id) {
return getSqlSession().selectOne(
"com.bobo.mapper.UserMapper.selectById", id
);
}
}
更进一步,可以创建 BaseDao 封装通用操作:
public class BaseDao extends SqlSessionDaoSupport {
@Autowired
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
super.setSqlSessionFactory(sqlSessionFactory);
}
public Object selectOne(String statement, Object parameter) {
return getSqlSession().selectOne(statement, parameter);
}
public List<?> selectList(String statement, Object parameter) {
return getSqlSession().selectList(statement, parameter);
}
// ...
}
四、Mapper 接口代理对象的创建
在实战中,我们通常直接注入 Mapper 接口,而不需要创建实现类。这是如何实现的?
4.1 MapperScannerConfigurer:扫描 Mapper 接口
Spring 配置文件中的 MapperScannerConfigurer 是关键:
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.bobo.mapper"/>
</bean>
MapperScannerConfigurer 的继承结构:
MapperScannerConfigurer
↓
implements BeanDefinitionRegistryPostProcessor
↓
postProcessBeanDefinitionRegistry()
实现了 BeanDefinitionRegistryPostProcessor 接口,可以在 Spring 创建 Bean 之前修改 Bean 定义:
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
// 1. 创建 ClassPathMapperScanner
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setSqlSessionFactory(this.sqlSessionFactory);
scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
// ... 其他配置
// 2. 注册过滤器
scanner.registerFilters();
// 3. 扫描 basePackage 指定的包及其子包
scanner.scan(StringUtils.tokenizeToStringArray(
this.basePackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS
));
}
4.2 doScan():扫描并注册 BeanDefinition
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
// 1. 调用父类方法扫描所有接口
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
LOGGER.warn(() -> "No MyBatis mapper was found...");
} else {
// 2. 处理 BeanDefinition
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
核心处理:将 Mapper 接口的 BeanClass 改为 MapperFactoryBean:
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
// 添加构造函数参数:Mapper 接口类型
definition.getConstructorArgumentValues()
.addGenericArgumentValue(beanClassName);
// 关键:将 BeanClass 设置为 MapperFactoryBean
definition.setBeanClass(this.mapperFactoryBeanClass);
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
核心思想:因为接口无法直接创建实例,所以在创建对象之前,将接口类型指向一个具体的 Java 类型 MapperFactoryBean。
4.3 MapperFactoryBean:FactoryBean 的妙用
类图结构:
MapperFactoryBean<T>
↓ extends
SqlSessionDaoSupport
↓ extends
DaoSupport
↓ implements
FactoryBean<T>
MapperFactoryBean 的作用:
- 继承了 SqlSessionDaoSupport → 可以获取 SqlSessionTemplate
- 实现了 FactoryBean → 自定义 Bean 实例化逻辑
public class MapperFactoryBean<T> extends SqlSessionDaoSupport
implements FactoryBean<T> {
private Class<T> mapperInterface;
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
@Override
public T getObject() throws Exception {
// 通过 SqlSessionTemplate 获取 Mapper 代理对象
return getSqlSession().getMapper(this.mapperInterface);
}
@Override
public Class<T> getObjectType() {
return this.mapperInterface;
}
@Override
public boolean isSingleton() {
return true;
}
}
最终调用链:
@Autowired
private UserMapper userMapper; // 注入时
↓
Spring 容器调用 MapperFactoryBean.getObject()
↓
getSqlSession().getMapper(UserMapper.class)
↓
SqlSessionTemplate.getMapper() → DefaultSqlSession.getMapper()
↓
Configuration.getMapper() → MapperRegistry.getMapper()
↓
MapperProxyFactory.newInstance() → Proxy.newProxyInstance()
↓
返回 Mapper 接口的 JDK 动态代理对象(MapperProxy)
执行 Mapper 方法时的流程:
userMapper.selectById(1)
↓
MapperProxy.invoke() // 代理对象的方法拦截
↓
进入 MyBatis 的 SQL 处理流程
4.4 整合原理总结
Spring 整合 MyBatis 的核心步骤:
| 步骤 | 说明 |
|---|---|
| 1 | 提供 SqlSessionTemplate 替代 DefaultSqlSession,线程安全 |
| 2 | 提供 SqlSessionDaoSupport 获取 SqlSessionTemplate |
| 3 | 扫描 Mapper 接口,注册到容器中的是 MapperFactoryBean |
| 4 | 注入 Mapper 时,调用 getObject() 方法,注入 JDK 动态代理对象 |
| 5 | 执行 Mapper 方法时,触发 MapperProxy,进入 SQL 处理流程 |
五、设计模式总结
MyBatis 和 MyBatis-Spring 中使用了大量的设计模式:
| 设计模式 | 应用类 | 说明 |
|---|---|---|
| 工厂模式 | SqlSessionFactory、ObjectFactory、MapperProxyFactory | 创建对象实例 |
| 建造者模式 | XMLConfigBuilder、XMLMapperBuilder | 复杂对象的分步构建 |
| 单例模式 | SqlSessionFactory、Configuration | 全局唯一实例 |
| 代理模式 | MapperProxy、Plugin、SqlSessionInterceptor | 方法拦截和增强 |
| 适配器模式 | Log 适配器 | 适配不同日志框架 |
| 模板方法 | BaseExecutor | 定义算法骨架,子类实现细节 |
| 装饰器模式 | LoggingCache、LruCache、CachingExecutor | 动态添加功能 |
| 责任链模式 | InterceptorChain | 多个拦截器链式执行 |
六、核心对象生命周期总结
| 对象 | 生命周期 | 说明 |
|---|---|---|
| SqlSessionTemplate | Spring 管理 | SqlSession 的线程安全替代品,单例 |
| SqlSessionDaoSupport | 抽象类 | 用于获取 SqlSessionTemplate |
| SqlSessionInterceptor | 内部类 | 代理对象,代理 DefaultSqlSession |
| MapperFactoryBean | Spring 管理 | 继承 SqlSessionDaoSupport,实现 FactoryBean |
| SqlSessionHolder | 事务绑定 | 控制 SqlSession 和事务的绑定关系 |
七、总结
本文从 MyBatis-Spring 的实战配置出发,深入剖析了整合的核心原理:
关键知识点:
-
SqlSessionFactoryBean 实现了
InitializingBean、FactoryBean、ApplicationListener三个接口,在 Bean 初始化时完成 SqlSessionFactory 的创建 -
SqlSessionTemplate 通过代理模式解决了 DefaultSqlSession 的线程安全问题,内部使用
SqlSessionInterceptor管理 SqlSession 的生命周期 -
MapperScannerConfigurer 扫描 Mapper 接口,将 BeanDefinition 的 BeanClass 修改为 MapperFactoryBean
-
MapperFactoryBean 实现了
FactoryBean接口,在getObject()中返回 Mapper 接口的 JDK 动态代理对象 -
最终调用 Mapper 方法时,仍然是 MapperProxy 的
invoke()方法拦截,与原生 MyBatis 流程一致
使用建议:
- 优先使用 MapperScannerConfigurer 自动扫描,减少手动配置
- 注意 SqlSession 的线程安全问题,始终使用 SqlSessionTemplate
- 结合 Spring 事务管理,确保数据库操作的一致性
希望这篇文章能帮助你深入理解 MyBatis 与 Spring 的整合原理!
💡 温馨提示:如果文章对你有帮助,欢迎点赞、收藏、关注!有任何问题欢迎在评论区留言讨论。
参考资源:
- MyBatis-Spring 官方文档:http://mybatis.org/spring/zh/index.html
- MyBatis 官方文档:https://mybatis.org/mybatis-3/zh/index.html
- Spring 官方文档:https://spring.io/projects/spring-framework
&spm=1001.2101.3001.5002&articleId=161199242&d=1&t=3&u=69ae0666506a47f1b06d6d28d6c45492)

被折叠的 条评论
为什么被折叠?



