在上面一节[MyBatis源码分析(7)]中看到,我们一般会使用mybatis的SqlSessionTemplate
来作为mapper的生成类,该类实际上就是SqlSessionFactory
的一个代理类。在官方文档中,也明确写明了该类是mybatis-spring的核心类。
在上一节中,我们可以看到该类的insert,select, getMapper
等方法。 本节看下具体代理实现。
该类内部通过SqlSessionInterceptor
类来代理SqlSessionFactory
。
我们看到这个类的invoke
方法是这样的:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33public 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. 调用具体方法
Object result = method.invoke(sqlSession, args);
//3. 判断是否需要提交
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
//4. 包装异常
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
//5. 关闭session
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
上面的代码是任意的一个Mapper的方法调用或者说通过SqlSessionTemplate
调用任何方法时候的代理方法,可以看到如下几个步骤(也就是mybatis-spring的核心):
- 获取一个
SqlSession
实例 - 调用具体方法
- 如果当前没有处于spring事务管理,则直接提交(否则由spring管理这个事务)
- 关闭session
- 包装异常为spring的异常
可以看到,这几步其实也是mybatis-spring的核心功能。
mybatis的事务如何嵌入到spring事务管理
这个关键就在上面的第1步 - 获取一个SqlSession
实例。 我们看下实现:
1 | public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { |
对spring的事务管理熟悉点的很容易看出来,这个逻辑与spring的DataSourceUtil
异曲同工: 使用一个holder类包装具体类型(此处SqlSession
),然后通过spring的同步事务管理器(TransactionSynchronizationManager)获取这个holder, 从holder里面获取需要的对象,如果同步事务管理器没有获取到则新建一个并且绑定到spring的同步事务管理器中。
这里介绍下TransactionSynchronizationManager
。 该类是线程级别的资源和事务的同步管理器,通常被资源管理器使用。他的典型使用场景是某些线程独立的、可复用的资源管理。可能说的不是很清楚,官方文档里这样描述:
central helper that manages resources and transaction synchronizations per thread 。。Resource management code should only register synchronizations when this manager is active, which can be checked via isSynchronizationActive(); it should perform immediate resource cleanup else. If transaction synchronization isn’t active, there is either no current transaction, or the transaction manager doesn’t support transaction synchronization.
具体到上面的例子来,TransactionSynchronizationManager
对于同一个sessionFory
,在事务激活的情况下,同一个线程总是返回相同的SessionHolder
。
比如对于一个开启了事务的service的方法,这个方法可能调用多个Mapper接口,那么它们会在同一个事务管理中,SqlSessionHolder
类则维护了一个SqlSession
的引用次数,它辅助了TransactionSynchronizationManager
会处理所有数据库连接的获取与释放,以及回滚,并且保证线程安全。这样就可以避免连接泄漏。SqlSessionHolder
类则维护了一个SqlSession
的引用次数,它主要起到一个辅助作用, 以便于在一个事务中复用资源-也就是SqlSession
。
如何开启mybatis-spring的自动事务管理
既然是使用的spring的事务管理,首先在配置中开启。通常的做法是在配置文件中加入如下的配置:1
2
3<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
这样就开启了spring的事务管理。 然后,使用@Transactional
注解或者AOP
方式标注具体的事务处理位置即可。
而对于mybatis-spring, 则会自动的使用spring的事务管理。注解或者AOP
标明的位置中的mybatis操作,都会被spring事务管理所管理。文档在这里
必须注意的是,mybatis-spring的连接在你的项目中没有开启spring事务的情况下,依然存在着连接泄漏的情况。 上面一直有提到,使用@Transactional
注解或者AOP
方式标注具体的事务处理这个必须要设置才行。