spring 事務處理中,同一個類中:A方法(無事務)調B方法(有事務),事務不生效問題


 

簡稱: test=a,test2=b

此時,調用a方法,b里的事務將不生效

這個問題,表面上是事務聲明失效的問題,實質上很可能是Spring的AOP機制實現角度的問題。我想到很久以前研究Spring的AOP實現時發現的一個現象:對於以Cglib方式增強的AOP目標類,會創建兩個對象,一個事Bean實例本身,一個是Cglib增強代理對象,而不僅僅是只有后者。我曾經疑惑過這一點,但當時沒有再仔細探究下去。

我們知道,Spring的AOP實現方式有兩種:1、Java代理方式;2、Cglib動態增強方式,這兩種方式在Spring中是可以無縫自由切換的。Java代理方式的優點是不依賴第三方jar包,缺點是不能代理類,只能代理接口。

Spring通過AopProxy接口,抽象了這兩種實現,實現了一致的AOP方式:

 

 

 

現在看來,這種抽象同樣帶了一個缺陷,那就是抹殺了Cglib能夠直接創建普通類的增強子類的能力,Spring相當於把Cglib動態生成的子類,當普通的代理類了,這也是為什么會創建兩個對象的原因。下圖顯示了Spring的AOP代理類的實際調用過程:


 

 

因此,從上面的分析可以看出,methodB沒有被AopProxy通知到,導致最終結果是:被Spring的AOP增強的類,在同一個類的內部方法調用時,其被調用方法上的增強通知將不起作用。

而這種結果,會造成什么影響呢:

  1:內部調用時,被調用方法的事務聲明將不起作用

  2:換句話說,你在某個方法上聲明它需要事務的時候,如果這個類還有其他開發者,你將不能保證這個方法真的會在事務環境中

  3:再換句話說,Spring的事務傳播策略在內部方法調用時將不起作用。不管你希望某個方法需要單獨事務,是RequiresNew,還是要嵌套事務,要Nested,等等,統統不起作用。

  4:不僅僅是事務通知,所有你自己利用Spring實現的AOP通知,都會受到同樣限制。。。。

解決辦法:

可以把方法B放到另外一個service或者dao,然后把這個server或者dao通過@Autowired注入到方法A的bean里面,這樣即使方法A沒用事務,方法B也可以執行自己的事務了。


以上參考原文出處:https://www.jianshu.com/p/9f472a68f240
 
 
 
 
 
其他解決方案:

spring同類調用事務不生效-原因及三種解決方式

spring提供的聲明式事務注解@Transactional,極大的方便了開發者管理事務,無需手動編寫開啟、提交、回滾事務的代碼。
但是也帶來了一些隱患,如果注解使用不當,可能導致事務不生效,最終導致臟數據也入庫。

如果在同一個類直接調用事務方法,就會導致事務不生效,示例如下

public class StudentServiceImpl implements StudentService { @Autowired private StudentMapper studentMapper; @Override public void insertStudent(){ insert(); } @Transactional(rollbackFor = Exception.class) public void insert() { StudentDO studentDO = new StudentDO(); studentDO.setName("小民"); studentDO.setAge(22); studentMapper.insert(studentDO); if (studentDO.getAge() > 18) { throw new RuntimeException("年齡不能大於18歲"); } } } 

 

事務不生效的原因在於,spring基於AOP機制實現事務的管理,@Authwired StudentService studentService這樣的方式,調用StudentService的方法時,實際上是通過StudentService的代理類調用StudentService的方法,代理類在執行目標方法前后,加上了事務管理的代碼。

 

 

 

 

因此,只有通過注入的StudentService調用事務方法,才會走代理類,才會執行事務管理;如果在同類直接調用,沒走代理類,事務就無效。
注意:除了@Transactional,@Async同樣需要代理類調用,異步才會生效

 

但是在實際的業務場景中,同類調用事務方法難以避免,怎么讓同類調用時事務依然生效呢?有以下三個方法

方法一

自己@Autowired自己,示例如下

@Service public class StudentServiceImpl implements StudentService { @Autowired private StudentMapper studentMapper; @Autowired private StudentService studentService; @Override public void insertStudent(){ studentService.insert(); } @Override @Transactional(rollbackFor = Exception.class) public void insert() { StudentDO studentDO = new StudentDO(); studentDO.setName("小民"); studentDO.setAge(22); studentMapper.insert(studentDO); if (studentDO.getAge() > 18) { throw new RuntimeException("年齡不能大於18歲"); } } } 

可能有人會擔心這樣會有循環依賴的問題,事實上,spring通過三級緩存解決了循環依賴的問題,所以上面的寫法不會有循環依賴問題。
但是!!!,這不代表spring永遠沒有循環依賴的問題(@Async導致循環依賴了解下)

 

方法二

使用AopContext獲取到當前代理類,需要在啟動類加上@EnableAspectJAutoProxy(exposeProxy = true),
示例如下

@Service public class StudentServiceImpl implements StudentService { @Autowired private StudentMapper studentMapper; @Override public void insertStudent(){ getService().insert(); } @Override @Transactional(rollbackFor = Exception.class) public void insert() { StudentDO studentDO = new StudentDO(); studentDO.setName("小民"); studentDO.setAge(22); studentMapper.insert(studentDO); if (studentDO.getAge() > 18) { throw new RuntimeException("年齡不能大於18歲"); } } /** * 通過AopContext獲取代理類 * @return StudentService代理類 */ private StudentService getService(){ return Objects.nonNull(AopContext.currentProxy()) ? (StudentService)AopContext.currentProxy() : this; } } 

exposeProxy = true用於控制AOP框架公開代理,公開后才可以通過AopContext獲取到當前代理類。(默認情況下不會公開代理,因為會降低性能)
注意:不能保證這種方式一定有效,使用@Async時,本方式可能失效。
從@Async案例找到Spring框架的bug:exposeProxy=true不生效原因大剖析

 

 

 

 

 

方式三(推薦)

通過spring上下文獲取到當前代理類,示例如下

@Component public class SpringBeanUtil implements ApplicationContextAware { private static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } /** * 通過class獲取Bean * @param clazz class * @param <T> 泛型 * @return bean */ public static <T> T getBean(Class<T> clazz) { return applicationContext.getBean(clazz); } } 
@Service public class StudentServiceImpl implements StudentService { @Autowired private StudentMapper studentMapper; @Override public void insertStudent(){ StudentService bean = SpringBeanUtil.getBean(StudentService.class); if (null != bean) { bean.insert(); } } @Override @Transactional(rollbackFor = Exception.class) public void insert() { StudentDO studentDO = new StudentDO(); studentDO.setName("小民"); studentDO.setAge(22); studentMapper.insert(studentDO); if (studentDO.getAge() > 18) { throw new RuntimeException("年齡不能大於18歲"); } } } 

當一個類實現ApplicationContextAware接口后,就可以方便的獲得ApplicationContext中的所有bean。


原文出處:https://www.jianshu.com/p/083605986c8f
來源:簡書


免責聲明!

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



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