注解為事務(wù)范圍的方法中,事務(wù)的回滾僅僅對(duì)于unchecked的異常有效。對(duì)于checked異常無效。也就是說事務(wù)回滾僅僅發(fā)生在,出現(xiàn)RuntimeException或Error的時(shí)候。通俗一點(diǎn)就是:代碼中出現(xiàn)的空指針等異常,會(huì)被回滾。而文件讀寫、網(wǎng)絡(luò)超時(shí)問題等,spring就沒法回滾了。
 前言
大家好,我是田螺。
日常開發(fā)中,我們經(jīng)常使用到spring事務(wù)。最近星球一位還有去美團(tuán)面試,被問了這么一道面試題: Spring 事務(wù)在哪幾種情況下會(huì)不生效? 今天田螺哥跟大家聊聊,spring事務(wù)不生效的15種場(chǎng)景。

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實(shí)體數(shù)據(jù)庫(kù)記錄
        tianLuoMapper.save(tianluo);
        //保存tianluo流水?dāng)?shù)據(jù)庫(kù)記錄
        tianLuoFlowMapper.saveFlow(buildFlowByTianLuo(tianluo));
    }
}
- 事務(wù)不生效的原因:上面例子中, @Service注解注釋之后,spring事務(wù)(@Transactional)沒有生效,因?yàn)镾pring事務(wù)是由AOP機(jī)制實(shí)現(xiàn)的,也就是說從Spring IOC容器獲取bean時(shí),Spring會(huì)為目標(biāo)類創(chuàng)建代理,來支持事務(wù)的。但是@Service被注釋后,你的service類都不是spring管理的,那怎么創(chuàng)建代理類來支持事務(wù)呢。
 - 解決方案:加上@Service注解。
 
2.沒有在Spring配置文件中啟用事務(wù)管理器
@Configuration
public class AppConfig {
    // 沒有配置事務(wù)管理器
}
@Service
public class MyService {
    @Transactional
    public void doSomething() {
        // ...
    }
}
- 事務(wù)不生效的原因:沒有在AppConfig中配置事務(wù)管理器,因此Spring無法創(chuàng)建事務(wù)代理對(duì)象,導(dǎo)致事務(wù)不生效。即使在MyService中添加了@Transactional注解,該方法也不會(huì)被Spring管理的事務(wù)代理攔截。
 - 解決方案:為了解決這個(gè)問題,應(yīng)該在AppConfig中配置一個(gè)事務(wù)管器。例如:
 
@Configuration
public class AppConfig {
    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dataSource());
    }
}
@Service
public class MyService {
    @Transactional
    public void doSomething() {
        // ...
    }
}
如果是Spring Boot項(xiàng)目,它默認(rèn)會(huì)自動(dòng)配置事務(wù)管理器并開啟事務(wù)支持。
3. 事務(wù)方法被final、static關(guān)鍵字修飾
@Service
public class TianLuoServiceImpl  {
    @Autowired
    private TianLuoMapper tianLuoMapper;
    
    @Autowired
    private TianLuoFlowMapper tianLuoFlowMapper;
    @Transactional
    public final void addTianLuo(TianLuo tianluo) {
         //保存tianluo實(shí)體數(shù)據(jù)庫(kù)記錄
        tianLuoMapper.save(tianluo);
        //保存tianluo流水?dāng)?shù)據(jù)庫(kù)記錄
        tianLuoFlowMapper.saveFlow(buildFlowByTianLuo(tianluo));
    }
}
- 事務(wù)不生效的原因:如果一個(gè)方法被聲明為final或者static,則該方法不能被子類重寫,也就是說無法在該方法上進(jìn)行動(dòng)態(tài)代理,這會(huì)導(dǎo)致Spring無法生成事務(wù)代理對(duì)象來管理事務(wù)。
 - 解決方案:addTianLuo事務(wù)方法不要用final修飾或者static修飾。
 
4. 同一個(gè)類中,方法內(nèi)部調(diào)用
@Service
public class TianLuoServiceImpl implements TianLuoService {
    @Autowired
    private TianLuoMapper tianLuoMapper;
    
    @Autowired
    private TianLuoFlowMapper tianLuoFlowMapper;
    
    public void addTianLuo(TianLuo tianluo){
     // 調(diào)用內(nèi)部的事務(wù)方法
     this.executeAddTianLuo(tianluo);
   }
    @Transactional
    public void executeAddTianLuo(TianLuo tianluo) {
        tianLuoMapper.save(tianluo);
        tianLuoFlowMapper.saveFlow(buildFlowByTianLuo(tianluo));
    }
}
- 事務(wù)不生效的原因: 事務(wù)是通過Spring AOP代理來實(shí)現(xiàn)的,而在同一個(gè)類中,一個(gè)方法調(diào)用另一個(gè)方法時(shí),調(diào)用方法直接調(diào)用目標(biāo)方法的代碼,而不是通過代理類進(jìn)行調(diào)用。即以上代碼,調(diào)用目標(biāo)executeAddTianLuo方法不是通過代理類進(jìn)行的,因此事務(wù)不生效。
 - 解決方案:可以新建多一個(gè)類,讓這兩個(gè)方法分開,分別在不同的類中。如下:
 
@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);
   }
}
當(dāng)然,有時(shí)候你也可以在該 Service 類中注入自己,或者通過AopContext.currentProxy()獲取代理對(duì)象。
5.方法的訪問權(quán)限不是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));
    }
}
- 事務(wù)不生效的原因:spring事務(wù)方法addTianLuo的訪問權(quán)限不是public,所以事務(wù)就不生效啦,因?yàn)镾pring事務(wù)是由AOP機(jī)制實(shí)現(xiàn)的,AOP機(jī)制的本質(zhì)就是動(dòng)態(tài)代理,而代理的事務(wù)方法不是public的話,computeTransactionAttribute()就會(huì)返回null,也就是這時(shí)事務(wù)屬性不存在了。大家可以看下AbstractFallbackTransactionAttributeSource的源碼:
 

- 解決方案:addTianLuo事務(wù)方法的訪問權(quán)限修改為public。
 
6. 數(shù)據(jù)庫(kù)的存儲(chǔ)引擎不支持事務(wù)
Spring事務(wù)的底層,還是依賴于數(shù)據(jù)庫(kù)本身的事務(wù)支持。在MySQL中,MyISAM存儲(chǔ)引擎是不支持事務(wù)的,InnoDB引擎才支持事務(wù)。因此開發(fā)階段設(shè)計(jì)表的時(shí)候,確認(rèn)你的選擇的存儲(chǔ)引擎是支持事務(wù)的。

7 .配置錯(cuò)誤的 @Transactional 注解
@Transactional(readOnly = true)
public void updateUser(User user) {
    userDao.updateUser(user);
}
- 事務(wù)不生效的原因:雖然使用了@Transactional注解,但是注解中的readOnly=true屬性指示這是一個(gè)只讀事務(wù),因此在更新User實(shí)體時(shí)會(huì)拋出異常。
 - 解決方案:將readOnly屬性設(shè)置為false,或者移除了@Transactional注解中的readOnly屬性。
 
8.事務(wù)超時(shí)時(shí)間設(shè)置過短
@Transactional(timeout = 1)
public void doSomething() {
    //...
}
- 事務(wù)不生效的原因:在上面的例子中,timeout屬性被設(shè)置為1秒,這意味著如果事務(wù)在1 秒內(nèi)無法完成,則報(bào)事務(wù)超時(shí)了。
 
9. 使用了錯(cuò)誤的事務(wù)傳播機(jī)制
@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));
    }
}
- 事務(wù)不生效的原因:Propagation.NOT_SUPPORTED傳播特性不支持事務(wù)。
 - 解決方案:選擇正確的事務(wù)傳播機(jī)制。
 
幫大家復(fù)習(xí)一下,Spring提供了七種事務(wù)傳播機(jī)制。它們分別是:
- REQUIRED(默認(rèn)):如果當(dāng)前存在一個(gè)事務(wù),則加入該事務(wù);否則,創(chuàng)建一個(gè)新事務(wù)。該傳播級(jí)別表示方法必須在事務(wù)中執(zhí)行。
 - SUPPORTS:如果當(dāng)前存在一個(gè)事務(wù),則加入該事務(wù);否則,以非事務(wù)的方式繼續(xù)執(zhí)行。
 - MANDATORY:如果當(dāng)前存在一個(gè)事務(wù),則加入該事務(wù);否則,拋出異常。
 - REQUIRES_NEW:創(chuàng)建一個(gè)新的事務(wù),并且如果存在一個(gè)事務(wù),則將該事務(wù)掛起。
 - NOT_SUPPORTED:以非事務(wù)方式執(zhí)行操作,如果當(dāng)前存在一個(gè)事務(wù),則將該事務(wù)掛起。
 - NEVER:以非事務(wù)方式執(zhí)行操作,如果當(dāng)前存在一個(gè)事務(wù),則拋出異常。
 - NESTED:如果當(dāng)前存在一個(gè)事務(wù),則在嵌套事務(wù)內(nèi)執(zhí)行。如果沒有事務(wù),則按REQUIRED傳播級(jí)別執(zhí)行。嵌套事務(wù)是外部事務(wù)的一部分,可以在外部事務(wù)提交或回滾時(shí)部分提交或回滾。
 
10. rollbackFor屬性配置錯(cuò)誤
@Service
public class TianLuoServiceImpl implements TianLuoService {
    @Autowired
    private TianLuoMapper tianLuoMapper;
    
    @Autowired
    private TianLuoFlowMapper tianLuoFlowMapper;
    @Transactional(rollbackFor = Error.class)
    public void addTianLuo(TianLuo tianluo) {
        //保存tianluo數(shù)據(jù)庫(kù)記錄
        tianLuoMapper.save(tianluo);
        //保存tianluo流水?dāng)?shù)據(jù)庫(kù)記錄
        tianLuoFlowMapper.saveFlow(tianluo);
        //模擬異常拋出
        throw new Exception();
    }
}
- 事務(wù)不生效的原因: 其實(shí)rollbackFor屬性指定的異常必須是Throwable或者其子類。默認(rèn)情況下,RuntimeException和Error兩種異常都是會(huì)自動(dòng)回滾的。但是因?yàn)橐陨系拇a例子,指定了rollbackFor = Error.class,但是拋出的異常又是Exception,而Exception和Error沒有任何什么繼承關(guān)系,因此事務(wù)就不生效。
 

大家可以看一下Transactional注解源碼哈:

- 解決方案:rollbackFor屬性指定的異常與拋出的異常匹配。
 
11.事務(wù)注解被覆蓋導(dǎo)致事務(wù)失效
public interface MyRepository {
    @Transactional
    void save(String data);
}
public class MyRepositoryImpl implements MyRepository {
    @Override
    public void save(String data) {
        // 數(shù)據(jù)庫(kù)操作
    }
}
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);
    }
}- 事務(wù)失效的原因:MyTianluoService是MyService的子類,并且覆蓋了doSomething()方法。在該方法中,使用了不同的傳播行為(REQUIRES_NEW)來覆蓋父類的@Transactional注解。在這種情況下,當(dāng)調(diào)用MyTianluoService的doSomething()方法時(shí),由于子類方法中的注解覆蓋了父類的注解,Spring框架將不會(huì)在父類的方法中啟動(dòng)事務(wù)。因此,當(dāng)MyRepository的save()方法被調(diào)用時(shí),事務(wù)將不會(huì)被啟動(dòng),也不會(huì)回滾。這將導(dǎo)致數(shù)據(jù)不一致的問題,因?yàn)樵贛yRepository的save()方法中進(jìn)行的數(shù)據(jù)庫(kù)操作將不會(huì)回滾。
 
12.嵌套事務(wù)的坑
@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();
    }
}
以上代碼使用了嵌套事務(wù),如果saveFlow出現(xiàn)運(yùn)行時(shí)異常,會(huì)繼續(xù)往上拋,到外層addTianLuo的方法,導(dǎo)致tianLuoMapper.save也會(huì)回滾啦。如果不想因?yàn)楸粌?nèi)部嵌套的事務(wù)影響,可以用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. 事務(wù)多線程調(diào)用
@Service
public class TianLuoService {
    @Autowired
    private TianLuoMapper tianLuoMapper;
    @Autowired
    private TianLuoFlowService tianLuoFlowService;
    @Transactional
    public void addTianLuo(TianLuo tianluo) {
        //保存tianluo數(shù)據(jù)庫(kù)記錄
        tianLuoMapper.save(tianluo);
        //多線程調(diào)用
        new Thread(() -> {
            tianLuoFlowService.saveFlow(tianluo);
        }).start();
    }
}
@Service
public class TianLuoFlowService {
    @Autowired
    private TianLuoFlowMapper tianLuoFlowMapper;
    @Transactional
    public void save(TianLuo tianLuo) {
        tianLuoFlowMapper.saveFlow(tianLuo);
    }
}
- 事務(wù)不生效原因:這是因?yàn)镾pring事務(wù)是基于線程綁定的,每個(gè)線程都有自己的事務(wù)上下文,而多線程環(huán)境下可能會(huì)存在多個(gè)線程共享同一個(gè)事務(wù)上下文的情況,導(dǎo)致事務(wù)不生效。Spring事務(wù)管理器通過使用線程本地變量(ThreadLocal)來實(shí)現(xiàn)線程安全。大家有興趣的話,可以去看下源碼哈.
 
在Spring事務(wù)管理器中,通過TransactionSynchronizationManager類來管理事務(wù)上下文。TransactionSynchronizationManager內(nèi)部維護(hù)了一個(gè)ThreadLocal對(duì)象,用來存儲(chǔ)當(dāng)前線程的事務(wù)上下文。在事務(wù)開始時(shí),TransactionSynchronizationManager會(huì)將事務(wù)上下文綁定到當(dāng)前線程的ThreadLocal對(duì)象中,當(dāng)事務(wù)結(jié)束時(shí),TransactionSynchronizationManager會(huì)將事務(wù)上下文從ThreadLocal對(duì)象中移除。

14.異常被捕獲并處理了,沒有重新拋出
@Service
public class TianLuoServiceImpl implements TianLuoService {
    @Autowired
    private TianLuoMapper tianLuoMapper;
    @Autowired
    private TianLuoFlowMapper tianLuoFlowMapper;
    @Transactional
    public void addTianLuo(TianLuo tianluo) {
        try {
            //保存tianluo數(shù)據(jù)庫(kù)記錄
            tianLuoMapper.save(tianluo);
            //保存tianluo flow數(shù)據(jù)庫(kù)記錄
            tianLuoFlowMapper.saveFlow(tianluo);
        } catch (Exception e) {
            log.error("add TianLuo error,id:{},message:{}", tianluo.getId(),e.getMessage());
        }
    }
}
- 事務(wù)不生效的原因: 事務(wù)中的異常已經(jīng)被業(yè)務(wù)代碼捕獲并處理,而沒有被正確地傳播回事務(wù)管理器,事務(wù)將無法回滾。我們可以從spring源碼(TransactionAspectSupport這個(gè)類)中找到答案:
 
public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {
 //這方法會(huì)省略部分代碼,只留關(guān)鍵代碼哈
  @Nullable
 protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass, final InvocationCallback invocation) throws Throwable {
  if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
  
   TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
   Object retVal;
   try {
        //Spring AOP中MethodInterceptor接口的一個(gè)方法,它允許攔截器在執(zhí)行被代理方法之前和之后執(zhí)行額外的邏輯。
    retVal = invocation.proceedWithInvocation();
   }
   catch (Throwable ex) {
        //用于在發(fā)生異常時(shí)完成事務(wù)(如果Spring catch不到對(duì)應(yīng)的異常的話,就不會(huì)進(jìn)入回滾事務(wù)的邏輯)
    completeTransactionAfterThrowing(txInfo, ex);
    throw ex;
   }
   finally {
    cleanupTransactionInfo(txInfo);
   }
      //用于在方法正常返回后提交事務(wù)。
   commitTransactionAfterReturning(txInfo);
   return retVal;
  }
}在invokeWithinTransaction方法中,當(dāng)Spring catch到Throwable異常的時(shí)候,就會(huì)調(diào)用completeTransactionAfterThrowing()方法進(jìn)行事務(wù)回滾的邏輯。但是,在TianLuoServiceImpl類的spring事務(wù)方法addTianLuo中,直接把異常catch住了,并沒有重新throw出來,因此 Spring自然就catch不到異常啦,因此事務(wù)回滾的邏輯就不會(huì)執(zhí)行,事務(wù)就失效了。
- 解決方案:在spring事務(wù)方法中,當(dāng)我們使用了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數(shù)據(jù)庫(kù)記錄
            tianLuoMapper.save(tianluo);
            //保存tianluo flow數(shù)據(jù)庫(kù)記錄
            tianLuoFlowMapper.saveFlow(tianluo);
        } catch (Exception e) {
            log.error("add TianLuo error,id:{},message:{}", tianluo.getId(),e.getMessage());
            throw e;
        }
    }
}
15. 手動(dòng)拋了別的異常
@Service
public class TianLuoServiceImpl implements TianLuoService {
    @Autowired
    private TianLuoMapper tianLuoMapper;
    
    @Autowired
    private TianLuoFlowMapper tianLuoFlowMapper;
    @Transactional
    public void addTianLuo(TianLuo tianluo) throws Exception {
        //保存tianluo數(shù)據(jù)庫(kù)記錄
        tianLuoMapper.save(tianluo);
        //保存tianluo流水?dāng)?shù)據(jù)庫(kù)記錄
        tianLuoFlowMapper.saveFlow(tianluo);
        throw new Exception();
    }
}
- 失效的原因:上面的代碼例子中,手動(dòng)拋了Exception異常,但是是不會(huì)回滾的,因?yàn)镾pring默認(rèn)只處理RuntimeException和Error,對(duì)于普通的Exception不會(huì)回滾,除非,用rollbackFor屬性指定配置。
 - 解決方案:添加屬性配置@Transactional(rollbackFor = Exception.class)。
 
注解為事務(wù)范圍的方法中,事務(wù)的回滾僅僅對(duì)于unchecked的異常有效。對(duì)于checked異常無效。也就是說事務(wù)回滾僅僅發(fā)生在,出現(xiàn)RuntimeException或Error的時(shí)候。通俗一點(diǎn)就是:代碼中出現(xiàn)的空指針等異常,會(huì)被回滾。而文件讀寫、網(wǎng)絡(luò)超時(shí)問題等,spring就沒法回滾了。