9、事务补充
当事务的传播级别为SUPPORTS时,如果当前有事务,则使用当前事务,如果当前没有事务,则以无事务方式执行
这里的无事务方式执行是指,Spring不会提交事务,而且MyBatis也不会执行commit。
除非设置数据库连接的auto-commit属性为true,否则不能写入数据
spring:
datasource:
auto-commit: true
1、propagation = Propagation.REQUIRED
在大多数情况下,我们使用默认隔离级别Propagation.REQUIRED,当propagation = Propagation.REQUIRED时,数据库连接由Spring事务管理器在DataSourceTransactionManager#doBegin中取得
doBegin会在org.springframework.transaction.support.AbstractPlatformTransactionManager#getTransaction中被调用
org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin
protected void doBegin(Object transaction, TransactionDefinition definition) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
Connection con = null;
try {
if (!txObject.hasConnectionHolder() ||
txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
// 由事务管理器从dataSource中获取连接
Connection newCon = obtainDataSource().getConnection();
if (logger.isDebugEnabled()) {
logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
}
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}
txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
con = txObject.getConnectionHolder().getConnection();
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
txObject.setPreviousIsolationLevel(previousIsolationLevel);
// Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
// so we don't want to do it unnecessarily (for example if we've explicitly
// configured the connection pool to set it already).
if (con.getauto-commit()) {
txObject.setMustRestoreauto-commit(true);
if (logger.isDebugEnabled()) {
logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
}
// 当以有事务方式执行时,Spring会将数据库连接的auto-commit属性改为false
// 所以当开启新的事务或者说有事务方式执行时,auto-commit属性会无效
// 因为有事务,不论是当前创建新的事务,或者是继承自上一层的事务,都会是用的Spring获取的连接,auto-commit都会被改成false
con.setauto-commit(false);
}
prepareTransactionalConnection(con, definition);
txObject.getConnectionHolder().setTransactionActive(true);
int timeout = determineTimeout(definition);
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
}
// Bind the connection holder to the thread.
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
}
}
catch (Throwable ex) {
if (txObject.isNewConnectionHolder()) {
DataSourceUtils.releaseConnection(con, obtainDataSource());
txObject.setConnectionHolder(null, false);
}
throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
}
}
以有事务方式执行时,事务的提交,也是由Spring执行
具体是在org.springframework.transaction.support.AbstractPlatformTransactionManager#processCommit中,具体不作分析了
还有一个问题。为什么有事务方式执行时,MyBatis不会提交事务?
相关逻辑在这里
org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
Object result = method.invoke(sqlSession, args);
// isSqlSessionTransactional会判断,当前执行SQL的会话sqlSession和在TransactionSynchronizationManager中和当前线程绑定的会话是否是同一个
// 如果是同一个,则说明事务由Spring管理,所以下面的sqlSession.commit(true);不会执行
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
// release the connection to avoid a deadlock if the translator is no loaded. See issue #22
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
2、当事务的传播级别由REQUIRED改成SUPPORTS时
propagation = Propagation.SUPPORTS
在org.springframework.transaction.support.AbstractPlatformTransactionManager#getTransaction中,由于propagation = Propagation.SUPPORTS,不会创建新的事务。
DataSourceTransactionManager#doBegin也不会被调用,所以auto-commit属性不会被修改,那么连接是在在哪里取的呢?
答案是在org.apache.ibatis.executor.SimpleExecutor#prepareStatement中由MyBatis自己搞定了
org.apache.ibatis.executor.SimpleExecutor#prepareStatement
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
org.apache.ibatis.executor.BaseExecutor#getConnection
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
org.apache.ibatis.transaction.jdbc.JdbcTransaction#getConnection
@Override
public Connection getConnection() throws SQLException {
if (connection == null) {
openConnection();
}
return connection;
}
传播级别为SUPPORTS时事务的提交有两种可能
a、auto-commit属性为false,事务不会提交,或者说connection不会commit(SqlSessionInterceptor的isSqlSessionTransactional此时仍然返回true)
b、auto-commit属性为true,事务由java.sql.Connection各个实现类自己在执行完SQL后自动提交了
3、不使用Spring事务,去掉@Transactionl注解
取数据库连接和使用propagation = Propagation.SUPPORTS时相同
事务的提交也有两种可能
a、auto-commit属性为false,事务由MyBatis提交(SqlSessionInterceptor的isSqlSessionTransactional此时返回false)
b、auto-commit属性为true,事务由java.sql.Connection各个实现类自己在执行完SQL后自动提交了
a和b两种情况都是执行一条SQL做一次commit。
在情况b中,SqlSessionInterceptor的isSqlSessionTransactional同样返回false,MyBatis为什么没有提交事务?
SqlSessionInterceptor中sqlSession.commit(true),最终调用的是org.apache.ibatis.transaction.jdbc.JdbcTransaction#commit
org.apache.ibatis.transaction.jdbc.JdbcTransaction#commit
@Override
public void commit() throws SQLException {
// 当connection的auto-commit属性为true时,MyBatis不会自动commit
if (connection != null && !connection.getauto-commit()) {
if (log.isDebugEnabled()) {
log.debug("Committing JDBC Connection [" + connection + "]");
}
connection.commit();
}
}
最后一个问题。propagation = Propagation.REQUIRED时,如果设置auto-commit属性为true,Spring会自动改成false,那么事务提交之后会将连接的自动提交属性改回来吗
答案是会
DataSourceTransactionManager.doBegin片段
if (con.getauto-commit()) {
// 这里将DataSourceTransactionObject的mustRestoreAutoCommit属性改成true,表示事务提交之后要重置connection的auto-commit属性为true
txObject.setMustRestoreauto-commit(true);
if (logger.isDebugEnabled()) {
logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
}```
// 当以有事务方式执行时,Spring会将数据库连接的auto-commit属性改为false
// 所以当开启新的事务或者说有事务方式执行时,auto-commit属性会无效
// 因为有事务,不论是当前创建新的事务,或者是继承自上一层的事务,都会是用的Spring获取的连接,auto-commit都会被改成false
con.setauto-commit(false);
}
具体调用路径是
org.springframework.transaction.support.AbstractPlatformTransactionManager#processCommit
org.springframework.transaction.support.AbstractPlatformTransactionManager#cleanupAfterCompletion
org.springframework.jdbc.datasource.DataSourceTransactionManager#doCleanupAfterCompletion
protected void doCleanupAfterCompletion(Object transaction) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
// Remove the connection holder from the thread, if exposed.
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.unbindResource(obtainDataSource());
}
// Reset connection.
Connection con = txObject.getConnectionHolder().getConnection();
try {
// 这里DataSourceTransactionObject的mustRestoreAutoCommit属性为true,修改连接auto-commit属性为true
if (txObject.isMustRestoreAutoCommit()) {
con.setAutoCommit(true);
}
DataSourceUtils.resetConnectionAfterTransaction(con, txObject.getPreviousIsolationLevel());
}
catch (Throwable ex) {
logger.debug("Could not reset JDBC Connection after transaction", ex);
}
if (txObject.isNewConnectionHolder()) {
if (logger.isDebugEnabled()) {
logger.debug("Releasing JDBC Connection [" + con + "] after transaction");
}
DataSourceUtils.releaseConnection(con, this.dataSource);
}
txObject.getConnectionHolder().clear();
}