美團二面:spring事務不生效的15種場景
來源:撿田螺的小男孩
前言
大家好,我是田螺。
日常開發中,我們經常使用到spring
事務。最近星球一位還有去美團面試,被問了這麼一道面試題: Spring 事務在哪幾種情況下會不生效? 今天田螺哥跟大家聊聊,spring
事務不生效的15
種場景。
1. 你的service類沒有被Spring管理
//@Service (註釋了@Service)
public class TianLuoServiceImpl implements TianLuoService {
@Autowired
private TianLuoMapper tianLuoMapper;
@Autowired
private TianLuoFlowMapper tianLuoFlowMapper;
@Transactional
public void addTianLuo(TianLuo tianluo) {
//儲存tianluo實體資料庫記錄
tianLuoMapper.save(tianluo);
//儲存tianluo流水資料庫記錄
tianLuoFlowMapper.saveFlow(buildFlowByTianLuo(tianluo));
}
}
事務不生效的原因:上面例子中, @Service
註解註釋之後,spring
事務(@Transactional
)沒有生效,因為Spring
事務是由AOP
機制實現的,也就是說從Spring IOC
容器獲取bean
時,Spring
會為目標類建立代理,來支援事務的。但是@Service
被註釋後,你的service
類都不是spring
管理的,那怎麼建立代理類來支援事務呢。解決方案:加上 @Service
註解。
2.沒有在Spring配置檔案中啟用事務管理器
@Configuration
public class AppConfig {
// 沒有配置事務管理器
}
@Service
public class MyService {
@Transactional
public void doSomething() {
// ...
}
}
事務不生效的原因:沒有在 AppConfig
中配置事務管理器,因此Spring
無法建立事務代理物件,導致事務不生效。即使在MyService
中新增了@Transactional
註解,該方法也不會被Spring
管理的事務代理攔截。解決方案:為了解決這個問題,應該在 AppConfig
中配置一個事務管器。例如:
@Configuration
public class AppConfig {
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
}
@Service
public class MyService {
@Transactional
public void doSomething() {
// ...
}
}
如果是
Spring Boot
專案,它預設會自動配置事務管理器並開啟事務支援。
3. 事務方法被final、static關鍵字修飾
@Service
public class TianLuoServiceImpl {
@Autowired
private TianLuoMapper tianLuoMapper;
@Autowired
private TianLuoFlowMapper tianLuoFlowMapper;
@Transactional
public final void addTianLuo(TianLuo tianluo) {
//儲存tianluo實體資料庫記錄
tianLuoMapper.save(tianluo);
//儲存tianluo流水資料庫記錄
tianLuoFlowMapper.saveFlow(buildFlowByTianLuo(tianluo));
}
}
事務不生效的原因:如果一個方法被宣告為 final
或者static
,則該方法不能被子類重寫,也就是說無法在該方法上進行動態代理,這會導致Spring
無法生成事務代理物件來管理事務。解決方案: addTianLuo
事務方法不要用final
修飾或者static
修飾。
4. 同一個類中,方法內部呼叫
@Service
public class TianLuoServiceImpl implements TianLuoService {
@Autowired
private TianLuoMapper tianLuoMapper;
@Autowired
private TianLuoFlowMapper tianLuoFlowMapper;
public void addTianLuo(TianLuo tianluo){
// 呼叫內部的事務方法
this.executeAddTianLuo(tianluo);
}
@Transactional
public void executeAddTianLuo(TianLuo tianluo) {
tianLuoMapper.save(tianluo);
tianLuoFlowMapper.saveFlow(buildFlowByTianLuo(tianluo));
}
}
事務不生效的原因: 事務是透過 Spring AOP
代理來實現的,而在同一個類中,一個方法呼叫另一個方法時,呼叫方法直接呼叫目標方法的程式碼,而不是透過代理類進行呼叫。即以上程式碼,呼叫目標executeAddTianLuo
方法不是透過代理類進行的,因此事務不生效。解決方案:可以新建多一個類,讓這兩個方法分開,分別在不同的類中。如下:
@Service
public class TianLuoExecuteServiceImpl implements TianLuoExecuteService {
@Autowired
private TianLuoMapper tianLuoMapper;
@Autowired
private TianLuoFlowMapper tianLuoFlowMapper;
@Transactional
public void executeAddTianLuo(TianLuo tianluo) {
tianLuoMapper.save(tianluo);
tianLuoFlowMapper.saveFlow(buildFlowByTianLuo(tianluo));
}
}
@Service
public class TianLuoAddServiceImpl implements TianLuoAddService {
@Autowired
private TianLuoExecuteService tianLuoExecuteService;
public void addTianLuo(User user){
tianLuoExecuteService.executeAddTianLuo(user);
}
}
當然,有時候你也可以在該 Service
類中注入自己,或者透過AopContext.currentProxy()
獲取代理物件。
5.方法的訪問許可權不是public
@Service
public class TianLuoServiceImpl implements TianLuoService {
@Autowired
private TianLuoMapper tianLuoMapper;
@Autowired
private TianLuoFlowMapper tianLuoFlowMapper;
@Transactional
private void addTianLuo(TianLuo tianluo) {
tianLuoMapper.save(tianluo);
tianLuoFlowMapper.saveFlow(buildFlowByTianLuo(tianluo));
}
}
事務不生效的原因: spring
事務方法addTianLuo
的訪問許可權不是public
,所以事務就不生效啦,因為Spring
事務是由AOP
機制實現的,AOP
機制的本質就是動態代理,而代理的事務方法不是public
的話,computeTransactionAttribute()
就會返回null,也就是這時事務屬性不存在了。大家可以看下AbstractFallbackTransactionAttributeSource
的原始碼:
解決方案: addTianLuo
事務方法的訪問許可權修改為public
。
6. 資料庫的儲存引擎不支援事務
Spring事務的底層,還是依賴於資料庫本身的事務支援。在MySQL
中,MyISAM
儲存引擎是不支援事務的,InnoDB
引擎才支援事務。因此開發階段設計表的時候,確認你的選擇的儲存引擎是支援事務的。
7 .配置錯誤的 @Transactional 註解
@Transactional(readOnly = true)
public void updateUser(User user) {
userDao.updateUser(user);
}
事務不生效的原因:雖然使用了 @Transactional
註解,但是註解中的readOnly=true
屬性指示這是一個只讀事務,因此在更新User
實體時會丟擲異常。解決方案:將 readOnly
屬性設定為false
,或者移除了@Transactional
註解中的readOnly
屬性。
8.事務超時時間設定過短
@Transactional(timeout = 1)
public void doSomething() {
//...
}
事務不生效的原因:在上面的例子中, timeout
屬性被設定為1
秒,這意味著如果事務在1
秒內無法完成,則報事務超時了。
9. 使用了錯誤的事務傳播機制
@Service
public class TianLuoServiceImpl {
@Autowired
private TianLuoMapper tianLuoMapper;
@Autowired
private TianLuoFlowMapper tianLuoFlowMapper;
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void doInsertTianluo(TianLuo tianluo) throws Exception {
tianLuoMapper.save(tianluo);
tianLuoFlowMapper.saveFlow(buildFlowByTianLuo(tianluo));
}
}
事務不生效的原因: Propagation.NOT_SUPPORTED
傳播特性不支援事務。解決方案:選擇正確的事務傳播機制。
幫大家複習一下,Spring
提供了七種事務傳播機制。它們分別是:
REQUIRED
(預設):如果當前存在一個事務,則加入該事務;否則,建立一個新事務。該傳播級別表示方法必須在事務中執行。SUPPORTS
:如果當前存在一個事務,則加入該事務;否則,以非事務的方式繼續執行。MANDATORY
:如果當前存在一個事務,則加入該事務;否則,丟擲異常。REQUIRES_NEW
:建立一個新的事務,並且如果存在一個事務,則將該事務掛起。NOT_SUPPORTED
:以非事務方式執行操作,如果當前存在一個事務,則將該事務掛起。NEVER
:以非事務方式執行操作,如果當前存在一個事務,則丟擲異常。NESTED
:如果當前存在一個事務,則在巢狀事務內執行。如果沒有事務,則按REQUIRED
傳播級別執行。巢狀事務是外部事務的一部分,可以在外部事務提交或回滾時部分提交或回滾。
10. rollbackFor屬性配置錯誤
@Service
public class TianLuoServiceImpl implements TianLuoService {
@Autowired
private TianLuoMapper tianLuoMapper;
@Autowired
private TianLuoFlowMapper tianLuoFlowMapper;
@Transactional(rollbackFor = Error.class)
public void addTianLuo(TianLuo tianluo) {
//儲存tianluo資料庫記錄
tianLuoMapper.save(tianluo);
//儲存tianluo流水資料庫記錄
tianLuoFlowMapper.saveFlow(tianluo);
//模擬異常丟擲
throw new Exception();
}
}
事務不生效的原因: 其實 rollbackFor
屬性指定的異常必須是Throwable
或者其子類。預設情況下,RuntimeException
和Error
兩種異常都是會自動回滾的。但是因為以上的程式碼例子,指定了rollbackFor = Error.class
,但是丟擲的異常又是Exception
,而Exception和Error
沒有任何什麼繼承關係,因此事務就不生效。
大家可以看一下Transactional
註解原始碼哈:
解決方案: rollbackFor
屬性指定的異常與丟擲的異常匹配。
11.事務註解被覆蓋導致事務失效
public interface MyRepository {
@Transactional
void save(String data);
}
public class MyRepositoryImpl implements MyRepository {
@Override
public void save(String data) {
// 資料庫操作
}
}
public class MyService {
@Autowired
private MyRepository myRepository;
@Transactional
public void doSomething(String data) {
myRepository.save(data);
}
}
public class MyTianluoService extends MyService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void doSomething(String data) {
super.doSomething(data);
}
}
事務失效的原因: MyTianluoService
是MyService
的子類,並且覆蓋了doSomething()
方法。在該方法中,使用了不同的傳播行為(REQUIRES_NEW)
來覆蓋父類的@Transactional
註解。在這種情況下,當呼叫MyTianluoService
的doSomething()
方法時,由於子類方法中的註解覆蓋了父類的註解,Spring
框架將不會在父類的方法中啟動事務。因此,當MyRepository
的save()
方法被呼叫時,事務將不會被啟動,也不會回滾。這將導致資料不一致的問題,因為在MyRepository
的save()
方法中進行的資料庫操作將不會回滾。
12.巢狀事務的坑
@Service
public class TianLuoServiceInOutService {
@Autowired
private TianLuoFlowService tianLuoFlowService;
@Autowired
private TianLuoMapper tianLuoMapper;
@Transactional
public void addTianLuo(TianLuo tianluo) throws Exception {
tianLuoMapper.save(tianluo);
tianLuoFlowService.saveFlow(tianluo);
}
}
@Service
public class TianLuoFlowService {
@Autowired
private TianLuoFlowMapper tianLuoFlowMapper;
@Transactional(propagation = Propagation.NESTED)
public void saveFlow(TianLuo tianLuo) {
tianLuoFlowMapper.save(tianLuo);
throw new RuntimeException();
}
}
以上程式碼使用了巢狀事務,如果
saveFlow
出現執行時異常,會繼續往上拋,到外層addTianLuo
的方法,導致tianLuoMapper.save
也會回滾啦。如果不想因為被內部巢狀的事務影響,可以用try-catch
包住,如下:
@Transactional
public void addTianLuo(TianLuo tianluo) throws Exception {
tianLuoMapper.save(tianluo);
try {
tianLuoFlowService.saveFlow(tianluo);
} catch (Exception e) {
log.error("save tian luo flow fail,message:{}",e.getMessage());
}
}
13. 事務多執行緒呼叫
@Service
public class TianLuoService {
@Autowired
private TianLuoMapper tianLuoMapper;
@Autowired
private TianLuoFlowService tianLuoFlowService;
@Transactional
public void addTianLuo(TianLuo tianluo) {
//儲存tianluo資料庫記錄
tianLuoMapper.save(tianluo);
//多執行緒呼叫
new Thread(() -> {
tianLuoFlowService.saveFlow(tianluo);
}).start();
}
}
@Service
public class TianLuoFlowService {
@Autowired
private TianLuoFlowMapper tianLuoFlowMapper;
@Transactional
public void save(TianLuo tianLuo) {
tianLuoFlowMapper.saveFlow(tianLuo);
}
}
事務不生效原因:這是因為 Spring
事務是基於執行緒繫結的,每個執行緒都有自己的事務上下文,而多執行緒環境下可能會存在多個執行緒共享同一個事務上下文的情況,導致事務不生效。Spring
事務管理器透過使用執行緒本地變數(ThreadLocal
)來實現執行緒安全。大家有興趣的話,可以去看下原始碼哈.
在Spring事務管理器中,透過
TransactionSynchronizationManager
類來管理事務上下文。TransactionSynchronizationManager
內部維護了一個ThreadLocal
物件,用來儲存當前執行緒的事務上下文。在事務開始時,TransactionSynchronizationManager
會將事務上下文繫結到當前執行緒的ThreadLocal
物件中,當事務結束時,TransactionSynchronizationManager
會將事務上下文從ThreadLocal
物件中移除。
14.異常被捕獲並處理了,沒有重新丟擲
@Service
public class TianLuoServiceImpl implements TianLuoService {
@Autowired
private TianLuoMapper tianLuoMapper;
@Autowired
private TianLuoFlowMapper tianLuoFlowMapper;
@Transactional
public void addTianLuo(TianLuo tianluo) {
try {
//儲存tianluo資料庫記錄
tianLuoMapper.save(tianluo);
//儲存tianluo flow資料庫記錄
tianLuoFlowMapper.saveFlow(tianluo);
} catch (Exception e) {
log.error("add TianLuo error,id:{},message:{}", tianluo.getId(),e.getMessage());
}
}
}
事務不生效的原因: 事務中的異常已經被業務程式碼捕獲並處理,而沒有被正確地傳播回事務管理器,事務將無法回滾。我們可以從 spring
原始碼(TransactionAspectSupport
這個類)中找到答案:
public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {
//這方法會省略部分程式碼,只留關鍵程式碼哈
@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class
在
invokeWithinTransaction
方法中,當Spring
catch到Throwable
異常的時候,就會呼叫completeTransactionAfterThrowing()
方法進行事務回滾的邏輯。但是,在TianLuoServiceImpl
類的spring
事務方法addTianLuo
中,直接把異常catch
住了,並沒有重新throw
出來,因此Spring
自然就catch
不到異常啦,因此事務回滾的邏輯就不會執行,事務就失效了。
解決方案:在 spring
事務方法中,當我們使用了try-catch
,如果catch住異常,記錄完異常日誌什麼的,一定要重新把異常丟擲來,正例如下:
@Service
public class TianLuoServiceImpl implements TianLuoService {
@Autowired
private TianLuoMapper tianLuoMapper;
@Autowired
private TianLuoFlowMapper tianLuoFlowMapper;
@Transactional(rollbackFor = Exception.class)
public void addTianLuo(TianLuo tianluo) {
try {
//儲存tianluo資料庫記錄
tianLuoMapper.save(tianluo);
//儲存tianluo flow資料庫記錄
tianLuoFlowMapper.saveFlow(tianluo);
} catch (Exception e) {
log.error("add TianLuo error,id:{},message:{}", tianluo.getId(),e.getMessage());
throw e;
}
}
}
15. 手動拋了別的異常
@Service
public class TianLuoServiceImpl implements TianLuoService {
@Autowired
private TianLuoMapper tianLuoMapper;
@Autowired
private TianLuoFlowMapper tianLuoFlowMapper;
@Transactional
public void addTianLuo(TianLuo tianluo) throws Exception {
//儲存tianluo資料庫記錄
tianLuoMapper.save(tianluo);
//儲存tianluo流水資料庫記錄
tianLuoFlowMapper.saveFlow(tianluo);
throw new Exception();
}
}
失效的原因:上面的程式碼例子中,手動拋了 Exception
異常,但是是不會回滾的,因為Spring預設只處理RuntimeException和Error
,對於普通的Exception
不會回滾,除非,用rollbackFor
屬性指定配置。解決方案:新增屬性配置 @Transactional(rollbackFor = Exception.class)
。
註解為事務範圍的方法中,事務的回滾僅僅對於unchecked的異常有效。對於checked異常無效。也就是說事務回滾僅僅發生在,出現RuntimeException或Error的時候。通俗一點就是:程式碼中出現的空指標等異常,會被回滾。而檔案讀寫、網路超時問題等,spring就沒法回滾了。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024923/viewspace-2943381/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- spring事務失效的幾種場景以及原因Spring
- 定時任務裡面事務不生效問題
- 聊聊spring事務失效的12種場景,太坑了Spring
- 8個Spring事務失效的場景,你碰到過幾種?Spring
- spring多資料來源下 事務不生效Spring
- 這種場景下的事務如何控制?
- Spring宣告式事務注意點,以及不生效情況Spring
- @Transactional 註解下,事務失效的多種場景
- 事務 - 失效的場景
- 專案中Spring事務失效的場景問題排查Spring
- Spring事務配置的五種方式和spring裡面事務的傳播屬性和事務隔離級別Spring
- Spring基礎系列-Spring事務不生效的問題與迴圈依賴問題Spring
- 各種分散式事務的實現方式適用的場景分散式
- 就這?Spring 事務失效場景及解決方案Spring
- Spring的事務管理(二)宣告式事務管理Spring
- ORACLE分散式事務鎖各種場景下的處理詳解Oracle分散式
- 分散式事務之Spring事務與JMS事務(二)分散式Spring
- spring cloud gateway 不生效SpringCloudGateway
- Spring事務配置的五種方式Spring
- Spring的四種宣告式事務的配置-Hibernate事務Spring
- Spring (二) 事務處理Spring
- @Transactional 四種不生效的 case 分析
- 面試官:你知道哪些事務失效的場景?面試
- 【原始碼講解】Spring事務是如何應用到你的業務場景中的?原始碼Spring
- Spring事務失效的一種原因(this呼叫)Spring
- Spring中同一個service中方法相互呼叫事務不生效問題解決方案Spring
- 【Spring】一次線上@Transational事務註解未生效的原因探究Spring
- [解決] spring service 呼叫當前類方法事務不生效Spring
- 美團多場景建模的探索與實踐
- 專案部署到內網伺服器後@Transactional事務不生效內網伺服器
- spring事務管理原始碼分析(二)事務處理流程分析Spring原始碼
- Spring宣告式事務的兩種實現方式Spring
- spring幾種事務配置@以及spring所需jar包SpringJAR
- 關於ORA-01536 報錯的幾種場景:
- 五(二)、spring 宣告式事務xml配置SpringXML
- Spring 七種事務傳播性介紹Spring
- 美團二面:為什麼不推薦使用 MyBatis 二級快取?大部分人都答不上來!MyBatis快取
- Spring/SpringBoot中的宣告式事務和程式設計式事務原始碼、區別、優缺點、適用場景、實戰Spring Boot程式設計原始碼