面試題:spring事務失效的9大緣由

歡迎關注公衆號【sharedCode】致力於主流中間件的源碼分析, 我的網站:https://www.shared-code.com/java

1.spring事務實現方式及原理

Spring 事務的本質其實就是數據庫對事務的支持,沒有數據庫的事務支持,spring 是沒法提供事務功能的。真正的數據庫層的事務提交和回滾是在binlog提交以後進行提交的 經過 redo log 來重作, undo log來回滾。spring

通常咱們在程序裏面使用的都是在方法上面加@Transactional 註解,這種屬於聲明式事務。數據庫

聲明式事務本質是經過 AOP 功能,對方法先後進行攔截,將事務處理的功能編織到攔截的方法中,也就是在目標方法開始以前加入一個事務,在執行完目標方法以後根據執行狀況提交或者回滾事務。源碼分析

2.數據庫自己不支持事務

這裏以 MySQL 爲例,其 MyISAM 引擎是不支持事務操做的,InnoDB 纔是支持事務的引擎,通常要支持事務都會使用 InnoDB網站

3.當前類的調用

@Service
public class UserServiceImpl implements UserService {

    public void update(User user) {
        updateUser(user);
    }
    
    @Transactional(rollbackFor = Exception.class)
    public void updateUser(User user) {
        // update user
    }
    
}

上面的這種狀況下是不會有事務管理操做的。this

經過看聲明式事務的原理可知,spring使用的是AOP切面的方式,本質上使用的是動態代理來達到事務管理的目的,當前類調用的方法上面加@Transactional 這個是沒有任何做用的,由於調用這個方法的是this..net

OK, 咱們在看下面的一種例子。代理

@Service
public class UserServiceImpl implements UserService {

    @Transactional(rollbackFor = Exception.class)
    public void update(User user) {
        updateUser(user);
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void updateUser(User user) {
        // update user
    }
    
}

此次在 update 方法上加了 @Transactional,updateUser 加了 REQUIRES_NEW 新開啓一個事務,那麼新開的事務管用麼?code

答案是:無論用!中間件

由於它們發生了自身調用,就調該類本身的方法,而沒有通過 Spring 的代理類,默認只有在外部調用事務纔會生效,這也是老生常談的經典問題了。

4.方法不是public的

@Service
public class UserServiceImpl implements UserService {

    @Transactional(rollbackFor = Exception.class)
    private void updateUser(User user) {
        // update user
    }
    
}

private 方法是不會被spring代理的,所以是不會有事務產生的,這種作法是無效的。

5.沒有被spring管理

//@Service
public class UserServiceImpl implements UserService {

    @Transactional(rollbackFor = Exception.class)
    public void updateUser(User user) {
        // update user
    }
    
}

沒有被spring管理的bean, spring連代理對象都沒法生成,固然無效咯。

6.配置的事務傳播性有問題

@Service
public class UserServiceImpl implements UserService {

    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void update(User user) {
        // update user
    }    
}

回顧一下spring的事務傳播行爲

Spring 事務的傳播行爲說的是,當多個事務同時存在的時候, Spring 如何處理這些事務的行爲。

  1. PROPAGATION_REQUIRED:若是當前沒有事務,就建立一個新事務,若是當前存在事務,就加入該事務,該設置是最經常使用的設置。
  2. PROPAGATION_SUPPORTS:支持當前事務,若是當前存在事務,就加入該事務,若是當前不存在事務,就以非事務執行
  3. PROPAGATION_MANDATORY:支持當前事務,若是當前存在事務,就加入該事務,若是當前不存在事務,就拋出異常。
  4. PROPAGATION_REQUIRES_NEW:建立新事務,不管當前存不存在事務,都建立新事務。
  5. PROPAGATION_NOT_SUPPORTED:以非事務方式執行操做,若是當前存在事務,就把當前事務掛起。
  6. PROPAGATION_NEVER: 以非事務方式執行,若是當前存在事務,則拋出異常。
  7. PROPAGATION_NESTED:若是當前存在事務,則在嵌套事務內執行。若是當前沒有事務,則按 REQUIRED 屬性執行

當傳播行爲設置了PROPAGATION_NOT_SUPPORTED,PROPAGATION_NEVER,PROPAGATION_SUPPORTS這三種時,就有可能存在事務不生效

7.異常被你 "抓住"了

@Service
public class UserServiceImpl implements UserService {

    @Transactional(rollbackFor = Exception.class)
    public void update(User user) {
        
      try{
        // update user
      }catch(Execption e){
         log.error("異常",e)
      }
    }    
}

異常被抓了,這樣子代理類就沒辦法知道你到底有沒有錯誤,需不須要回滾,因此這種狀況也是沒辦法回滾的哦。

8.接口層聲明式事務使用cglib代理

public interface UserService   {

    @Transactional(rollbackFor = Exception.class)
    public void update(User user)  
}
@Service
public class UserServiceImpl implements UserService {

    
    public void update(User user) {
        // update user
    }    
}

經過元素的 "proxy-target-class" 屬性值來控制是基於接口的仍是基於類的代理被建立。若是 "proxy-target-class" 屬值被設置爲 "true",那麼基於類的代理將起做用(這時須要CGLIB庫cglib.jar在CLASSPATH中)。若是 "proxy-target-class" 屬值被設置爲 "false" 或者這個屬性被省略,那麼標準的JDK基於接口的代理將起做用

註解@Transactional cglib與java動態代理最大區別是代理目標對象不用實現接口,那麼註解要是寫到接口方法上,要是使用cglib代理,這是註解事務就失效了,爲了保持兼容註解最好都寫到實現類方法上。

9.rollbackFor異常指定錯誤

@Service
public class UserServiceImpl implements UserService {

    @Transactional
    public void update(User user) {
        // update user
    }    
}

上面這種沒有指定回滾異常,這個時候默認的回滾異常是RuntimeException ,若是出現其餘異常那麼就不會回滾事務

相關文章
相關標籤/搜索