一、引言
现在大家很少直接使用Mybatis了,一般都是与spring集成。下面我们回顾下原生Mybatis的使用示例:
java
// 1. 加载MyBatis核心配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 2. 构建SqlSessionFactory(会话工厂)
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 3. 开启数据库会话(try-with-resources自动关闭资源)
// 注意:默认 openSession() 是自动提交(autoCommit=true),需显式设置为 false
try (SqlSession sqlSession = sqlSessionFactory.openSession(false)) {
// 4. 获取Mapper接口的代理实例
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 5. 执行数据库操作
// 假装有些更新数据库的代码
// 6. 手动提交事务
sqlSession.commit();
} catch (Exception e) {
// 7. 发生异常时回滚事务
sqlSession.rollback();
}
这是一个带有事务的示例,使用起来比较复杂,这时好奇的你可能有些疑问,为何在Spring内使用时非常简单呢
- 原生的Mybatis,需要依赖
SqlSession
获取Mapper,还要通过SqlSession
做事务管理,最后还要自行关闭,为什么在Spring中居然都没有显示的使用SqlSession? - 原生的Mapper需要从
SqlSession
中获取,显然不支持并发,为什么在Spring中,Mapper居然是单例的?
二、集成配置
我们先看下,Mybatis集成到Spring,需要的配置(如何在pom引入MyBatis-Spring这里就不演示了):
java
@Configuration
@MapperScan("com.example.mapper") // 扫描Mapper接口所在包
@EnableTransactionManagement // 开启事务管理
public class AppConfig {
// 1. 配置数据源(HikariCP)
@Bean
public DataSource dataSource() {
// 读取数据库配置(实际项目中可放在properties文件中)
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC");
config.setUsername("root");
config.setPassword("123456");
config.setDriverClassName("com.mysql.cj.jdbc.Driver");
// 连接池配置
config.setMaximumPoolSize(10);
config.setMinimumIdle(5);
return new HikariDataSource(config);
}
// 2. 配置SqlSessionFactory
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) throws IOException {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
// 设置数据源
sessionFactory.setDataSource(dataSource);
// 配置MyBatis全局属性(替代mybatis-config.xml),也可以还使用XML,通过setConfigLocation配置。
org.apache.ibatis.session.Configuration mybatisConfig = new org.apache.ibatis.session.Configuration();
mybatisConfig.setMapUnderscoreToCamelCase(true); // 下划线转驼峰
sessionFactory.setConfiguration(mybatisConfig);
// 注册Mapper映射文件(如果使用XML方式)
org.springframework.core.io.Resource[] mapperLocations = new org.springframework.core.io.Resource[] {
new org.springframework.core.io.ClassPathResource("mappers/UserMapper.xml")
};
sessionFactory.setMapperLocations(mapperLocations);
// 设置事务工厂(与Spring事务集成)
sessionFactory.setTransactionFactory(new SpringManagedTransactionFactory());
return sessionFactory;
}
// 3. 配置事务管理器
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
// 使用Spring的数据源事务管理器,与MyBatis集成
return new DataSourceTransactionManager(dataSource);
}
}
解释下都配置了哪些东西
@MapperScan("com.example.mapper")
:指定Mapper接口所在包,这个注解会把包下的Mapper都注册到Spring的容器中。@EnableTransactionManagement
: 开启事务管理。DataSource
: 配置数据库连接池。SqlSessionFactoryBean
:SqlSessionFactory
的FactoryBean
,用于构Mybatis的SqlSessionFactory
。PlatformTransactionManager
: 事务管理器。
这里的核心是1和4,其他配置,并不是特意给Mybatis用的,无论使用哪种持久层框架,都要配置。下面我们对这两个配置作用,做详细介绍。
三、 MapperScan
我们先概括下@MapperScan
的实现:
- 通过
@Import
导入MapperScannerRegistrar
,将其纳入 Spring 初始化流程; MapperScannerRegistrar
实现了Spring的ImportBeanDefinitionRegistrar
接口,它的registerBeanDefinitions
方法,会在Spring容器Bean实例化之前执行,进行Bean定义注册。- 在
registerBeanDefinitions
方法中,根据注解的配置,创建ClassPathMapperScanner
扫描指定包下的Mapper接口,并将接口配置,转换为MapperFactoryBean
注册到Spring容器。 - Spring实例化Bean时,通过
MapperFactoryBean
的getObject
方法,创建Mapper代理对象,加载到Spring容器中。
题外话:
BeanFactory
与FactoryBean
有什么区别?
答:简单说:BeanFactory
是 "容器",管理所有 Bean;FactoryBean
是 "Bean 工厂",专注于创建特定 Bean。
我们先看下@MapperScan
都有哪些属性:
value
/basePackages
:指定扫描的包路径(两者功能一致,value
是简写)。例如@MapperScan("com.example.mapper")
表示扫描该包下的所有接口。basePackageClasses
:通过指定类的方式间接包路径,扫描这些类所在的包。例如@MapperScan(basePackageClasses = UserMapper.class)
会扫描UserMapper
所在包下的接口。annotationClass
:指定只扫描标注了特定注解的接口。例如@MapperScan(annotationClass = Mapper.class)
表示仅扫描带@Mapper
注解的接口(需配合@Mapper
使用)。一般不配置,默认扫描包下所有接口,省去使用注解的麻烦。markerInterface
:指定只扫描继承了特定接口的接口。例如@MapperScan(markerInterface = BaseMapper.class)
表示仅扫描继承BaseMapper
的子接口,一般不配置。sqlSessionTemplateRef
:指定要注入的SqlSessionTemplate
的Bean名称。当你有自定义SqlSessionTempate的需要时,可以设置。当容器中有多个SqlSessionTemplate
时,也可以用这个属性指定具体使用哪一个,不过一般不这样使用,而是使用下面的属性👇🏻。sqlSessionFactoryRef
:指定要注入的SqlSessionFactory
的 Bean 名称。用于多数据源场景下指定对应的工厂。如果是多数据源,一般是设置这个属性,而不是上一个sqlSessionTemplateRef
,因为一般我们也不去主动定义SqlSessionTemplate
。factoryBean
:自定义创建 Mapper 代理对象的工厂类,默认使用MapperFactoryBean
。可通过继承MapperFactoryBean
扩展自定义逻辑。
接下来看下MapperScannerRegistrar
的registerBeanDefinitions
方法,是如何在BeanFactory
中注册Bean的定义的。
java
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 1. 创建自定义扫描器,设置注册器(Spring 的 BeanDefinitionRegistry)
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// 2. 省略一堆读取value、basePackages、basePackageClasses等注解属性,并把属性设置到scanner的代码
// 假装这里是读取注解属性&配置scanner属性的代码
// 3.扫描Mapper&注册
scanner.scan(StringUtils.toStringArray(basePackages));
}
解释
简单来说,就是创建并配置ClassPathMapperScanner
用来扫描并注册Mapper的Bean定义。这个ClassPathMapperScanner
继承自Spring的ClassPathBeanDefinitionScanner
,复用了他的包扫描能力,重写了doScan
方法:将扫描到的接口转换为MapperFactoryBean
的Bean定义:
java
@Override
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
// 调用 Spring 父类方法扫描包,获取接口的 BeanDefinition
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
logger.warn("未扫描到 Mapper 接口");
} else {
// 处理扫描到的 BeanDefinition:转换为 MapperFactoryBean
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
// 核心处理:将接口的 BeanDefinition 转换为 MapperFactoryBean 的定义
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
// 设置beanDefinition的类是MapperFactoryBean、泛型是Mapper接口类
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
definition.setBeanClass(this.mapperFactoryBean.getClass());
// 省略其他配置beanDefinition代码
boolean explicitFactoryUsed = ...
// 如果没有设置sqlSessionTemplate、sqlSessionFactory,则设置by类型注入,这样Spring就可以把唯一的sqlSessionTemplate或sqlSessionFactory自动注入了
if (!explicitFactoryUsed) {
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
接下来我们看下核心,MapperFactoryBean是如何创建Mapper的
java
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
@Override
public Class<T> getObjectType() {
return this.mapperInterface;
}
@Override
public boolean isSingleton() {
return true;
}
哎呦喂,好些也没啥特别的,指定了类是接口类,单例的,创建方法,和原生的好像也没啥区别,还是使用SqlSession创建Mapper的动态代理类。他怎么就能单例了呢?!
我们看看这个getSqlSession
有什么特殊的呢,这个getSqlSession
方法,是继承自SqlSessionDaoSupport
:
java
private SqlSession sqlSession;
private boolean externalSqlSession;
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (!this.externalSqlSession) {
this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
}
}
public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
this.sqlSession = sqlSessionTemplate;
this.externalSqlSession = true;
}
public SqlSession getSqlSession() {
return this.sqlSession;
}
注意这两个set方法。如果我们只有单数据源,不需要指定@MapperScan
的sqlSessionFactoryRef属性,那么Spring会按照类型,把唯一的SqlSessionFactory
自动注入进来。如果没有指定SqlSessionTemplate
,那么自动创建一个SqlSessionTemplate
作为SqlSession
。显然这个SqlSession
与原生的DefaultSqlSession
不同,这应该是Spring中的Mapper可以单例的核心。
四、SqlSessionFactoryBean & SqlSessionTemplate
1. SqlSessionFactoryBean
在解析SqlSessionTemplate
前,我们先看下SqlSessionFactoryBean
。SqlSessionFactoryBean
也是一个FactoryBean
。核心在getObject
中。
java
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
SqlSessionFactory
构造在afterPropertiesSet
方法中,代码很长,创建了Mybatis的核心配置类Configuration
,并配置了很多属性,不过大多数配置与原生的类似,只不过换一种写法。最大的不同,在这里:
java
// SqlSessionFactoryBean 内部逻辑
if (this.transactionFactory == null) {
this.transactionFactory = new SpringManagedTransactionFactory();
}
configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
关键不同点
- 事务管理器,是为Spring实现的
SpringManagedTransactionFactory
。 - dataSource,使用的是Spring的数据库连接池,这个
this.dataSource
,就是在配置类那里配置的数据源。
也就是说,核心实现,还是原生的DefaultSqlSessionFactory
,但是事务管理器换成了SpringManagedTransactionFactory
,数据源换成了Spring的连接池。
2. SqlSessionTemplate
2.1 SqlSessionTemplate
SqlSessionTemplate
是Mybatis集成到Spring的核心,他实现了SqlSession
接口,可以替换原生的非线程安全的DefaultSqlSession
,并且可以与Spring的事务配合,让你可以直接使用Spring的事务管理,而不需要自己手动管理。我们先看下他的构造函数:
java
public class SqlSessionTemplate implements SqlSession {
// 上文提到的SqlSessionFactory
private final SqlSessionFactory sqlSessionFactory;
// 执行器类型(SIMPLE/REUSE/BATCH)
private final ExecutorType executorType;
// 原生 SqlSession 的代理对象(核心!所有方法调用都会被拦截)
private final SqlSession sqlSessionProxy;
// 异常转换器,将 MyBatis 异常转为 Spring 数据访问异常
private final PersistenceExceptionTranslator exceptionTranslator;
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
// 校验参数
notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
notNull(executorType, "Property 'executorType' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
// 创建 SqlSession 代理对象,拦截所有方法调用
this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
SqlSession.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor() // 代理处理器
);
}
// 所有SqlSession方法都委托给代理对象
@Override
public <T> T selectOne(String statement) {
return sqlSessionProxy.selectOne(statement);
}
}
2.2 SqlSessionInterceptor
好家伙,又是JDK的动态代理,也就是说,核心实现在SqlSessionInterceptor
中。
java
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 1. 获取SqlSession
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator
);
try {
// 2. 调用原生 SqlSession 的目标方法(如 selectOne、update)
Object result = method.invoke(sqlSession, args);
// 3. 非事务场景下自动提交
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
sqlSession.commit(true); // 自动提交
}
return result;
} catch (Throwable t) {
// 4. 处理异常:转为 Spring 数据访问异常
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
// 转换异常(如将 MyBatis 的 SqlException 转为 Spring 的 DataAccessException)
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
// 5. 释放资源:非事务场景下关闭 SqlSession
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
关键步骤解析:
1. 获取线程绑定的 SqlSession
(getSqlSession
方法)
先看下getSqlSession
方法,是如何获取线程安全的SqlSession
:
java
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
// 从线程上下文获取 SqlSessionHolder(封装了 SqlSession)
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session; // 获取线程中的sqlSession
}
// 当前线程中没有:创建新的 SqlSession
session = sessionFactory.openSession(executorType);
// 将新SqlSession绑定到线程
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
简单说,就是如果存在,就直接返回SqlSession
对象,没有则通过SqlSessionFactory
创建新的SqlSession
,并绑定到线程中。
看下TrasactionSynchronizationManager
的getResource
方法,从ThreadLocal
中获取,原生的SqlSession
虽然不是线程安全的,但这里保证了一个SqlSession
只被一个线程使用,也就达到了线程安全的目的。
java
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<Map<Object, Object>>("Transactional resources");
public static Object getResource(Object key) {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
Object value = doGetResource(actualKey);
if (value != null && logger.isTraceEnabled()) {
logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" +
Thread.currentThread().getName() + "]");
}
return value;
}
在看下注册SqlSession
注册到线程的代码:
java
private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
SqlSessionHolder holder;
// 开启事务才会注册到TransactionSynchronizationManager
if (TransactionSynchronizationManager.isSynchronizationActive()) {
//省略非核心代码
holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
TransactionSynchronizationManager.bindResource(sessionFactory, holder);
//注册同步器
TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
holder.setSynchronizedWithTransaction(true);
holder.requested();
}
} else {
LOGGER.debug(() -> "SqlSession [" + session
+ "] was not registered for synchronization because synchronization is not active");
}
}
注意:开启事务,才会绑定到TransactionSynchronizationManager
的线程变量中,否则只是打印个日志
2. 事务判断与自动提交 / 回滚
回到SqlSessionInterceptor
的invoke
方法,获取SqlSession
,执行SqlSession
目标方法后:
- 若处于事务中:不自动提交,由 Spring 事务管理器在事务结束时统一提交 / 回滚。
- 若不处于事务中:方法执行成功后自动提交(
sqlSession.commit(true)
)。
那么如何判断是否在事务中呢:
java
private static boolean isSqlSessionTransactional(SqlSession session, SqlSessionFactory sessionFactory) {
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
return holder != null && holder.getSqlSession() == session;
}
就是判断TransactionSynchronizationManager
是否存在SqlSessionHolder
,上文中已经分析了,如果不存在SqlSession
,会自动创建,但只有在Spring的事务中,才会绑定到TransactionSynchronizationManager
。因此靠判断TransactionSynchronizationManager
是否存在SqlSessionHolder
,就可以判断是否在事务内。
3. 资源释放(closeSqlSession
方法)
SqlSessionInterceptor
的invoke
方法,在异常和finally代码块中,会执行closeSqlSession
方法,乍看起来很奇怪,因为如果在事务中,SqlSession
是共享的,怎么可以随意关闭呢,我们看下源码:
java
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
if (holder != null && holder.getSqlSession() == session) {
holder.released(); // 事务内:仅标记释放(计数减一),不关闭
} else {
session.close(); // 非事务:直接关闭
}
}
破案了,只有在非事务场景下,才会执行关闭,否则只是released
标记释放(计数减一),不关闭。
2.3 与Spring事务结合
到此为止,已经能够解释,为什么Spring中的Mapper可以是单例的了,因为他依赖的不是原生的SqlSession
,而是SqlSessionTemplate
,对原生的SqlSession
进行了代理:
- 在Spring的事务内,第一次执行时创建
SqlSession
,并通过ThreadLocal
机制,事务内共享一个SqlSession
。 - 非事务内,每次执行创建新的
SqlSession
,执行后直接关闭。
那么问题来了,SqlSession
的创建看起来没什么特别的,那Mybatis与Spring的事务是如何结合起来的呢?
疑问解答
1. SpringManagedTransactionFactory
还记得SpringManagedTransactionFactory
么,就是我们在解析SqlSessionFactoryBean
时,创建的SqlSessionFactory
与原生的核心不同点,就是它的TransactionFactory
是专门为Spring环境实现的SpringManagedTransactionFactory
,而不是原生的ManagedTransactionFactory
。
而这个Factory,创建的是这样的一个事务:
java
@Override
public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
return new SpringManagedTransaction(dataSource);
}
这个SpringManagedTransaction
的getConnection()
方法,会先检查Spring事务是否已绑定连接,若有则复用,否则创建新连接。
java
public class SpringManagedTransaction implements Transaction {
private final DataSource dataSource; // Spring 管理的数据源
private Connection connection; // 数据库连接(复用 Spring 事务的连接)
private boolean isConnectionTransactional; // 是否处于 Spring 事务中
private boolean autoCommit; // 连接的自动提交状态
@Override
public Connection getConnection() throws SQLException {
if (this.connection == null) {
openConnection(); // 首次调用时获取连接
}
return this.connection;
}
private void openConnection() throws SQLException {
// 1. 从 Spring 事务同步管理器获取与数据源绑定的连接持有者
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
// 2. 存在连接持有者:复用 Spring 事务的连接
this.connection = conHolder.getConnection();
this.isConnectionTransactional = true; // 标记为事务内连接
this.autoCommit = this.connection.getAutoCommit(); // 记录原始自动提交状态
} else {
// 3. 不存在连接持有者:非事务场景,直接从数据源获取新连接
this.connection = DataSourceUtils.getConnection(dataSource);
this.isConnectionTransactional = false;
this.autoCommit = this.connection.getAutoCommit();
}
}
}
Spring的事务管理,简单来说,就是在配置的事务开启位置,获取JDBC的Connection
,并设置他的autoCommit为false,再把Connection
绑定到线程变量中,之后的数据库操作都使用同一个Connection
,也就达到了事务自动管理的目的。
另外SpringManagedTransaction
的提交与回滚,也只在非事务的场景下执行:
java
@Override
public void commit() throws SQLException {
// 事务内:不执行提交(由 Spring 事务管理器统一提交)
if (this.connection != null && !isConnectionTransactional && !autoCommit) {
// 非事务场景:执行提交(此时连接是 MyBatis 自己创建的)
connection.commit();
}
}
@Override
public void rollback() throws SQLException {
// 事务内:不执行回滚(由 Spring 事务管理器统一回滚)
if (this.connection != null && !isConnectionTransactional && !autoCommit) {
// 非事务场景:执行回滚
connection.rollback();
}
}
2. SqlSessionSynchronization
SpringManagedTransaction
在Spring的事务内,是不会真正提交&回滚的,那Mybatis如何感知事务提交呢,比如Mybatis的二级缓存,是提交后才会生效的,如果不感知事务提交,二级缓存不就挂了么?!
还记得在创建新的SqlSession
后,如果在Spring事务内,注册到TransactionSynchronizationManager
的那段代码么:
java
//绑定SqlSession
holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
TransactionSynchronizationManager.bindResource(sessionFactory, holder);
//注册同步器
TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
holder.setSynchronizedWithTransaction(true);
holder.requested();
这里除了SqlSession
外,还注册了个同步器SqlSessionSynchronization
,这个同步器实现了Spring的TransactionSynchronization
接口,在事务提交或者回滚后,会调用afterCompletion
方法,这也就达到了感知事务结束的目的:
java
private static class SqlSessionSynchronization implements TransactionSynchronization {
private final SqlSessionHolder holder;
private final SqlSessionFactory sessionFactory;
private boolean holderActive = true;
@Override
public void afterCompletion(int status) {
// 移除线程绑定的 SqlSessionHolder
TransactionSynchronizationManager.unbindResource(sessionFactory);
holderActive = false;
// 关闭 SqlSession(将连接归还给连接池)
SqlSessionUtils.closeSqlSession(holder.getSqlSession(), sessionFactory);
}
// 其他回调方法(如 suspend/resume,处理线程挂起/恢复时的资源管理)
@Override
public void suspend() {
if (holderActive) {
TransactionSynchronizationManager.unbindResource(sessionFactory);
}
}
@Override
public void resume() {
if (holderActive) {
TransactionSynchronizationManager.bindResource(sessionFactory, holder);
}
}
}
五、总结
总结一下,Mybatis集成到Spring的实现:
@MapperScan
: 通过@MapperScan
-->ImportBeanDefinitionRegistrar
-->ClassPathMapperScanner
-->MapperFactoryBean
创建Mapper对象,注册到Spring容器中。而这些Mapper,引用的SqlSession
是SqlSessionTemplate
。SqlSessionTemplate
:SqlSessionTemplate
对原生SqlSession
做了动态代理。- 非事务场景 :每次执行使用
SqlSessionFactory
创建新的SqlSession
,执行后直接关闭。 - 事务场景 :第一次执行时,新创建
SqlSession
,同时把SqlSession
绑定到TransactionSynchronizationManager
的线程变量中,之后每次都从这个线程变量中获取,保证一个事务内使用相同的SqlSession
。
- 非事务场景 :每次执行使用
SqlSessionSynchronization
:事务内,第一次创建SqlSession
时,还注册了SqlSessionSynchronization
,可以在Spring执行事务结束后,收到回调,执行afterCompletion
方法,用于关闭SqlSession
。SpringManagedTransaction
:SqlSessionFactoryBean
创建的SqlSessionFactory
,配置了特殊的事务管理器(SpringManagedTransaction
),可以从Spring事务的线程变量中,获取Connection
,进而达到在Spring的事务共用一个Connection
的目的。