1. 程式人生 > 實用技巧 >記一次實際開發過程中遇到事務報錯問題 Transaction synchronization is not active

記一次實際開發過程中遇到事務報錯問題 Transaction synchronization is not active

一:問題場景

在一次http請求的後臺介面中返回結果中出現了這個錯誤資訊“Transaction synchronization is not active”,意思是“事務同步器沒有啟用”,但是被呼叫的介面已經添加了@Transactional註解,所以百思不得不得其解為什麼還會報這個錯誤,那麼是什麼原因引起了這個異常呢?自己通過百度大神部落格終於找到一篇相似的文章解決了自己的問題,今天就是把解決方案記錄下來為以後再出現類似情況提供解決方案。

二:出現原因

Google搜尋一番之後,發現了這篇文章Spring的TransactionEventListener,文中提到了可能出現這個錯誤資訊的一種情況:

@EventListener
public void afterRegisterSendMail(MessageEvent event) {
// Spring 4.2 之前
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
internalSendMailNotification(event);

}
});
}
上面的程式碼將在事務提交後執行.如果在非事務context中(就是方法未開啟spring事務)將丟擲java.lang.IllegalStateException: Transaction synchronization is not active

於是找到自己程式碼中的TransactionSynchronizationManager部分,果然在當前http請求鏈路中的一個介面裡發現了類似的程式碼:

TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {

@Override
public void afterCommit() {
try {
byte[] bytes = JSON.toJSONBytes(task, new SerializeConfig());
MessageProperties messageProperties = new MessageProperties();
messageProperties.setMessageId(idWorker.nextStringId());
Message message = new Message(bytes, messageProperties);
salaryFileMsgProducer.sendSalaryFileMsg(message);
} catch (Exception e) {
log.error(e.getMessage());
e.printStackTrace();
}
}
});

想必是執行這段程式碼的時候報的異常,但是上面那篇文章說了引起這個異常的原因是“在非事務context中註冊同步器”,難道當前事務沒有開啟?我們的業務程式碼一般都是使用spring的註解@Transactional來開啟事務,那麼去看一下開啟事務的程式碼片段。

三:根本原因
我在呼叫TransactionSynchronizationManager.registerSynchronization()的方法體上找到了@Transactional註解,方法程式碼如下,省略具體實現:

@Transactional(rollbackFor = Exception.class)
public Boolean creatSalaryMonthStatistic(SalaryMonthlyStatistic salaryMonthlyStatistic){
....
}
這就奇怪了,明明方法上已經新增@Transactional註解開啟事務,為什麼事務開啟失敗,帶著這個疑問再次求助了Google,發現了這篇部落格spring的service類呼叫自己方法事務無效,作者也遇到了事務無效的問題,其中總結了事務有效性相關的幾點重要資訊:

1.在需要事務管理的地方加@Transactional 註解。@Transactional 註解可以被應用於介面定義和介面方法、類定義和類的 public 方法上。

[email protected] 註解只能應用到 public 可見度的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 註解,它也不會報錯, 但是這個被註解的方法將不會展示已配置的事務設定。

3.注意僅僅 @Transactional 註解的出現不足於開啟事務行為,它僅僅 是一種元資料。必須在配置檔案中使用配置元素,才真正開啟了事務行為。(spring配置檔案中,開啟宣告式事務)

4.通過 元素的 “proxy-target-class” 屬性值來控制是基於介面的還是基於類的代理被建立。如果 “proxy-target-class” 屬值被設定為 “true”,那麼基於類的代理將起作用(這時需要CGLIB庫cglib.jar在CLASSPATH中)。如果 “proxy-target-class” 屬值被設定為 “false” 或者這個屬性被省略,那麼標準的JDK基於介面的代理將起作用。

5.Spring團隊建議在具體的類(或類的方法)上使用 @Transactional 註解,而不要使用在類所要實現的任何介面上。在介面上使用 @Transactional 註解,只能當你設定了基於介面的代理時它才生效。因為註解是 不能繼承 的,這就意味著如果正在使用基於類的代理時,那麼事務的設定將不能被基於類的代理所識別,而且物件也將不會被事務代理所包裝。

[email protected]的事務開啟 ,或者是基於介面的 或者是基於類的代理被建立。所以在同一個類中一個無事務的方法呼叫另一個有事務的方法,事務是不會起作用的。

特別注意第6點:同一個類中一個無事務的方法呼叫另一個有事務的方法,事務是不會起作用的。這一點引起了我的注意,可能我的加了註解@Transactional的creatSalaryMonthStatistic方法也是被類裡另一個沒有開啟事務的方法呼叫,如果真的是這樣,所有一切都解釋的通了。所以帶著這個思路檢查了一遍自己的程式碼,

果然,creatSalaryMonthStatistic方法被一個沒加事務的介面方法呼叫,從而整個creatSalaryMonthStatistic方法都沒有事務效果:

//此介面方法未加事務
public Boolean batchInitSalaryFileDataTask4Tenant(String tenantId){
.....
salaryFileInitTaskBiz.createSalaryFileDateInitTask4Staff(file);
.....
}
四:解決方案
知道了引起問題的原因,解決方法也比較簡單,有好幾種方法都可以解決這個問題,可以根據個人需要選擇一種解決方法,本文列出其中三種:
1.比較方便、暴力的一種方法就是直接在最外層介面方法中新增事務註解,也就是給本文中的batchInitSalaryFileDataTask4Tenant方法加上註解即可。
2.也可以通過代理類呼叫creatSalaryMonthStatistic方法,代理類中包含了事務邏輯,這樣也能實現事務功能。
3.還可以直接將creatSalaryMonthStatistic這個方法放到另外一個類中。