Spring事務管理的一些注意點


      在《Spring Boot事務管理(下)》中,已經介紹了如果在 protected、private 或者默認可見性的方法上使用@Transactional,事務將是擺設,也不會拋出任何異常,並簡單的給出了一些使用時的注意事項。本文在此基礎上進一步解釋如何正確使用Spring聲明式事務。

一、關於目標對象內部方法自我調用的不同情形和存在的問題

1、情形1:只給b方法上加事務注解,a方法上不加

   目標類的接口和實現代碼示例:
public interface AService { 
    public void a(); 
    public void b(); 
} 
 
@Service() 
public class AServiceImpl implements AService{ 
    public void a() { 
        this.b(); 
   }
 
   @Transactional(rollbackFor={Exception.class}) 
   public void b() { 
      insert(); 
      update(); 
   } 
}

       只要給目標類AServiceImpl的某個方法加上注解@Transactional,Spring就會為目標類生成對應的代理類,以后調用AServiceImpl中的所有方法都會先走代理類(即使調用未加事務注解的方法a,也走),即在通過getBean("AServiceImpl")獲得業務類時,實際上得到的是一個代理類,假設這個類叫做AServiceImplProxy,Spring為AServiceImpl生成的代理類類似於如下代碼:

public class AServiceImplProxy implements AService{
 
    public void a() { 
        //反射調用目標類的a方法 
   }
 
   public void b() { 
      //啟動事務的代碼 
      //反射調用目標類的b方法 
      //事務提交的代碼 
   } 
}

       由於目標類中只有b方法加入了事務管理,所以代理類中只為b方法加入了橫切事務邏輯,Spring事務管理的本質是通過aop為目標類生成動態代理類,並在需要進行事務管理的方法中加入事務管理的橫切邏輯代碼,如AServiceImplProxy中的b方法所示。

        調用getBean("AServiceImpl").a()時,實際上執行的是AServiceImplProxy.a(),代理類的a方法會通過反射調用目標類的a方法, 再在目標類的a方法中調用b方法,故最終a中調用的b方法是來自於AServiceImpl中的b方法,AServiceImpl的b方法並沒有橫切事務邏輯代碼(切記:事務邏輯代碼在代理類中,@Transactional只是標記此方法在代理類中要加入事務邏輯代碼)。所以調用a方法時,b方法的事務會失效。
       其實,在proxy對象與目標對象之間還有一個InvocationHandler對象(以jdk動態代理為例),真正的橫切邏輯是放到InvocationHandler對象中的,調用邏輯分離到InvocationHandler中主要是為了構造出具有通用性和簡單性的代理類,此處為了簡化處理過程,統一放到代理對象中來說明,動態代理簡化的調用關系圖如下:
 
AOP中存在方法嵌套調用時,相應的調用過程序列圖如下:
 

2、情形2:給a方法加事務注解,b方法上加或不加

對1中的代碼做修改,為a方法也加上事務注解:
@Service() 
public class AServiceImpl implements AService{ 
    @Transactional(rollbackFor={Exception.class}) 
        public void a() { 
        this.b(); 
    }
 
    @Transactional(rollbackFor={Exception.class}) 
        public void b() { 
        insert(); 
        update(); 
    } 
}

    此時生成的代理類類似如下代碼:

public class AServiceImplProxy implements AService{ 
    public void a() { 
        //啟動事務的代碼 
        //反射調用目標類的a方法 
        //事務提交的代碼 
    }
 
    public void b() { 
        //啟動事務的代碼 
        //反射調用目標類的b方法 
        //事務提交的代碼 
    }
 
}

    即為a和b都加入了事務橫切邏輯。在這種情況下,調用順序還和1中情形類似,區別在於在反射調用目標對象的a方法前,會對a方法開啟事務管理,雖然調用的b方法還是目標對象中沒有加事務邏輯的代碼,spring卻會把b合並到a的事務中去,此時相當於只有一個事務。如果再將目標類代碼改為:

@Service() 
public class AServiceImpl implements AService{ 
    @Transactional(rollbackFor={Exception.class}) 
    public void a() { 
        this.b(); 
    }
     public void b() { 
        insert();
        update(); 
    }
 }

    即只在a上加事務控制,由於b會合並到a的事務中,所以b中的邏輯也可以被事務管理。

    由於a和b都合並到了a的事務中,所以這種情形下事務傳遞規則不適用。代理類中加了事務邏輯的b方法永遠不會被調用。 那么問題來了,如果我想讓b也執行自己的事務邏輯,即調用b時執行代理類中b方法的事務邏輯,該怎么辦? 修改目標類中的a方法:
@Transactional(rollbackFor={Exception.class}) 
public void a() { 
    ((AService) AopContext.currentProxy()).b(); 
    //即調用AOP代理對象的b方法即可執行事務切面進行事務增強 
}

     這時,就會強制要求調用代理類中的b方法,從而開啟b上的事務,此時b事務上標注的事務傳遞規則也就可以生效了,詳情參見:http://jinnianshilongnian.iteye.com/blog/1487235

     個人覺得這種方法不太好,會污染業務邏輯代碼,使代碼變復雜,而且不符合低侵入的開發理念。 還有一種辦法就是接口下沉,把b方法分離到另一個接口中,從根源上避免目標對象內部方法自我調用。

二、try catch的問題

     有時需要在業務邏輯代碼中顯式try catch包裹事務代碼,以便在出現異常時進行一些別的處理。 目標類的接口和實現示例代碼如下:
public interface AService { 
    public void a(); 
} 
@Service() 
public class AServiceImpl implements AService{ 
    @Transactional(rollbackFor={Exception.class}) 
    public void a() { 
        try{ 
            insert(); 
            update(); 
        }catch(Exception e){ 
            // to do something
        } 
    }
 
}

    自己在代碼中顯式捕獲異常會導致spring事務回滾失效,原因:spring事務是通過aop捕獲到異常后再執行回滾,如果業務代碼中顯式捕獲了異常,會導致spring捕獲不到,回滾自然失敗。

有如下幾種解決辦法:
(1)業務代碼catch住異常后重新拋出,如:
public void a() throws Exception{ 
    try{ 
        insert(); 
        update(); 
    }catch(Exception e){ 
        throw new Exception(e); 
    }
 
}

不足是本方法的調用端也必須顯式捕獲異常。

(2)使用編程式事務顯式回滾:
public void a() { 
    try{ 
        insert(); 
        update(); 
    }catch(Exception e){ 
        //顯式回滾 
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); 
    } 
}

    不足是事務控制代碼會侵入業務代碼,也正是因為編程式事務管理會侵入業務邏輯代碼,所以才有了申明式事務管理。

(3)接口下沉
    將需要事務控制的代碼分到另一個接口方法中,如:
public interface BService { 
    public void a(); 
    public void b(); 
} 
 
@Service() 
public class BServiceImpl implements BService{ 
    @Transactional(rollbackFor={Exception.class}) 
    public void b() { 
        insert(); 
        update(); 
    } 
}

     相應的調用端a方法中變為:

public void a() throws Exception{
    try{ 
        bService.b(); 
    }catch(Exception e){ 
        throw new Exception(e); 
    } 
}

Reference

https://blog.csdn.net/liujinye/article/details/78286721

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM