正文 上一篇文章講解了獲取事務,並且通過獲取的connection設置只讀、隔離級別等,這篇文章講解剩下的事務的回滾和提交。最全面的Java面試網站 回滾處理 之前已經完成了目標方法運行前的事務準備工作,而這些準備工作最大的目的無非是對於程式沒有按照我們期待的那樣進行,也就是出現特定的錯誤,那麼,當 ...
正文
上一篇文章講解了獲取事務,並且通過獲取的connection設置只讀、隔離級別等,這篇文章講解剩下的事務的回滾和提交。最全面的Java面試網站
回滾處理
之前已經完成了目標方法運行前的事務準備工作,而這些準備工作最大的目的無非是對於程式沒有按照我們期待的那樣進行,也就是出現特定的錯誤,那麼,當出現錯誤的時候,Spring是怎麼對數據進行恢復的呢?
protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
// 當拋出異常時首先判斷當前是否存在事務,這是基礎依據
if (txInfo != null && txInfo.getTransactionStatus() != null) {
if (logger.isTraceEnabled()) {
logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
"] after exception: " + ex);
}
// 這裡判斷是否回滾預設的依據是拋出的異常是否是RuntimeException或者是Error的類型
if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
try {
txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
}
catch (TransactionSystemException ex2) {
logger.error("Application exception overridden by rollback exception", ex);
ex2.initApplicationException(ex);
throw ex2;
}
catch (RuntimeException | Error ex2) {
logger.error("Application exception overridden by rollback exception", ex);
throw ex2;
}
}
else {
// We don't roll back on this exception.
// Will still roll back if TransactionStatus.isRollbackOnly() is true.
// 如果不滿足回滾條件即使拋出異常也同樣會提交
try {
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
catch (TransactionSystemException ex2) {
logger.error("Application exception overridden by commit exception", ex);
ex2.initApplicationException(ex);
throw ex2;
}
catch (RuntimeException | Error ex2) {
logger.error("Application exception overridden by commit exception", ex);
throw ex2;
}
}
}
}
在對目標方法的執行過程中,一旦出現Throwable就會被引導至此方法處理,但是並不代表所有的Throwable都會被回滾處理,比如我們最常用的Exception,預設是不會被處理的。 預設情況下,即使出現異常,數據也會被正常提交,而這個關鍵的地方就是在txlnfo.transactionAttribute.rollbackOn(ex)這個函數。
回滾條件
@Override
public boolean rollbackOn(Throwable ex) {
return (ex instanceof RuntimeException || ex instanceof Error);
}
預設情況下Spring中的亊務異常處理機制只對RuntimeException和Error兩種情況感興趣,我們可以利用註解方式來改變,例如:
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
回滾處理
當然,一旦符合回滾條件,那麼Spring就會將程式引導至回滾處理函數中。
分享一份大彬精心整理的大廠面試手冊,包含電腦基礎、Java基礎、多線程、JVM、資料庫、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分散式、微服務、設計模式、架構、校招社招分享等高頻面試題,非常實用,有小伙伴靠著這份手冊拿過位元組offer~
需要的小伙伴可以自行下載:
private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
try {
boolean unexpectedRollback = unexpected;
try {
triggerBeforeCompletion(status);
// 如果status有savePoint,說明此事務是NESTD,且為子事務,只回滾到savePoint
if (status.hasSavepoint()) {
if (status.isDebug()) {
logger.debug("Rolling back transaction to savepoint");
}
//回滾到保存點
status.rollbackToHeldSavepoint();
}
// 如果此時的status顯示是新的事務才進行回滾
else if (status.isNewTransaction()) {
if (status.isDebug()) {
logger.debug("Initiating transaction rollback");
}
//如果此時是子事務,我們想想哪些類型的事務會進入到這裡?
//回顧上一篇文章中已存在事務的處理,NOT_SUPPORTED創建的Status是prepareTransactionStatus(definition, null, false...),說明是舊事物,並且事務為null,不會進入
//REQUIRES_NEW會創建一個新的子事務,Status是newTransactionStatus(definition, transaction, true...)說明是新事務,將會進入到這個分支
//PROPAGATION_NESTED創建的Status是prepareTransactionStatus(definition, transaction, false...)是舊事物,使用的是外層的事務,不會進入
//PROPAGATION_SUPPORTS 或 PROPAGATION_REQUIRED或PROPAGATION_MANDATORY存在事務加入事務即可,標記為舊事務,prepareTransactionStatus(definition, transaction, false..)
//說明當子事務,只有REQUIRES_NEW會進入到這裡進行回滾
doRollback(status);
}
else {
// Participating in larger transaction
// 如果status中有事務,進入下麵
// 根據上面分析,PROPAGATION_SUPPORTS 或 PROPAGATION_REQUIRED或PROPAGATION_MANDATORY創建的Status是prepareTransactionStatus(definition, transaction, false..)
// 如果此事務時子事務,表示存在事務,並且事務為舊事物,將進入到這裡
if (status.hasTransaction()) {
if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
if (status.isDebug()) {
logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
}
// 對status中的transaction作一個回滾了的標記,並不會立即回滾
doSetRollbackOnly(status);
}
else {
if (status.isDebug()) {
logger.debug("Participating transaction failed - letting transaction originator decide on rollback");
}
}
}
else {
logger.debug("Should roll back transaction but cannot - no transaction available");
}
// Unexpected rollback only matters here if we're asked to fail early
if (!isFailEarlyOnGlobalRollbackOnly()) {
unexpectedRollback = false;
}
}
}
catch (RuntimeException | Error ex) {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
throw ex;
}
triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
}
finally {
// 清空記錄的資源並將掛起的資源恢復
// 子事務結束了,之前掛起的事務就要恢復了
cleanupAfterCompletion(status);
}
}
我i們先來看看第13行,回滾到保存點的代碼,根據保存點回滾的實現方式其實是根據底層的資料庫連接進行的。回滾到保存點之後,也要釋放掉當前的保存點
public void rollbackToHeldSavepoint() throws TransactionException {
Object savepoint = getSavepoint();
if (savepoint == null) {
throw new TransactionUsageException(
"Cannot roll back to savepoint - no savepoint associated with current transaction");
}
getSavepointManager().rollbackToSavepoint(savepoint);
getSavepointManager().releaseSavepoint(savepoint);
setSavepoint(null);
}
這裡使用的是JDBC的方式進行資料庫連接,那麼getSavepointManager()函數返回的是JdbcTransactionObjectSupport,也就是說上面函數會調用JdbcTransactionObjectSupport 中的 rollbackToSavepoint 方法。
@Override
public void rollbackToSavepoint(Object savepoint) throws TransactionException {
ConnectionHolder conHolder = getConnectionHolderForSavepoint();
try {
conHolder.getConnection().rollback((Savepoint) savepoint);
conHolder.resetRollbackOnly();
}
catch (Throwable ex) {
throw new TransactionSystemException("Could not roll back to JDBC savepoint", ex);
}
}
當之前已經保存的事務信息中的事務為新事物,那麼直接回滾。常用於單獨事務的處理。對於沒有保存點的回滾,Spring同樣是使用底層資料庫連接提供的API來操作的。由於我們使用的是DataSourceTransactionManager,那麼doRollback函數會使用此類中的實現:
@Override
protected void doRollback(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
if (status.isDebug()) {
logger.debug("Rolling back JDBC transaction on Connection [" + con + "]");
}
try {
con.rollback();
}
catch (SQLException ex) {
throw new TransactionSystemException("Could not roll back JDBC transaction", ex);
}
}
當前事務信息中表明是存在事務的,又不屬於以上兩種情況,只做回滾標識,等到提交的時候再判斷是否有回滾標識,下麵回滾的時候再介紹,子事務中狀態為PROPAGATION_SUPPORTS 或 PROPAGATION_REQUIRED或PROPAGATION_MANDATORY回滾的時候將會標記為回滾標識,我們來看看是怎麼標記的
@Override
protected void doSetRollbackOnly(DefaultTransactionStatus status) {
// 將status中的transaction取出
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
if (status.isDebug()) {
logger.debug("Setting JDBC transaction [" + txObject.getConnectionHolder().getConnection() +
"] rollback-only");
}
// transaction執行標記回滾
txObject.setRollbackOnly();
}
public void setRollbackOnly() {
// 這裡將transaction裡面的connHolder標記回滾
getConnectionHolder().setRollbackOnly();
}
public void setRollbackOnly() {
// 將holder中的這個屬性設置成true
this.rollbackOnly = true;
}
我們看到將status中的Transaction中的 ConnectionHolder的屬性rollbackOnly標記為true,這裡我們先不多考慮,等到下麵提交的時候再介紹
我們簡單的做個小結
- status.hasSavepoint()如果status中有savePoint,只回滾到savePoint!
- status.isNewTransaction()如果status是一個新事務,才會真正去回滾!
- status.hasTransaction()如果status有事務,將會對staus中的事務標記!
事務提交
在事務的執行並沒有出現任何的異常,也就意味著事務可以走正常事務提交的流程了。這裡回到流程中去,看看commitTransactionAfterReturning(txInfo)
方法做了什麼:
protected void commitTransactionAfterReturning(@Nullable TransactionInfo txInfo) {
if (txInfo != null && txInfo.getTransactionStatus() != null) {
if (logger.isTraceEnabled()) {
logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "]");
}
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
}
在真正的數據提交之前,還需要做個判斷。不知道大家還有沒有印象,在我們分析事務異常處理規則的時候,當某個事務既沒有保存點又不是新事物,Spring對它的處理方式只是設置一個回滾標識。這個回滾標識在這裡就會派上用場了,如果子事務狀態是
PROPAGATION_SUPPORTS 或 PROPAGATION_REQUIRED或PROPAGATION_MANDATORY,將會在外層事務中運行,回滾的時候,並不執行回滾,只是標記一下回滾狀態,當外層事務提交的時候,會先判斷ConnectionHolder中的回滾狀態,如果已經標記為回滾,則不會提交,而是外層事務進行回滾
@Override
public final void commit(TransactionStatus status) throws TransactionException {
if (status.isCompleted()) {
throw new IllegalTransactionStateException(
"Transaction is already completed - do not call commit or rollback more than once per transaction");
}
DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
// 如果在事務鏈中已經被標記回滾,那麼不會嘗試提交事務,直接回滾
if (defStatus.isLocalRollbackOnly()) {
if (defStatus.isDebug()) {
logger.debug("Transactional code has requested rollback");
}
processRollback(defStatus, false);
return;
}
if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
if (defStatus.isDebug()) {
logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
}
// 這裡會進行回滾,並且拋出一個異常
processRollback(defStatus, true);
return;
}
// 如果沒有被標記回滾之類的,這裡才真正判斷是否提交
processCommit(defStatus);
}
而當事務執行一切都正常的時候,便可以真正地進入提交流程了。
private void processCommit(DefaultTransactionStatus status) throws TransactionException {
try {
boolean beforeCompletionInvoked = false;
try {
boolean unexpectedRollback = false;
prepareForCommit(status);
triggerBeforeCommit(status);
triggerBeforeCompletion(status);
beforeCompletionInvoked = true;
// 判斷是否有savePoint
if (status.hasSavepoint()) {
if (status.isDebug()) {
logger.debug("Releasing transaction savepoint");
}
unexpectedRollback = status.isGlobalRollbackOnly();
// 不提交,僅僅是釋放savePoint
status.releaseHeldSavepoint();
}
// 判斷是否是新事務
else if (status.isNewTransaction()) {
if (status.isDebug()) {
logger.debug("Initiating transaction commit");
}
unexpectedRollback = status.isGlobalRollbackOnly();
// 這裡才真正去提交!
doCommit(status);
}
else if (isFailEarlyOnGlobalRollbackOnly()) {
unexpectedRollback = status.isGlobalRollbackOnly();
}
// Throw UnexpectedRollbackException if we have a global rollback-only
// marker but still didn't get a corresponding exception from commit.
if (unexpectedRollback) {
throw new UnexpectedRollbackException(
"Transaction silently rolled back because it has been marked as rollback-only");
}
}
catch (UnexpectedRollbackException ex) {
// 略...
}
// Trigger afterCommit callbacks, with an exception thrown there
// propagated to callers but the transaction still considered as committed.
try {
triggerAfterCommit(status);
}
finally {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
}
}
finally {
// 清空記錄的資源並將掛起的資源恢復
cleanupAfterCompletion(status);
}
}
- status.hasSavepoint()如果status有savePoint,說明此時的事務是嵌套事務NESTED,這個事務外面還有事務,這裡不提交,只是釋放保存點。這裡也可以看出來NESTED的傳播行為了。
- status.isNewTransaction()如果是新的事務,才會提交!!,這裡如果是子事務,只有PROPAGATION_NESTED狀態才會走到這裡提交,也說明瞭此狀態子事務提交和外層事務是隔離的
- 如果是子事務,PROPAGATION_SUPPORTS 或 PROPAGATION_REQUIRED或PROPAGATION_MANDATORY這幾種狀態是舊事物,提交的時候將什麼都不做,因為他們是運行在外層事務當中,如果子事務沒有回滾,將由外層事務一次性提交
如果程式流通過了事務的層層把關,最後順利地進入了提交流程,那麼同樣,Spring會將事務提交的操作引導至底層資料庫連接的API,進行事務提交。
@Override
protected void doCommit(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
if (status.isDebug()) {
logger.debug("Committing JDBC transaction on Connection [" + con + "]");
}
try {
con.commit();
}
catch (SQLException ex) {
throw new TransactionSystemException("Could not commit JDBC transaction", ex);
}
}
從回滾和提交的邏輯看,只有status是新事務,才會進行提交或回滾,需要讀者記好這個狀態–>是否是新事務。
清理工作
而無論是在異常還是沒有異常的流程中,最後的finally塊中都會執行一個方法cleanupAfterCompletion(status)
private void cleanupAfterCompletion(DefaultTransactionStatus status) {
// 設置完成狀態
status.setCompleted();
if (status.isNewSynchronization()) {
TransactionSynchronizationManager.clear();
}
if (status.isNewTransaction()) {
doCleanupAfterCompletion(status.getTransaction());
}
if (status.getSuspendedResources() != null) {
if (status.isDebug()) {
logger.debug("Resuming suspended transaction after completion of inner transaction");
}
Object transaction = (status.hasTransaction() ? status.getTransaction() : null);
// 結束之前事務的掛起狀態
resume(transaction, (SuspendedResourcesHolder) status.getSuspendedResources());
}
}
如果是新事務需要做些清除資源的工作?
@Override
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 {
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();
}
如果在事務執行前有事務掛起,那麼當前事務執行結束後需要將掛起事務恢復。
如果有掛起的事務的話,status.getSuspendedResources() != null
,也就是說status中會有suspendedResources這個屬性,取得status中的transaction後進入resume方法:
protected final void resume(@Nullable Object transaction, @Nullable SuspendedResourcesHolder resourcesHolder)
throws TransactionException {
if (resourcesHolder != null) {
Object suspendedResources = resourcesHolder.suspendedResources;
// 如果有被掛起的事務才進入
if (suspendedResources != null) {
// 真正去resume恢復的地方
doResume(transaction, suspendedResources);
}
List<TransactionSynchronization> suspendedSynchronizations = resourcesHolder.suspendedSynchronizations;
if (suspendedSynchronizations != null) {
// 將上面提到的TransactionSynchronizationManager專門存放線程變數的類中
// 的屬性設置成被掛起事務的屬性
TransactionSynchronizationManager.setActualTransactionActive(resourcesHolder.wasActive);
TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(resourcesHolder.isolationLevel);
TransactionSynchronizationManager.setCurrentTransactionReadOnly(resourcesHolder.readOnly);
TransactionSynchronizationManager.setCurrentTransactionName(resourcesHolder.name);
doResumeSynchronization(suspendedSynchronizations);
}
}
}
我們來看看doResume
@Override
protected void doResume(@Nullable Object transaction, Object suspendedResources) {
TransactionSynchronizationManager.bindResource(obtainDataSource(), suspendedResources);
}
這裡恢復只是把suspendedResources
重新綁定到線程中。
幾種事務傳播屬性詳解
我們先來看看七種傳播屬性
Spring事物傳播特性表:
傳播特性名稱 | 說明 |
---|---|
PROPAGATION_REQUIRED | 如果當前沒有事物,則新建一個事物;如果已經存在一個事物,則加入到這個事物中 |
PROPAGATION_SUPPORTS | 支持當前事物,如果當前沒有事物,則以非事物方式執行 |
PROPAGATION_MANDATORY | 使用當前事物,如果當前沒有事物,則拋出異常 |
PROPAGATION_REQUIRES_NEW | 新建事物,如果當前已經存在事物,則掛起當前事物 |
PROPAGATION_NOT_SUPPORTED | 以非事物方式執行,如果當前存在事物,則掛起當前事物 |
PROPAGATION_NEVER | 以非事物方式執行,如果當前存在事物,則拋出異常 |
PROPAGATION_NESTED | 如果當前存在事物,則在嵌套事物內執行;如果當前沒有事物,則與PROPAGATION_REQUIRED傳播特性相同 |
當前不存在事務的情況下
每次創建一個TransactionInfo的時候都會去new一個transaction,然後去線程變數Map中拿holder,當此時線程變數的Map中holder為空時,就視為當前情況下不存在事務,所以此時transaction中holder = null。
1、PROPAGATION_MANDATORY
使用當前事物,如果當前沒有事物,則拋出異常
在上一篇博文中我們在getTransaction方法中可以看到如下代碼,當前線程不存在事務時,如果傳播屬性為PROPAGATION_MANDATORY,直接拋出異常,因為PROPAGATION_MANDATORY必須要在事務中運行
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
throw new IllegalTransactionStateException(
"No existing transaction found for transaction marked with propagation 'mandatory'");
}
2、REQUIRED、REQUIRES_NEW、NESTED
我們繼續看上一篇博文中的getTransaction方法
else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
// PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW、PROPAGATION_NESTED都需要新建事務
// 因為此時不存在事務,將null掛起
SuspendedResourcesHolder suspendedResources = suspend(null);
if (debugEnabled) {
logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition);
}
try {
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
// new一個status,存放剛剛創建的transaction,然後將其標記為新事務!
// 這裡transaction後面一個參數決定是否是新事務!
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
// 新開一個連接的地方,非常重要
doBegin(transaction, definition);
prepareSynchronization(status, definition);
return status;
}
catch (RuntimeException | Error ex) {
resume(null, suspendedResources);
throw ex;
}
}
此時會講null掛起,此時的status變數為:
DefaultTransactionStatus status = newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
此時的transaction中holder依然為null,標記為新事務,接著就會執行doBegin方法了:
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
Connection con = null;
// 此時會進入這個if語句塊,因為此時的holder依然為null
if (!txObject.hasConnectionHolder() ||
txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
// 從dataSource從取得一個新的connection
Connection newCon = obtainDataSource().getConnection();
if (logger.isDebugEnabled()) {
logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
}
// new一個新的holder放入新的連接,設置為新的holder
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}
// 略...
prepareTransactionalConnection(con, definition);
// 將holder設置avtive = true
txObject.getConnectionHolder().setTransactionActive(true);
// Bind the connection holder to the thread.
// 綁定到當前線程
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
}
}
}
所以,一切都是新的,新的事務,新的holder,新的連接,在當前不存在事務的時候一切都是新創建的。
這三種傳播特性在當前不存在事務的情況下是沒有區別的,此事務都為新創建的連接,在回滾和提交的時候都可以正常回滾或是提交,就像正常的事務操作那樣。
3、PROPAGATION_SUPPORTS、PROPAGATION_NOT_SUPPORTED、PROPAGATION_NEVER
我們看看當傳播屬性為PROPAGATION_SUPPORTS、PROPAGATION_NOT_SUPPORTED、PROPAGATION_NEVER這幾種時的代碼,getTransaction方法
else {
//其他的傳播特性一律返回一個空事務,transaction = null
//當前不存在事務,且傳播機制=PROPAGATION_SUPPORTS/PROPAGATION_NOT_SUPPORTED/PROPAGATION_NEVER,這三種情況,創建“空”事務
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);
}
我們看到Status中第二個參數傳的是null,表示一個空事務,意思是當前線程中並沒有Connection,那如何進行資料庫的操作呢?上一篇文章中我們有一個擴充的知識點,Mybaits中使用的資料庫連接是從通過TransactionSynchronizationManager.getResource(Object key)獲取spring增強方法中綁定到線程的connection,
如下代碼,那當傳播屬性為PROPAGATION_SUPPORTS、PROPAGATION_NOT_SUPPORTED、PROPAGATION_NEVER這幾種時,並沒有創建新的Connection,當前線程中也沒有綁定Connection,那Mybatis是如何獲取Connecion的呢?這裡留一個疑問,我們後期看Mybatis的源碼的時候來解決這個疑問
@Nullable
public static Object getResource(Object key) {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
Object value = doGetResource(actualKey);
if (value != null && logger.isTraceEnabled()) {
logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" +
Thread.currentThread().getName() + "]");
}
return value;
}
@Nullable
private static Object doGetResource(Object actualKey) {
Map<Object, Object> map = resources.get();
if (map == null) {
return null;
}
Object value = map.get(actualKey);
// Transparently remove ResourceHolder that was marked as void...
if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
map.remove(actualKey);
// Remove entire ThreadLocal if empty...
if (map.isEmpty()) {
resources.remove();
}
value = null;
}
return value;
}
此時我們知道Status中的Transaction為null,在目標方法執行完畢後,進行回滾或提交的時候,會判斷當前事務是否是新事務,代碼如下
@Override
public boolean isNewTransaction() {
return (hasTransaction() && this.newTransaction);
}
此時transacion為null,回滾或提交的時候將什麼也不做
當前存在事務情況下
上一篇文章中已經講過,第一次事務開始時必會新創一個holder然後做綁定操作,此時線程變數是有holder的且avtive為true,如果第二個事務進來,去new一個transaction之後去線程變數中取holder,holder是不為空的且active是為true的,所以會進入handleExistingTransaction方法:
private TransactionStatus handleExistingTransaction(
TransactionDefinition definition, Object transaction, boolean debugEnabled)
throws TransactionException {
// 1.NERVER(不支持當前事務;如果當前事務存在,拋出異常)報錯
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
throw new IllegalTransactionStateException(
"Existing transaction found for transaction marked with propagation 'never'");
}
// 2.NOT_SUPPORTED(不支持當前事務,現有同步將被掛起)掛起當前事務,返回一個空事務
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
if (debugEnabled) {
logger.debug("Suspending current transaction");
}
// 這裡會將原來的事務掛起,並返回被掛起的對象
Object suspendedResources = suspend(transaction);
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
// 這裡可以看到,第二個參數transaction傳了一個空事務,第三個參數false為舊標記
// 最後一個參數就是將前面掛起的對象封裝進新的Status中,當前事務執行完後,就恢復suspendedResources
return prepareTransactionStatus(definition, null, false, newSynchronization, debugEnabled, suspendedResources);
}
// 3.REQUIRES_NEW掛起當前事務,創建新事務
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
if (debugEnabled) {
logger.debug("Suspending current transaction, creating new transaction with name [" +
definition.getName() + "]");
}
// 將原事務掛起,此時新建事務,不與原事務有關係
// 會將transaction中的holder設置為null,然後解綁!
SuspendedResourcesHolder suspendedResources = suspend(transaction);
try {
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
// new一個status出來,傳入transaction,並且為新事務標記,然後傳入掛起事務
DefaultTransactionStatus status = newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
// 這裡也做了一次doBegin,此時的transaction中holer是為空的,因為之前的事務被掛起了
// 所以這裡會取一次新的連接,並且綁定!
doBegin(transaction, definition);
prepareSynchronization(status, definition);
return status;
}
catch (RuntimeException beginEx) {
resumeAfterBeginException(transaction, suspendedResources, beginEx);
throw beginEx;
}
catch (Error beginErr) {
resumeAfterBeginException(transaction, suspendedResources, beginErr);
throw beginErr;
}
}
// 如果此時的傳播特性是NESTED,不會掛起事務
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
if (!isNestedTransactionAllowed()) {
throw new NestedTransactionNotSupportedException(
"Transaction manager does not allow nested transactions by default - " +
"specify 'nestedTransactionAllowed' property with value 'true'");
}
if (debugEnabled) {
logger.debug("Creating nested transaction with name [" + definition.getName() + "]");
}
// 這裡如果是JTA事務管理器,就不可以用savePoint了,將不會進入此方法
if (useSavepointForNestedTransaction()) {
// 這裡不會掛起事務,說明NESTED的特性是原事務的子事務而已
// new一個status,傳入transaction,傳入舊事務標記,傳入掛起對象=null
DefaultTransactionStatus status =prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);
// 這裡是NESTED特性特殊的地方,在先前存在事務的情況下會建立一個savePoint
status.createAndHoldSavepoint();
return status;
}
else {
// JTA事務走這個分支,創建新事務
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, null);
doBegin(transaction, definition);
prepareSynchronization(status, definition);
return status;
}
}
// 到這裡PROPAGATION_SUPPORTS 或 PROPAGATION_REQUIRED或PROPAGATION_MANDATORY,存在事務加入事務即可,標記為舊事務,空掛起
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
}
1、NERVER
不支持當前事務;如果當前事務存在,拋出異常
// 1.NERVER(不支持當前事務;如果當前事務存在,拋出異常)報錯
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
throw new IllegalTransactionStateException(
"Existing transaction found for transaction marked with propagation 'never'");
}
我們看到如果當前線程中存在事務,傳播屬性為PROPAGATION_NEVER,會直接拋出異常
2、NOT_SUPPORTED
以非事物方式執行,如果當前存在事物,則掛起當前事物
我們看上面代碼第9行,如果傳播屬性為PROPAGATION_NOT_SUPPORTED,會先將原來的transaction掛起,此時status為:
return prepareTransactionStatus(definition, null, false, newSynchronization, debugEnabled, suspendedResources);
transaction為空,舊事務,掛起的對象存入status中。
此時與外層事務隔離了,在這種傳播特性下,是不進行事務的,當提交時,因為是舊事務,所以不會commit,失敗時也不會回滾rollback
3、REQUIRES_NEW
此時會先掛起,然後去執行doBegin方法,此時會創建一個新連接,新holder,新holder有什麼用呢?
如果是新holder,會在doBegin中做綁定操作,將新holder綁定到當前線程,其次,在提交或是回滾時finally語句塊始終會執行清理方法時判斷新holder會進行解綁操作。
@Override
protected void doCleanupAfterCompletion(Object transaction) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
// Remove the connection holder from the thread, if exposed.
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.unbindResource(obtainDataSource());
}
}
符合傳播特性,所以這裡REQUIRES_NEW這個傳播特性是與原事務相隔的,用的連接都是新new出來的。
此時返回的status是這樣的:
DefaultTransactionStatus status = newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
其中transaction中holder為新holder,連接都是新的。標記為新事務,在開頭的回顧中提到,如果是新事務,提交時才能成功提交。並且在最後一個參數放入掛起的對象,之後將會恢復它。
REQUIRES_NEW小結
會於前一個事務隔離,自己新開一個事務,與上一個事務無關,如果報錯,上一個事務catch住異常,上一個事務是不會回滾的,這裡要註意(在invokeWithinTransaction方法中的catch代碼塊中,處理完異常後,還通過 throw ex;將異常拋給了上層,所以上層要catch住子事務的異常,子事務回滾後,上層事務也會回滾),而只要自己提交了之後,就算上一個事務後面的邏輯報錯,自己是不會回滾的(因為被標記為新事務,所以在提交階段已經提交了)。
4、NESTED
不掛起事務,並且返回的status對象如下:
DefaultTransactionStatus status =prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);
status.createAndHoldSavepoint();
不同於其他的就是此傳播特性會創建savePoint,有什麼用呢?前面說到,如果是舊事務的話回滾是不會執行的,但先看看它的status,雖然標記為舊事務,但它還有savePoint,如果有savePoint,會回滾到保存點去,提交的時候,會釋放保存點,但是不提交!切記,這裡就是NESTED與REQUIRES_NEW不同點之一了,NESTED只會在外層事務成功時才進行提交,實際提交點只是去釋放保存點,外層事務失敗,NESTED也將回滾,但如果是REQUIRES_NEW的話,不管外層事務是否成功,它都會提交不回滾。這就是savePoint的作用。
由於不掛起事務,可以看出來,此時transaction中的holder用的還是舊的,連接也是上一個事務的連接,可以看出來,這個傳播特性會將原事務和自己當成一個事務來做。
NESTED 小結
與前一個事務不隔離,沒有新開事務,用的也是老transaction,老的holder,同樣也是老的connection,沒有掛起的事務。關鍵點在這個傳播特性在存在事務情況下會創建savePoint,但不存在事務情況下是不會創建savePoint的。在提交時不真正提交,只是釋放了保存點而已,在回滾時會回滾到保存點位置,如果上層事務catch住異常的話,是不會影響上層事務的提交的,外層事務提交時,會統一提交,外層事務回滾的話,會全部回滾
5、REQUIRED 、PROPAGATION_REQUIRED或PROPAGATION_MANDATORY
存在事務加入事務即可,標記為舊事務,空掛起
status為:
return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
使用舊事務,標記為舊事務,掛起對象為空。
與前一個事務不隔離,沒有新開事務,用的也是老transaction,老的connection,但此時被標記成舊事務,所以,在提交階段不會真正提交的,在外層事務提交階段,才會把事務提交。
如果此時這裡出現了異常,內層事務執行回滾時,舊事務是不會去回滾的,而是進行回滾標記,我們看看文章開頭處回滾的處理函數processRollback中第39行,當前事務信息中表明是存在事務的,但是既沒有保存點,又不是新事務,回滾的時候只做回滾標識,等到提交的時候再判斷是否有回滾標識,commit的時候,如果有回滾標識,就進行回滾
@Override
protected void doSetRollbackOnly(DefaultTransactionStatus status) {
// 將status中的transaction取出
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
if (status.isDebug()) {
logger.debug("Setting JDBC transaction [" + txObject.getConnectionHolder().getConnection() +
"] rollback-only");
}
// transaction執行標記回滾
txObject.setRollbackOnly();
}
public void setRollbackOnly() {
// 這裡將transaction裡面的connHolder標記回滾
getConnectionHolder().setRollbackOnly();
}
public void setRollbackOnly() {
// 將holder中的這個屬性設置成true
this.rollbackOnly = true;
}
我們知道,在內層事務中transaction對象中的holder對象其實就是外層事務transaction里的holder,holder是一個對象,指向同一個地址,在這裡設置holder標記,外層事務transaction中的holder也是會被設置到的,在外層事務提交的時候有這樣一段代碼:
@Override
public final void commit(TransactionStatus status) throws TransactionException {
// 略...
// !shouldCommitOnGlobalRollbackOnly()只有JTA與JPA事務管理器才會返回false
// defStatus.isGlobalRollbackOnly()這裡判斷status是否被標記了
if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
if (defStatus.isDebug()) {
logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
}
// 如果內層事務拋異常,外層事務是會走到這個方法中的,而不是去提交
processRollback(defStatus, true);
return;
}
// 略...
}
在外層事務提交的時候是會去驗證transaction中的holder里是否被標記rollback了,內層事務回滾,將會標記holder,而holder是線程變數,在此傳播特性中holder是同一個對象,外層事務將無法正常提交而進入processRollback方法進行回滾,並拋出異常:
private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
try {
// 此時這個值為true
boolean unexpectedRollback = unexpected;
try {
triggerBeforeCompletion(status);
if (status.hasSavepoint()) {
if (status.isDebug()) {
logger.debug("Rolling back transaction to savepoint");
}
status.rollbackToHeldSavepoint();
}
// 新事務,將進行回滾操作
else if (status.isNewTransaction()) {
if (status.isDebug()) {
logger.debug("Initiating transaction rollback");
}
// 回滾!
doRollback(status);
}
// 略...
// Raise UnexpectedRollbackException if we had a global rollback-only marker
// 拋出一個異常
if (unexpectedRollback) {
// 這個就是上文說到的拋出的異常類型
throw new UnexpectedRollbackException(
"Transaction rolled back because it has been marked as rollback-only");
}
}
finally {
cleanupAfterCompletion(status);
}
}
最後給大家分享200多本電腦經典書籍PDF電子書,包括C語言、C++、Java、Python、前端、資料庫、操作系統、電腦網路、數據結構和演算法、機器學習、編程人生等,感興趣的小伙伴可以自取: