背景
在做業務開發時,遇到了一個事務不起作用的問題。大概流程是這樣的,方法内部的方法調用了一個帶事務的方法,失敗後事務沒有復原。查閱資料後,問題得到解決,記錄下來分享給大家。
場景
在一個UserService裡面,一個内部方法callSaveUser調用該service裡面的saveUser方法
@Override
@Transactional(rollbackFor = RuntimeException.class)
public void saveUser(User user) {
userMapper.save(user);
throw new RuntimeException("");
}
/**
* 内部調用儲存使用者方法
*
* @param user
*/
@Override
public void callSaveUser(User user) {
this.saveUser(user);
}
原因
AOP使用的是動态代理的機制,它會給類生成一個代理類,事務的相關操作都在代理類上完成。内部方式使用this調用方式時,使用的是執行個體調用,并沒有通過代理類調用方法,是以會導緻事務失效。
過程如下圖:
首先調用的是AOP代理對象而不是目标對象,首先執行事務切面,事務切面内部通過TransactionInterceptor環繞增強進行事務的增強,即進入目标方法之前開啟事務,退出目标方法時送出/復原事務。
解決辦法
1. 通過初始化方法在目标對象中注入代理對象
注入自身bean
@Autowired
@Lazy
private UserService service;
修改方法調用
/**
* 解決方法一 在bean中将自己注入進來
* @param user
*/
@Override
public void callSaveUser(User user) {
this.service.saveUser(user);
}
總結:隻能解決 普通(無循環依賴)的 的Bean注入AOP代理,無法解決循環依賴的AOP代理對象注入問題,即無法解決目标對象的自我調用問題。
2. 通過ApplicationContext引入bean
注入ApplicationContext
@Autowired
ApplicationContext applicationContext;
修改方法調用
/**
* 解決方法二 通過applicationContext擷取到bean
* @param user
*/
@Override
public void callSaveUser(User user) {
((UserService)applicationContext.getBean("userService")).saveUser(user);
}
總結:能解決普通(無循環依賴)的AOP代理對象注入問題,而且也能解決循環依賴(應該是singleton之間的循環依賴)造成的目标對象無法注入AOP代理對象問題,但該解決方案不适合解決循環依賴中包含prototype Bean的自我調用問題。
3. 通過AopContext擷取目前類的代理類(推薦)
通過AopContext擷取目前類的代理類,直接通過代理類調用方法在引導類上添加@EnableAspectJAutoProxy(exposeProxy=true)注解,然後修改callSaveUser方法
/**
* 解決方法三 通過applicationContext擷取到bean
*
* @param user
*/
@Override
public void invokeInsertUser(User user) {
((UserService) AopContext.currentProxy()).callSaveUser(user);
}
原理:
- 在進入代理對象之後通過AopContext.serCurrentProxy(proxy)暴露目前代理對象ThreadLocal,并儲存上次ThreadLocal綁定的代理對象為oldProxy;
- 接下來我們可以通過 AopContext.currentProxy() 擷取目前代理對象;
- 在退出代理對象之前要重新将ThreadLocal綁定的代理對象設定為上一次的代理對象,即AopContext.serCurrentProxy(oldProxy)。
有些人不喜歡這種方式,說通過ThreadLocal暴露有性能問題,其實這個不需要考慮,因為事務相關的(Session和Connection)内部也是通過SessionHolder和ConnectionHolder暴露ThreadLocal實作的。