spring本類中呼叫事務方法不生效
前段時間寫程式碼時,呼叫本類中@Transactional(propagation = Propagation.REQUIRES_NEW) 的方法,事務不生效不回滾,記錄於此
本文轉自http://blog.csdn.net/dapinxiaohuo/article/details/52092447
1、預備知識
使用AOP 代理後的方法呼叫執行流程,如圖所示
也就是說我們首先呼叫的是AOP代理物件而不是目標物件,首先執行事務切面,事務切面內部通過TransactionInterceptor環繞增強進行事務的增強,即進入目標方法之前開啟事務,退出目標方法時提交/回滾事務。
2、測試程式碼準備
Java程式碼- public interface AService {
- public void a();
- public void b();
- }
- @Service()
- public class AServiceImpl1 implements AService{
- @Transactional(propagation = Propagation.REQUIRED)
- public void a() {
- this.b();
- }
- @Transactional(propagation = Propagation.REQUIRES_NEW)
- public void b() {
- }
- }
3、問題
目標物件內部的自我呼叫將無法實施切面中的增強,如圖所示
此處的this指向目標物件,因此呼叫this.b()將不會執行b事務切面,即不會執行事務增強,因此b方法的事務定義“@Transactional(propagation = Propagation.REQUIRES_NEW)”將不會實施,即結果是b和a方法的事務定義是一樣的,可以從以下日誌看出:
org.springframework.transaction.annotation.AnnotationTransactionAttributeSource Adding transactional method 'a' with attribute: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
org.springframework.beans.factory.support.DefaultListableBeanFactory Returning cached instance of singleton bean 'txManager'
org.springframework.orm.hibernate4.HibernateTransactionManager Creating new transaction with name [com.sishuok.service.impl.AServiceImpl1.a]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; '' -----建立a方法事務
org.springframework.orm.hibernate4.HibernateTransactionManager Opened new Session …… for hibernate transaction ---開啟Session
……
org.springframework.transaction.support.TransactionSynchronizationManager Initializing transaction synchronization
org.springframework.transaction.interceptor.TransactionInterceptor Getting transaction for [com.sishuok.service.impl.AServiceImpl1.a]
org.springframework.transaction.interceptor.TransactionInterceptor Completing transaction for [com.sishuok.service.impl.AServiceImpl1.a] ----完成a方法事務
org.springframework.orm.hibernate4.HibernateTransactionManager Triggering beforeCommit synchronization
org.springframework.orm.hibernate4.HibernateTransactionManager Triggering beforeCompletion synchronization
org.springframework.orm.hibernate4.HibernateTransactionManager Initiating transaction commit
org.springframework.orm.hibernate4.HibernateTransactionManager Committing Hibernate transaction on Session ……---提交a方法事務
或
org.springframework.orm.hibernate4.HibernateTransactionManager Rolling back Hibernate transaction on Session ……---如果有異常將回滾a方法事務
org.springframework.orm.hibernate4.HibernateTransactionManager Triggering afterCommit synchronization
org.springframework.orm.hibernate4.HibernateTransactionManager Triggering afterCompletion synchronization
org.springframework.transaction.support.TransactionSynchronizationManager Clearing transaction synchronization
……
org.springframework.orm.hibernate4.HibernateTransactionManager Closing Hibernate Session …… after transaction --關閉Session
我們可以看到事務切面只對a方法進行了事務增強,沒有對b方法進行增強。
3、解決方案
此處a方法中呼叫b方法時,只要通過AOP代理呼叫b方法即可走事務切面,即可以進行事務增強,如下所示:
Java程式碼- public void a() {
- aopProxy.b();//即呼叫AOP代理物件的b方法即可執行事務切面進行事務增強
- }
判斷一個Bean是否是AOP代理物件可以使用如下三種方法:
AopUtils.isAopProxy(bean) : 是否是代理物件;
AopUtils.isCglibProxy(bean) : 是否是CGLIB方式的代理物件;
AopUtils.isJdkDynamicProxy(bean) : 是否是JDK動態代理方式的代理物件;
3.1、通過ThreadLocal暴露Aop代理物件
1、開啟暴露Aop代理到ThreadLocal支援(如下配置方式從spring3開始支援)
Java程式碼- <aop:aspectj-autoproxy expose-proxy="true"/><!—註解風格支援-->
- <aop:config expose-proxy="true"><!—xml風格支援-->
2、修改我們的業務實現類
this.b();-----------修改為--------->((AService) AopContext.currentProxy()).b();
3、執行測試用例,日誌如下
org.springframework.beans.factory.support.DefaultListableBeanFactory Returning cached instance of singleton bean 'txManager'
org.springframework.orm.hibernate4.HibernateTransactionManager Creating new transaction with name [com.sishuok.service.impl.AServiceImpl2.a]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; '' -----建立a方法事務
org.springframework.orm.hibernate4.HibernateTransactionManager Opened new Session ……for Hibernate transaction --開啟a Session
org.springframework.orm.hibernate4.HibernateTransactionManager Preparing JDBC Connection of Hibernate Session ……
org.springframework.orm.hibernate4.HibernateTransactionManager Exposing Hibernate transaction as JDBC transaction ……
……
org.springframework.transaction.support.TransactionSynchronizationManager Initializing transaction synchronization
org.springframework.transaction.interceptor.TransactionInterceptor Getting transaction for [com.sishuok.service.impl.AServiceImpl2.a]
org.springframework.transaction.annotation.AnnotationTransactionAttributeSource Adding transactional method 'b' with attribute: PROPAGATION_REQUIRES_NEW,ISOLATION_DEFAULT; ''
……
org.springframework.orm.hibernate4.HibernateTransactionManager Suspending current transaction, creating new transaction with name [com.sishuok.service.impl.AServiceImpl2.b] -----建立b方法事務(並暫停a方法事務)
……
org.springframework.orm.hibernate4.HibernateTransactionManager Opened new Session for Hibernate transaction ---開啟b Session
……
org.springframework.transaction.support.TransactionSynchronizationManager Initializing transaction synchronization
org.springframework.transaction.interceptor.TransactionInterceptor Getting transaction for [com.sishuok.service.impl.AServiceImpl2.b]
org.springframework.transaction.interceptor.TransactionInterceptor Completing transaction for [com.sishuok.service.impl.AServiceImpl2.b] ----完成b方法事務
org.springframework.orm.hibernate4.HibernateTransactionManager Triggering beforeCommit synchronization
org.springframework.orm.hibernate4.HibernateTransactionManager Triggering beforeCompletion synchronization
org.springframework.orm.hibernate4.HibernateTransactionManager Initiating transaction commit
org.springframework.orm.hibernate4.HibernateTransactionManager Committing Hibernate transaction on Session …… ---提交b方法事務
org.springframework.orm.hibernate4.HibernateTransactionManager Triggering afterCommit synchronization
org.springframework.orm.hibernate4.HibernateTransactionManager Triggering afterCompletion synchronization
org.springframework.transaction.support.TransactionSynchronizationManager Clearing transaction synchronization
……
org.springframework.orm.hibernate4.HibernateTransactionManager Closing Hibernate Session …… after transaction --關閉 b Session
-----到此b方法事務完畢
org.springframework.orm.hibernate4.HibernateTransactionManager Resuming suspended transaction after completion of inner transaction ---恢復a方法事務
……
org.springframework.transaction.support.TransactionSynchronizationManager Initializing transaction synchronization
org.springframework.transaction.interceptor.TransactionInterceptor Completing transaction for [com.sishuok.service.impl.AServiceImpl2.a] ----完成a方法事務
org.springframework.orm.hibernate4.HibernateTransactionManager Triggering beforeCommit synchronization
org.springframework.orm.hibernate4.HibernateTransactionManager Triggering beforeCompletion synchronization
org.springframework.orm.hibernate4.HibernateTransactionManager Initiating transaction commit
org.springframework.orm.hibernate4.HibernateTransactionManager Committing Hibernate transaction on Session ……---提交a方法事務
org.springframework.orm.hibernate4.HibernateTransactionManager Triggering afterCommit synchronization
org.springframework.orm.hibernate4.HibernateTransactionManager Triggering afterCompletion synchronization
org.springframework.transaction.support.TransactionSynchronizationManager Clearing transaction synchronization
……
org.springframework.orm.hibernate4.HibernateTransactionManager Closing Hibernate Session …… after transaction --關閉 a Session
此處我們可以看到b方法的事務起作用了。
以上方式是解決目標物件內部方法自我呼叫並實施事務的最簡單的解決方案。
4、實現原理分析
4.1、在進入代理物件之後通過AopContext.serCurrentProxy(proxy)暴露當前代理物件到ThreadLocal,並儲存上次ThreadLocal繫結的代理物件為oldProxy;
4.2、接下來我們可以通過 AopContext.currentProxy() 獲取當前代理物件;
4.3、在退出代理物件之前要重新將ThreadLocal繫結的代理物件設定為上一次的代理物件,即AopContext.serCurrentProxy(oldProxy)。
有些人不喜歡這種方式,說通過ThreadLocal暴露有效能問題,其實這個不需要考慮,因為事務相關的(Session和Connection)內部也是通過SessionHolder和ConnectionHolder暴露到ThreadLocal實現的。
不過自我呼叫這種場景確實只有很少情況遇到,因此不用這種方式我們也可以通過如下方式實現。
3.2、通過初始化方法在目標物件中注入代理物件
Java程式碼- @Service
- public class AServiceImpl3 implements AService{
- @Autowired //① 注入上下文
- private ApplicationContext context;
- private AService proxySelf; //② 表示代理物件,不是目標物件
- @PostConstruct //③ 初始化方法
- private void setSelf() {
- //從上下文獲取代理物件(如果通過proxtSelf=this是不對的,this是目標物件)
- //此種方法不適合於prototype Bean,因為每次getBean返回一個新的Bean
- proxySelf = context.getBean(AService.class);
- }
- @Transactional(propagation = Propagation.REQUIRED)
- public void a() {
- proxySelf.b(); //④ 呼叫代理物件的方法 這樣可以執行事務切面
- }
- @Transactional(propagation = Propagation.REQUIRES_NEW)
- public void b() {
- }
- }
此處日誌就不分析,和3.1類似。此種方式不是很靈活,所有需要自我呼叫的實現類必須重複實現程式碼。
BeanPostProcessor 的介紹和使用敬請等待我的下一篇分析帖。
一、定義BeanPostProcessor 需要使用的標識介面
Java程式碼- public interface BeanSelfAware {
- void setSelf(Object proxyBean);
- }
即我們自定義的BeanPostProcessor (InjectBeanSelfProcessor)如果發現我們的Bean是實現了該標識介面就呼叫setSelf注入代理物件。
二、Bean實現
Java程式碼- @Service
- public class AServiceImpl4 implements AService, BeanSelfAware {//此處省略介面定義
- private AService proxySelf;
- public void setSelf(Object proxyBean) { //通過InjectBeanSelfProcessor注入自己(目標物件)的AOP代理物件
- this.proxySelf = (AService) proxyBean;
- }
- @Transactional(propagation = Propagation.REQUIRED)
- public void a() {
- proxySelf.b();//呼叫代理物件的方法 這樣可以執行事務切面
- }
- @Transactional(propagation = Propagation.REQUIRES_NEW)
- public void b() {
- }
- }
實現BeanSelfAware標識介面的setSelf將代理物件注入,並且通過“proxySelf.b()”這樣可以實施b方法的事務定義。
三、InjectBeanSelfProcessor實現
Java程式碼- @Component
- public class InjectBeanSelfProcessor implements BeanPostProcessor {
- public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
- return bean;
- }
- public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
- if(bean instanceof BeanSelfAware) {//如果Bean實現了BeanSelfAware標識介面,就將代理物件注入
- ((BeanSelfAware) bean).setSelf(bean); //即使是prototype Bean也可以使用此種方式
- }
- return bean;
- }
- }
postProcessAfterInitialization根據目標物件是否實現BeanSelfAware標識介面,通過setSelf(bean)將代理物件(bean)注入到目標物件中,從而可以完成目標物件內部的自我呼叫。
關於BeanPostProcessor的執行流程等請一定參考我的這篇帖子,否則無法繼續往下執行。
四、InjectBeanSelfProcessor的問題
(1、場景:通過InjectBeanSelfProcessor進行注入代理物件且迴圈依賴場景下會產生前者無法通過setSelf設定代理物件的問題。 迴圈依賴是應該避免的,但是實際工作中不可避免會有人使用這種注入,畢竟沒有強制性。
(2、用例
(2.1、定義BeanPostProcessor 需要使用的標識介面
和3.1中一樣此處不再重複。
(2.2、Bean實現
Java程式碼- @Service
- public class AServiceImpl implements AService, BeanSelfAware {//此處省略Aservice介面定義
- @Autowired
- private BService bService; //① 通過@Autowired方式注入BService
- private AService self; //② 注入自己的AOP代理物件
- public void setSelf(Object proxyBean) {
- this.self = (AService) proxyBean; //③ 通過InjectBeanSelfProcessor注入自己(目標物件)的AOP代理物件
- System.out.println("AService=="+ AopUtils.isAopProxy(this.self)); //如果輸出true標識AOP代理物件注入成功
- }
- @Transactional(propagation = Propagation.REQUIRED)
- public void a() {
- self.b();
- }
- @Transactional(propagation = Propagation.REQUIRES_NEW)
- public void b() {
- }
- }
- @Service
- public class BServiceImpl implements BService, BeanSelfAware {//此處省略Aservice介面定義
- @Autowired
- private AService aService; //① 通過@Autowired方式注入AService
- private BService self; //② 注入自己的AOP代理物件
- public void setSelf(Object proxyBean) { //③ 通過InjectBeanSelfProcessor注入自己(目標物件)的AOP代理物件
- this.self = (BService) proxyBean;
- System.out.println("BService=" + AopUtils.isAopProxy(this.self)); //如果輸出true標識AOP代理物件注入成功
- }
- @Transactional(propagation = Propagation.REQUIRED)
- public void a() {
- self.b();
- }
- @Transactional(propagation = Propagation.REQUIRES_NEW)
- public void b() {
- }
- }
此處A依賴B,B依賴A,即構成迴圈依賴,此處不探討迴圈依賴的設計問題(實際工作應該避免迴圈依賴),只探討為什麼迴圈依賴會出現注入代理物件失敗的問題。
(2.3、InjectBeanSelfProcessor實現
和之前3.3中一樣 此處不再重複。
(2.4、測試用例
Java程式碼- @RunWith(value = SpringJUnit4ClassRunner.class)
- @ContextConfiguration(value = {"classpath:spring-config.xml"})
- public class SelfInjectTest {
- @Autowired
- AService aService;
- @Autowired
- BService bService;
- @Test
- public void test() {
- }
- }
執行如上測試用例會輸出:
BService=true
AService==false
即BService通過InjectBeanSelfProcessor注入代理物件成功,而AService卻失敗了(實際是注入了目標物件),如下是debug得到的資訊:
(2. 5、這是為什麼呢,怎麼在迴圈依賴會出現這種情況?
敬請期待我的下一篇分析帖。
3.4、改進版的InjectBeanSelfProcessor的解決方案
Java程式碼- @Component
- public class InjectBeanSelfProcessor2 implements BeanPostProcessor, ApplicationContextAware {
- private ApplicationContext context;
- //① 注入ApplicationContext
- public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
- this.context = applicationContext;
- }
- public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
- if(!(bean instanceof BeanSelfAware)) { //② 如果Bean沒有實現BeanSelfAware標識介面 跳過
- return bean;
- }
- if(AopUtils.isAopProxy(bean)) { //③ 如果當前物件是AOP代理物件,直接注入
- ((BeanSelfAware) bean).setSelf(bean);
- } else {
- //④ 如果當前物件不是AOP代理,則通過context.getBean(beanName)獲取代理物件並注入
- //此種方式不適合解決prototype Bean的代理物件注入
- ((BeanSelfAware)bean).setSelf(context.getBean(beanName));
- }
- return bean;
- }
- public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
- return bean;
- }
- }
5、總結
縱觀其上:
【3.1 通過ThreadLocal暴露Aop代理物件】適合解決所有場景(不管是singleton Bean還是prototype Bean)的AOP代理獲取問題(即能解決目標物件的自我呼叫問題);
【3.2 通過初始化方法在目標物件中注入代理物件】 和【3.4 改進版的InjectBeanSelfProcessor的解決方案】能解決普通(無迴圈依賴)的AOP代理物件注入問題,而且也能解決【3.3】中提到的迴圈依賴(應該是singleton之間的迴圈依賴)造成的目標物件無法注入AOP代理物件問題,但該解決方案不適合解決迴圈依賴中包含prototype Bean的自我呼叫問題;
【3.3 通過BeanPostProcessor 在目標物件中注入代理物件】:只能解決 普通(無迴圈依賴)的 的Bean注入AOP代理,無法解決迴圈依賴的AOP代理物件注入問題,即無法解決目標物件的自我呼叫問題。
jingnianshilongnian 寫道spring允許的迴圈依賴(只考慮單例和原型Bean):
A----B
B----A
只有在A和B都不為原型是允許的,即如果A和B都是prototype則會報錯(無法進行原型Bean的迴圈依賴)。
A(單例)---B(單例) 或 A(原型)---B(單例) 這是可以的,但 A(原型)---B(原型)或 A(原型)---B(單例Lazy)【且context.getBean("A")】時 這是不允許的。
一、A(原型)---B(原型) A(原型)---B(單例Lazy)【且context.getBean("A")】 會報:
Error creating bean with name 'BServiceImpl': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.sishuok.issue.AService com.sishuok.issue.impl.BServiceImpl.aService; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'AServiceImpl': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.sishuok.issue.BService com.sishuok.issue.impl.AServiceImpl.bService; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'BServiceImpl': Requested bean is currently in creation: [color=red]Is there an unresolvable circular reference[/color]?
二、A(原型)---B(單例) 和 A(單例)---B(單例)
這種方式 使用我的 【3.3 通過BeanPostProcessor 在目標物件中注入代理物件】 是沒有問題的。
因此【 3.4 改進版的InjectBeanSelfProcessor的解決方案 】 可以作為最後的解決方案
相關推薦
spring本類中呼叫事務方法不生效
前段時間寫程式碼時,呼叫本類中@Transactional(propagation = Propagation.REQUIRES_NEW) 的方法,事務不生效不回滾,記錄於此 本文轉自http://blog.csdn.net/dapinxiaohuo/article/de
spring 本類中方法呼叫另外一個方法事務不生效
1、在spring配置檔案中新增 <aop:aspectj-autoproxy expose-proxy="true"/&g
【Spring類的自呼叫事務失效問題】(某個類中無事務方法呼叫有事務方法)
咋麼先來看一類 public class Demo{ @Transactional public void insert() { /* … */ } public void query() { &
grails專案中的java類中呼叫service方法
最近寫java多執行緒時需要呼叫service,嘗試後唯一下方法可行: 以task.java裡呼叫smsStackService內方法testSingleMt()方法為例 1).在resources.groovy內 beans = { myBean(co
非Activity類中呼叫Activity方法
專案中遇到一個問題,要在非Activity的類中呼叫Activity的setResult()方法,通過好友和CSDN網友的幫助終於解決了,感謝! 解決地址:http://bbs.csdn.net/topics/390911138 我的實現方式: 在類中定義Context,
SSM中,事務控制不生效問題
今天在使用ssm事務時,發現方法報錯,事務沒有回滾,弄了半天,終於解決,下面記錄一下 事務有兩種處理方式: 1、程式設計式事務,即使用配置檔案 2、宣告式事務,即使用註解
Spring service本類中方法呼叫另一個方法事務不生效問題
前些日子一朋友在需要在目標物件中進行自我呼叫,且需要實施相應的事務定義,且網上的一種通過BeanPostProcessor的解決方案是存在問題的。因此專門寫此篇帖子分析why。 1、預備知識 使用AOP 代理後的方法呼叫執行流程,如圖所示 也就是說我們首先呼叫的是
關於在Spring 中方法內部呼叫自身方法事務 REQUIRE_NEW 不生效的解釋
開發十年,就只剩下這套架構體系了! >>>
spring 同一個類中方法呼叫 註解不起作用
spring 同一個類中方法呼叫 註解不起作用 需要新增 AopContext.currentProxy() 這樣
Junit單元測試+aop+spring+執行緒池,在進行Junit測試時切面中執行緒池內呼叫的方法不執行
一、問題背景: 寫了一個切面,指向某service包下的所有類及方法,當該service包下方法被呼叫時切面執行,切面中用了執行緒池ExecutorService pool = Executors.newFixedThreadPool(5);執行緒池內呼叫了dao層的方法。 二、問題描述:單
如何使用spring容器在一個類中呼叫另一個類的方法。
學了spring容器之後,最基本的就是這個在一個類中呼叫另一個類中的方法了。本文講述的就是如何使用spring容器從一個類呼叫另一個類的方法。首先,我們先新建一個Student類,在類中建立一個int型別的方法。如圖: 第一個類Student就完成了,那麼我們
Spring 從同一個類中的某個方法呼叫另一個有註解(@Transactional)的方法時,失效的解決方案
考慮有下面這麼一個類 public class Foo { @Transactional public void bar() { /* … */ } public void
在同一個類中呼叫另一個方法沒有觸發 Spring AOP 的問題
起因 考慮如下一個例子: @Target(value = {ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MyMonitor { } @Component @Aspect pub
單元測試被spring管理的類(如service實現類)中的私有方法
目標 ServiceImpl類中有一個私有方法MethodA,而ServiceImpl是被spring管理的,該類中有一些用@Autowired注入的依賴項,因此不能直接new來建立它的例項,需要通過spring來建立它的例項,那麼該怎麼測試該私有方法呢? 方案 測試類 pu
在同一個類中,一個方法呼叫另外一個有註解(比如@Async,@Transational)的方法,註解失效的原因和解決方法
在同一個類中,一個方法呼叫另外一個有註解(比如@Async,@Transational)的方法,註解是不會生效的。 比如,下面程式碼例子中,有兩方法,一個有@Transational註解,一個沒有。如果呼叫了有註解的addPerson()方法,會啟動一個Transaction;如果呼叫updatePerso
在子類中呼叫父類的方法
在子類中呼叫父類的方法 class Vehicle: Country='China' def __init__(self,name,speed,load,power): self.name=name self.speed=speed se
靜態程式碼塊、構造器、靜態方法在類中呼叫的先後順序關係
package test; /* * 當一個類中,有靜態方法、靜態程式碼塊、構造方法時; * 如果 初始化該類,new一個物件,則會 * 1,先呼叫靜態程式碼塊; * 2. 再呼叫構造器,初始化該物件; * 3.靜態方法需要被主動顯式呼叫; * 可通過以下簡單
springboot 專案普通類中呼叫mapper或service介面(utils包下的類封裝方法時呼叫mapper或service介面)
1、該類使用@Component註解 2、新增一個你需要用到的類的靜態欄位 3、建立一個初始化方法,貼上@PostConstruct 標籤,用於注入bean 4、建立方法呼叫mapper或service介面 5、最後直接在普通類中呼叫即可 //1 @Compon
Spring專案普通工具類中呼叫service介面
WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext(); IControlService controlService= (IControlService) context
JAVA基礎 -從父類的引用如何呼叫子類中獨有的方法
首先放上原始檔 父類 Animal.java package com.imooc; public class Animal { public void eat(){