Spring官方并没有提供对MyBatis的集成方案,于是MyBatis项目组自己写了一个项目mybatis-spring专门用于在spring中使用MyBatis。
mybatis-spring的实现很大程度上依赖spring jdbc的事务管理,所以我们先看一下在spring中直接使用jdbc访问数据库时是如何处理事务的。无论你是使用@Transactional注解这样的AOP配置方式,还是TransactionTemplate这样的编码方式,最终执行的操作事务的代码都会是类似下面这样
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
PlatformTransactionManager txManager = new DataSourceTransactionManager(dataSource);
TransactionStatus status = txManager.getTransaction(def);
try {
//get jdbc connection...
//execute sql...
txManager.commit(status);
}
catch (Exception e) {
txManager.rollback(status);
throw e;
}
可以看到PlatformTransactionManager的getTransaction(), rollback(), commit()是spring处理事务的核心api,分别对应事务的开始,提交和回滚。
spring事务处理的一个关键是保证在整个事务的生命周期里所有执行sql的jdbc connection和处理事务的jdbc connection始终是同一个。然后执行sql的业务代码一般都分散在程序的不同地方,如何让它们共享一个jdbc connection呢?这里spring做了一个前提假设:即一个事务的操作一定是在一个thread中执行,且一个thread中如果有多个不同jdbc connection生成的事务的话,他们必须顺序执行,不能同时存在。(这个假设在绝大多数情况下都是成立的)。基于这个假设,spring在transaction创建时,会用ThreadLocal把创建这个事务的jdbc connection绑定到当前thread,接下来在事务的整个生命周期中都会从ThreadLocal中获取同一个jdbc connection。
我们看一下详细调用过程
- TransactionSynchronizationManager负责从ThreadLocal中存取jdbc connection
- 创建事务的时候会通过dataSource.getConnection()获取一个新的jdbc connection,然后绑定到ThreadLocal
- 在业务代码中执行sql时,通过DataSourceUtils.getConnection()从ThreadLocal中获取当前事务的jdbc connection, 然后在该jdbc connection上执行sql
- commit和rollback事务时,从ThreadLocal中获取当前事务的jdbc connection,然后对该jdbc connection进行commit和rollback
对spring jdbc的事务处理有了了解后,我们来看mybatis是如何通过spring处理事务的。
先看一下配置
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="transactionFactory">
<bean class="org.apache.ibatis.spring.transaction.SpringManagedTransactionFactory" />
</property>
</bean>
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory" />
</bean>
- mybatis-spring依赖DataSourceTransactionManager来处理事务,并没有创建自己的PlatformTransactionManager实现。
- mybatis通过SqlSessionFactoryBuilder创建SqlSessionFactory,而mybatis-spring通过SqlSessionFactoryBean创建SqlSessionFactory。
- 配置使用SpringManagedTransactionFactory来创建MyBatis的Transaction实现SpringManagedTransaction
- 配置使用SqlSessionTemplate代替通过SqlSessionFactory.openSession()获取SqlSession
然后看其调用过程
可以看到mybatis-spring处理事务的主要流程和spring jdbc处理事务并没有什么区别,都是通过DataSourceTransactionManager的getTransaction(), rollback(), commit()完成事务的生命周期管理,而且jdbc connection的创建也是通过DataSourceTransactionManager.getTransaction()完成,mybatis并没有参与其中,mybatis只是在执行sql时通过DataSourceUtils.getConnection()获得当前thread的jdbc connection,然后在其上执行sql。
下面结合代码来看
<SqlSessionUtils>:
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Creating a new SqlSession");
}
session = sessionFactory.openSession(executorType);
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
SqlSessionHolder holder;
if (TransactionSynchronizationManager.isSynchronizationActive()) {
Environment environment = sessionFactory.getConfiguration().getEnvironment();
if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registering transaction synchronization for SqlSession [" + session + "]");
}
holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
TransactionSynchronizationManager.bindResource(sessionFactory, holder);
TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
holder.setSynchronizedWithTransaction(true);
holder.requested();
} else {
if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because DataSource is not transactional");
}
} else {
throw new TransientDataAccessResourceException(
"SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
}
}
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because synchronization is not active");
}
}
}
执行sql时调用sqlSessionTemplate的insert,update,delete方法,sqlSessionTemplate是DefaultSqlSession的一个代理类,它通过SqlSessionUtils.getSqlSession()试图从ThreadLocal获取当前事务所使用的SqlSession。如果是第一次获取时会调用SqlSessionFactory.openSession()创建一个SqlSession并绑定到ThreadLocal,同时还会通过TransactionSynchronizationManager注册一个SqlSessionSynchronization。
<SqlSessionSynchronization>:
public void beforeCommit(boolean readOnly) {
// Connection commit or rollback will be handled by ConnectionSynchronization or
// DataSourceTransactionManager.
// But, do cleanup the SqlSession / Executor, including flushing BATCH statements so
// they are actually executed.
// SpringManagedTransaction will no-op the commit over the jdbc connection
// TODO This updates 2nd level caches but the tx may be rolledback later on!
if (TransactionSynchronizationManager.isActualTransactionActive()) {
try {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Transaction synchronization committing SqlSession [" + this.holder.getSqlSession() + "]");
}
this.holder.getSqlSession().commit();
} catch (PersistenceException p) {
if (this.holder.getPersistenceExceptionTranslator() != null) {
DataAccessException translated = this.holder
.getPersistenceExceptionTranslator()
.translateExceptionIfPossible(p);
if (translated != null) {
throw translated;
}
}
throw p;
}
}
SqlSessionSynchronization是一个事务生命周期的callback接口,mybatis-spring通过SqlSessionSynchronization在事务提交和回滚前分别调用DefaultSqlSession.commit()和DefaultSqlSession.rollback()
<BaseExecutor>:
public void commit(boolean required) throws SQLException {
if (closed) throw new ExecutorException("Cannot commit, transaction is already closed");
clearLocalCache();
flushStatements();
if (required) {
transaction.commit();
}
}
public void rollback(boolean required) throws SQLException {
if (!closed) {
try {
clearLocalCache();
flushStatements(true);
} finally {
if (required) {
transaction.rollback();
}
}
}
}
public void clearLocalCache() {
if (!closed) {
localCache.clear();
localOutputParameterCache.clear();
}
}
<SpringManagedTransaction>:
this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
public void commit() throws SQLException {
if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Committing JDBC Connection [" + this.connection + "]");
}
this.connection.commit();
}
}
public void rollback() throws SQLException {
if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Rolling back JDBC Connection [" + this.connection + "]");
}
this.connection.rollback();
}
}
<DataSourceUtils>:
/**
* Determine whether the given JDBC Connection is transactional, that is,
* bound to the current thread by Spring's transaction facilities.
* @param con the Connection to check
* @param dataSource the DataSource that the Connection was obtained from
* (may be {@code null})
* @return whether the Connection is transactional
*/
public static boolean isConnectionTransactional(Connection con, DataSource dataSource) {
if (dataSource == null) {
return false;
}
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
return (conHolder != null && connectionEquals(conHolder, con));
}
这里的DefaultSqlSession只会进行一些自身缓存的清理工作,并不会真正提交事务给数据库,原因是这里的DefaultSqlSession使用的Transaction实现为SpringManagedTransaction,SpringManagedTransaction在提交事务前会检查当前事务是否应该由spring控制,如果是,则不会自己提交事务,而将提交事务的任务交给spring,所以DefaultSqlSession并不会自己处理事务。
<SpringManagedTransaction>:
public Connection getConnection() throws SQLException {
if (this.connection == null) {
openConnection();
}
return this.connection;
}
/**
* Gets a connection from Spring transaction manager and discovers if this
* {@code Transaction} should manage connection or let it to Spring.
* <p>
* It also reads autocommit setting because when using Spring Transaction MyBatis
* thinks that autocommit is always false and will always call commit/rollback
* so we need to no-op that calls.
*/
private void openConnection() throws SQLException {
this.connection = DataSourceUtils.getConnection(this.dataSource);
this.autoCommit = this.connection.getAutoCommit();
this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"JDBC Connection ["
+ this.connection
+ "] will"
+ (this.isConnectionTransactional ? " " : " not ")
+ "be managed by Spring");
}
}
DefaultSqlSession执行sql时,会通过SpringManagedTransaction调用DataSourceUtils.getConnection()从ThreadLocal中获取jdbc connection并在其上执行sql。
总结:mybatis-spring处理事务的主要流程和spring jdbc处理事务并没有什么区别,都是通过DataSourceTransactionManager的getTransaction(), rollback(), commit()完成事务的生命周期管理,而且jdbc connection的创建也是通过DataSourceTransactionManager.getTransaction()完成,mybatis并没有参与其中,mybatis只是在执行sql时通过DataSourceUtils.getConnection()获得当前thread的jdbc connection,然后在其上执行sql。
mybatis-spring做的最主要的事情是:
- 在SqlSession执行sql时通过用SpringManagedTransaction代替mybatis的JdbcTransaction,让SqlSession从spring的ThreadLocal中获取jdbc connection。
- 通过注册事务生命周期callback接口SqlSessionSynchronization,让SqlSession有机会在spring管理的事务提交或回滚时清理自己的内部缓存。
来源:oschina
链接:https://my.oschina.net/u/1452390/blog/785621