目录
前言
在有事务的情况下,为了保证事务的特性,spring和mybatis结合的过程做了哪些处理呢?本文从源码上分析,首先分析如何保证spring和mybatis获取connection的一致性,然后分析sqlsession获取的过程以及带来的影响。
一、connection的获取过程
在有spring事务参与的情况下,mybatis获取connection调用的是spring自己的dataSourceUtils的方法,在前面spring构建事务的过程中也是使用的同样方法,获取connection,并将connection写入本地连接中,从而保证mybatis查询过程中使用的connection与spring事务中的一致,保证spring事务中的提交和回滚能够生效。
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
private void openConnection() throws SQLException {
this.connection = DataSourceUtils.getConnection(this.dataSource);
this.autoCommit = this.connection.getAutoCommit();
this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
LOGGER.debug(() -> "JDBC Connection [" + this.connection + "] will"
+ (this.isConnectionTransactional ? " " : " not ") + "be managed by Spring");
}
二、sqlsession的相关源码分析
参与的情况在mapperFactoryBean中的getObject方法中获取的sqlsession是SqlSessionTemplate,本文主要针对有事务参与的情况下,sqlsessiontemplate请求过程中的一些问题进行分析,比如sqpsession的线程安全性,sqlsession的获取流程,事务提交过程等,过程可能比较乱,基本就是围绕几个问题,并不是对流程进行系统分析,回头有机会再对流程进行分析。
1.整体流程
本文主要针对以下代码分析,重点分析getSqlSession和sqlsession.commit方法。
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//获取sqlsession
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
try {
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
//提交sqlsession
sqlSession.commit(true);
}
return result;
}
}
2.sqlsession的获取
在获取sqlsession的时候,首先尝试从事务管理器中获取,获取到了直接返回,获取不到就新建一个sqlsession。DefaultSqlSession本身不是线程安全的,但是在与spring结合之后,能够保证不同的事务中使用不同的sqlsession,从而保证线程安全性,但同时也有一个问题,如果在事务中使用多线程,会导致事务失效。
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
//在TransactionSynchronizationManager中获取session
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
LOGGER.debug(() -> "Creating a new SqlSession");
//获取不到session时新创建session
session = sessionFactory.openSession(executorType);
//如果存在事务,将当前的sessionFactory作为key注册到TransactionSynchronizationManager中
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
3.sqlsession的提交方法
commit方法中主要是对缓存进行操作,在没有事务的情况下,还会调用connection的commit方法进行提交。
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 commit() {
if (clearOnCommit) {
delegate.clear();
}
//把本地缓存刷新到二级缓存。
flushPendingEntries();
reset();
}
在没有事务的时候,会直接调用commit方法,进行缓存的相关操作;在有事务的情况下,会在triggerBeforeCommit方法中调用commit方法,由于事务是一个整体,所以需要在事务执行完成后头统一提交,而在前面获取sqlsession的过程也保证了一个事务的执行过程中sqlsession是统一的,这样能够保证在事务执行完成之后同意更新一级缓存和二级缓存,避免出现事务没有提交就更新缓存的情况,或者事务回滚导致缓存出现脏数据的情况。
private void processCommit(DefaultTransactionStatus status) throws TransactionException {
try {
boolean beforeCompletionInvoked = false;
try {
boolean unexpectedRollback = false;
prepareForCommit(status);
triggerBeforeCommit(status);
triggerBeforeCompletion(status);
beforeCompletionInvoked = true;
}
}
三、SqlSessionSynchronization
spring在事务提交的时候直接通过connection的commit方法提交,回滚的时候通过connection的rollback方法回滚,同时mybatis中的Executor接口也有commit和rollback方法,而且commit方法还会将一级缓存中的内容提交到二级缓存。
spring事物中的提交方法是如何与mybatis的提交方法相关联,主要是通过SqlSessionUtils类中的内部静态类SqlSessionSynchronization。
SqlSessionSynchronization间接实现了TransactionSynchronization接口,能够在spring事物执行的过程中穿插一些mybatis的方法,从而实现spring事务与mybatis的处理之间的联系,其实还是Spring开放的接口牛逼。
private static final class SqlSessionSynchronization extends TransactionSynchronizationAdapter {}
public abstract class TransactionSynchronizationAdapter implements TransactionSynchronization, Ordered {}
Spring事务的processCommit方法中的triggerBeforeCommit方法会调用所有TransactionSynchronization的beforeCommit方法,注意这里是指当前线程本地所有的,记录在事务线程本地的。在beforeCommit方法中,调用了sqlSession的commit方法,间接调用了Executor的commit方法。实现了将一级缓存中的内容刷新到二级缓存。
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;
}
}
}
在beforeCompletion方法中将当前事务关联的sqlSession关闭。
public void beforeCompletion() {
// Issue #18 Close SqlSession and deregister it now
// because afterCompletion may be called from a different thread
if (!this.holder.isOpen()) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Transaction synchronization deregistering SqlSession [" + this.holder.getSqlSession() + "]");
}
TransactionSynchronizationManager.unbindResource(sessionFactory);
this.holderActive = false;
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Transaction synchronization closing SqlSession [" + this.holder.getSqlSession() + "]");
}
this.holder.getSqlSession().close();
}
}
在afterCompletion方法中将SqlSessionHolder重置。
public void afterCompletion(int status) {
if (this.holderActive) {
// afterCompletion may have been called from a different thread
// so avoid failing if there is nothing in this one
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Transaction synchronization deregistering SqlSession [" + this.holder.getSqlSession() + "]");
}
TransactionSynchronizationManager.unbindResourceIfPossible(sessionFactory);
this.holderActive = false;
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Transaction synchronization closing SqlSession [" + this.holder.getSqlSession() + "]");
}
this.holder.getSqlSession().close();
}
this.holder.reset();
}
比较有意思的是在SqlSessionSynchronization类中并没有rollback相关的单独处理,想想确实回滚就相当于当前操作没有做过,由于spring与mybatis结合之后一级缓存失效,也不需要清除一级缓存。
总结
本文主要对connection和sqlsession的获取过程进行分析,分析了sqlsesssion的线程安全性,有事务的情况下sqlsession的一致性及其原因和mybatis和sqlsession的connection的一致性,相对比较简单,有机会还是要把主流程分析一下。