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 会自动处理这些细节。