當事務的傳播級別為SUPPORTS時冤吨,如果當前有事務蒿柳,則使用當前事務,如果當前沒有事務漩蟆,則以無事務方式執(zhí)行
這里的無事務方式執(zhí)行是指垒探,Spring不會提交事務,而且MyBatis也不會執(zhí)行commit怠李。
除非設(shè)置數(shù)據(jù)庫連接的auto-commit屬性為true圾叼,否則不能寫入數(shù)據(jù)
spring:
datasource:
auto-commit: true
1、propagation = Propagation.REQUIRED
在大多數(shù)情況下捺癞,我們使用默認隔離級別Propagation.REQUIRED夷蚊,當propagation = Propagation.REQUIRED時,數(shù)據(jù)庫連接由Spring事務管理器在DataSourceTransactionManager#doBegin中取得
doBegin會在org.springframework.transaction.support.AbstractPlatformTransactionManager#getTransaction中被調(diào)用
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");
}
// 當以有事務方式執(zhí)行時髓介,Spring會將數(shù)據(jù)庫連接的auto-commit屬性改為false
// 所以當開啟新的事務或者說有事務方式執(zhí)行時惕鼓,auto-commit屬性會無效
// 因為有事務,不論是當前創(chuàng)建新的事務唐础,或者是繼承自上一層的事務箱歧,都會是用的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);
}
}
以有事務方式執(zhí)行時一膨,事務的提交呀邢,也是由Spring執(zhí)行
具體是在org.springframework.transaction.support.AbstractPlatformTransactionManager#processCommit中,具體不作分析了
還有一個問題豹绪。為什么有事務方式執(zhí)行時价淌,MyBatis不會提交事務?
相關(guān)邏輯在這里
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會判斷,當前執(zhí)行SQL的會話sqlSession和在TransactionSynchronizationManager中和當前線程綁定的會話是否是同一個
// 如果是同一個蝉衣,則說明事務由Spring管理豺型,所以下面的sqlSession.commit(true);不會執(zhí)行
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姻氨,不會創(chuàng)建新的事務。
DataSourceTransactionManager#doBegin也不會被調(diào)用剪验,所以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各個實現(xiàn)類自己在執(zhí)行完SQL后自動提交了
3、不使用Spring事務乘粒,去掉@Transactionl注解
取數(shù)據(jù)庫連接和使用propagation = Propagation.SUPPORTS時相同
事務的提交也有兩種可能
a豌注、auto-commit屬性為false,事務由MyBatis提交(SqlSessionInterceptor的isSqlSessionTransactional此時返回false)
b灯萍、auto-commit屬性為true轧铁,事務由java.sql.Connection各個實現(xiàn)類自己在執(zhí)行完SQL后自動提交了
a和b兩種情況都是執(zhí)行一條SQL做一次commit。
在情況b中旦棉,SqlSessionInterceptor的isSqlSessionTransactional同樣返回false齿风,MyBatis為什么沒有提交事務?
SqlSessionInterceptor中sqlSession.commit(true)绑洛,最終調(diào)用的是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時真屯,如果設(shè)置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");
}```
// 當以有事務方式執(zhí)行時纪他,Spring會將數(shù)據(jù)庫連接的auto-commit屬性改為false
// 所以當開啟新的事務或者說有事務方式執(zhí)行時,auto-commit屬性會無效
// 因為有事務晾匠,不論是當前創(chuàng)建新的事務茶袒,或者是繼承自上一層的事務,都會是用的Spring獲取的連接凉馆,auto-commit都會被改成false
con.setauto-commit(false);
}
具體調(diào)用路徑是
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();
}