MyBatis 的 SqlSession
本身不是线程安全的。每个线程都拥有自己的 SqlSession
实例。Spring 通过与 MyBatis 的集成,以及 Spring 自身的事务管理机制,解决了 SqlSession
的线程安全问题。主要有两种方式:
1. SqlSessionFactoryBean
+ SqlSessionTemplate
(传统方式):
-
SqlSessionFactoryBean
:- Spring 提供的 FactoryBean,用于创建 MyBatis 的
SqlSessionFactory
。 SqlSessionFactoryBean
本身是单例的,并且是线程安全的。- 它在 Spring 容器启动时创建
SqlSessionFactory
实例。
- Spring 提供的 FactoryBean,用于创建 MyBatis 的
-
SqlSessionTemplate
:- 实现了
SqlSession
接口,是SqlSession
的线程安全代理。 SqlSessionTemplate
内部使用ThreadLocal
来保存每个线程的SqlSession
。- 当需要
SqlSession
时,SqlSessionTemplate
会从ThreadLocal
中获取当前线程的SqlSession
,如果没有,则创建一个新的SqlSession
并绑定到当前线程。 - 在事务环境下,
SqlSessionTemplate
会参与 Spring 的事务管理。
- 实现了
-
工作原理:
- Spring 容器启动时,
SqlSessionFactoryBean
创建SqlSessionFactory
。 - 当你注入
SqlSessionTemplate
并调用其方法(如selectOne
、insert
等)时:SqlSessionTemplate
检查当前线程是否已经绑定了SqlSession
。- 如果没有,
SqlSessionTemplate
会从SqlSessionFactory
获取一个新的SqlSession
,并将其绑定到当前线程的ThreadLocal
。 - 如果已经绑定,则直接使用
ThreadLocal
中的SqlSession
。 SqlSessionTemplate
将方法调用委托给实际的SqlSession
执行。- 如果当前存在 Spring 管理的事务,
SqlSessionTemplate
会参与事务(获取事务的SqlSession
,并在事务提交或回滚时关闭SqlSession
)。 - 如果没有事务,
SqlSessionTemplate
会在使用完SqlSession
后自动关闭它 (通常是在方法调用结束后)。
- Spring 容器启动时,
2. MapperFactoryBean / MapperScannerConfigurer (更常用, 特别是与 Spring Boot 结合):
-
MapperFactoryBean
:- Spring 提供的 FactoryBean,用于创建 MyBatis 的 Mapper 接口的代理对象。
MapperFactoryBean
内部使用SqlSessionTemplate
来执行 SQL 操作。- 你可以直接注入 Mapper 接口,而无需显式使用
SqlSessionTemplate
。
-
MapperScannerConfigurer
:- Spring 提供的 BeanDefinitionRegistryPostProcessor,用于自动扫描 Mapper 接口,并为每个 Mapper 接口创建
MapperFactoryBean
。 - 使用
@MapperScan
注解(或 XML 配置)可以启用MapperScannerConfigurer
。 - Spring Boot 会自动配置
MapperScannerConfigurer
(如果你使用了mybatis-spring-boot-starter
)。
- Spring 提供的 BeanDefinitionRegistryPostProcessor,用于自动扫描 Mapper 接口,并为每个 Mapper 接口创建
-
工作原理:
- Spring容器启动时,
MapperScannerConfigurer
(或手动配置的MapperFactoryBean
) 会扫描并注册Mapper接口。 - 对于每个Mapper接口, 会创建一个
MapperFactoryBean
. - 当你注入 Mapper 接口并调用其方法时:
- Spring 会调用
MapperFactoryBean
创建 Mapper 接口的代理对象(如果尚未创建)。 - 代理对象内部会使用
SqlSessionTemplate
来获取当前线程的SqlSession
(如果需要,会创建新的SqlSession
并绑定到当前线程)。 - 代理对象将方法调用委托给
SqlSession
执行相应的 SQL 操作。 - 事务的处理与
SqlSessionTemplate
相同.
- Spring 会调用
- Spring容器启动时,
Spring 的事务管理:
- Spring 的事务管理机制(声明式事务
@Transactional
或编程式事务)与 MyBatis 的集成是解决SqlSession
线程安全问题的关键。 - 当使用
@Transactional
注解时,Spring 会为当前线程创建一个事务上下文,并将SqlSession
绑定到该事务上下文。 - 在同一个事务中,所有对
SqlSession
的操作都会使用同一个SqlSession
实例,保证了事务的一致性。 - 事务提交或回滚时,Spring 会自动关闭
SqlSession
。
代码示例 (使用 MapperFactoryBean
和 @Transactional
):
UserMapper.java:
java
public interface UserMapper {
User getUserById(int id);
void insertUser(User user);
}
UserService.java
java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Transactional
public User getUser(int id) {
return userMapper.getUserById(id);
}
@Transactional
public void addUser(User user){
userMapper.insertUser(user);
// ... 其他操作 ...
}
}
配置 (Spring Boot):
java
// 假设你已经添加了 mybatis-spring-boot-starter 依赖
// application.properties
mybatis.mapper-locations=classpath:mapper/*.xml
# 其他配置...
在这个例子中:
UserMapper
接口会被 Spring 自动代理(通过MapperFactoryBean
或MapperScannerConfigurer
)。UserService
中的getUser
和addUser
方法使用了@Transactional
注解,表示这些方法需要在事务中执行。- 当调用
getUser
或addUser
方法时:- Spring 会开启一个事务。
- Spring 会获取或创建一个
SqlSession
,并将其绑定到当前线程的事务上下文。 UserMapper
的代理对象会使用该SqlSession
执行 SQL 操作。- 如果方法成功执行,Spring 会提交事务并关闭
SqlSession
。 - 如果方法抛出异常,Spring 会回滚事务并关闭
SqlSession
。
总结:
Spring 通过以下方式解决 MyBatis 的 SqlSession
线程安全问题:
SqlSessionTemplate
: 使用ThreadLocal
为每个线程提供独立的SqlSession
实例。MapperFactoryBean
/MapperScannerConfigurer
: 自动创建 Mapper 接口的代理对象,代理对象内部使用SqlSessionTemplate
。- Spring 事务管理: 将
SqlSession
绑定到当前线程的事务上下文,保证同一个事务中的所有操作使用同一个SqlSession
。
通过这些机制,Spring 确保了在多线程环境下 SqlSession
的安全使用,并简化了 MyBatis 与 Spring 的集成。 开发过程中咱们无需手动管理 SqlSession
的创建、获取和关闭,Spring 会自动处理这些细节。