在《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方法所示。
2、情形2:給a方法加事務注解,b方法上加或不加
@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中的邏輯也可以被事務管理。
@Transactional(rollbackFor={Exception.class}) public void a() { ((AService) AopContext.currentProxy()).b(); //即調用AOP代理對象的b方法即可執行事務切面進行事務增強 }
這時,就會強制要求調用代理類中的b方法,從而開啟b上的事務,此時b事務上標注的事務傳遞規則也就可以生效了,詳情參見:http://jinnianshilongnian.iteye.com/blog/1487235
二、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捕獲不到,回滾自然失敗。
public void a() throws Exception{ try{ insert(); update(); }catch(Exception e){ throw new Exception(e); } }
不足是本方法的調用端也必須顯式捕獲異常。
public void a() { try{ insert(); update(); }catch(Exception e){ //顯式回滾 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } }
不足是事務控制代碼會侵入業務代碼,也正是因為編程式事務管理會侵入業務邏輯代碼,所以才有了申明式事務管理。
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