問題描述:
我們在用Spring框架開發Web項目過程中,經常需要用同一個service中的一個方法調用另一個方法,如果此時調用方沒有添加事務注解@Transactional,而在被調用方添加事務注解@Transactional,當被調用方法中出現異常,這時候會發現事務並沒有回滾,事務注解@Transactional沒有起作用。
分析原因:
我們知道Spring中事務管理是使用AOP代理技術實現的,目標對象自身並沒有事務管理功能的,而是通過代理對象動態增強功能對事務進行增強的。因此當我們在同一個service類中通過一個方法調用另一個方法時,是通過目標對象this對象調用的,目標對象自身並沒有事務管理功能,因此事務不能生效。
下面我們用代碼演示下:
1 public class UserService{ 2 ... 3 public User getUserByName(String name) { 4 return userDao.getUserByName(name); 5 } 6 ...
如果配置了事務, 就相當於又創建了一個類:
1 public class UserServiceProxy extends UserService{ 2 private UserService userService; 3 ... 4 public User getUserByName(String name){ 5 User user = null; 6 try{ 7 // 在這里開啟事務 8 user = userService.getUserByName(name); 9 // 在這里提交事務 10 } 11 catch(Exception e){ 12 // 在這里回滾事務 13 14 // 這塊應該需要向外拋異常, 否則我們就無法獲取異常信息了. 15 // 至於方法聲明沒有添加異常聲明, 是因為覆寫方法, 異常必須和父類聲明的異常"兼容". 16 // 這塊應該是利用的java虛擬機並不區分普通異常和運行時異常的特點. 17 throw e; 18 } 19 return user; 20 } 21 ... 22 }
1 @Autowired 2 private UserService userService; // 這里spring注入的實際上是UserServiceProxy的對象 3 4 private void test(){ 5 // 由於userService是UserServiceProxy的對象, 所以擁有了事務管理的能力 6 userService.getUserByName("aa"); 7 }
Spring事務失效的其他原因
通過對Spring事務代理模式的分析,我們不難發現Spring事務失效的原因有以下幾種情況:
1.private、static、final的使用
解決方法:不在類和方法上使用此類關鍵字
2.通過this.xxx(調用當前類的方法)
使用xml配置方式暴露代理對象.然后在service中通過代理對象AopContext.currentProxy()去調用方法。
xml配置
<aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/>
service調用
1 @Service 2 public class HelloWorldServiceImpl implements HelloWorldService { 3 @Autowired 4 private BlogRepository blogRepository; 5 6 @Override 7 public void a(BlogEntity blogEntity) throws Exception { 8 ((HelloWorldService) AopContext.currentProxy()).b(blogEntity); 9 } 10 11 @Transactional(rollbackFor = Exception.class) 12 @Override 13 public void b(BlogEntity blogEntity) throws Exception { 14 blogRepository.save(blogEntity); 15 throw new Exception("錯誤"); 16 } 17 }
3.使用默認的事務處理方式
spring的事務默認是對RuntimeException進行回滾,而不繼承RuntimeException的不回滾。因為在java的設計中,它認為不繼承RuntimeException的異常是”checkException”或普通異常,如IOException,這些異常在java語法中是要求強制處理的。對於這些普通異常,spring默認它們都已經處理,所以默認不回滾。可以添加rollbackfor=Exception.class來表示所有的Exception都回滾。
4.線程Thread中聲明式事務不起作用
1 @Override 2 public void run() { 3 DefaultTransactionDefinition def = new DefaultTransactionDefinition(); 4 def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); 5 PlatformTransactionManager txManager = ContextLoader.getCurrentWebApplicationContext().getBean(PlatformTransactionManager.class); 6 TransactionStatus status = txManager.getTransaction(def); 7 try { 8 testDao.save(entity); 9 txManager.commit(status); // 提交事務 10 } catch (Exception e) { 11 System.out.println("異常信息:" + e.toString()); 12 txManager.rollback(status); // 回滾事務 13 } 14 }
從上面代碼可以看出,我們的解決方案是使用了編程式事務。
5.配置的事務與掃描的service不在同一個容器
在spring-framework-reference.pdf文檔中有這樣一段話:
<tx:annotation-driven/> only looks for @Transactional on beans in the same application context it is defined in. This means that, if you put <tx:annotation-driven/> in a WebApplicationContext for a DispatcherServlet, it only checks for @Transactional beans in your controllers, and not your services.
這句話的意思是,<tx:annoation-driven/>只會查找和它在相同的應用上下文件中定義的bean上面的@Transactional注解,如果你把它放在Dispatcher的應用上下文中,它只檢查控制器上的@Transactional注解,而不是你services上的@Transactional注解。
如果將事務配置定義在Spring MVC的應用上下文(*-servlet.xml)中,在Controller上的@Transactional注解是可以起作用的。而services上的@Transactional注解將不起作用。
詳情可見:https://www.cnblogs.com/xiaojiesir/p/11058541.html
6.方法配置的事務傳播行為有問題
被調用方法的事務傳播行為設置為PROPAGATION_REQUIRES_NEW,導致產生兩個獨立的事務,外圍方法拋出異常只回滾和外圍方法同一事務的方法。