問題概述 在Transactional方法中使用this方式調用另一個Transactional方法時,攔截器無法攔截到被調用方法,嚴重時會使事務失效。 類似以下代碼: @Transactional public void insertBlogList(List<Blog> blogList) { ...
問題概述
在Transactional方法中使用this方式調用另一個Transactional方法時,攔截器無法攔截到被調用方法,嚴重時會使事務失效。
類似以下代碼:
@Transactional
public void insertBlogList(List<Blog> blogList) {
for (Blog blog : blogList) {
this.blogMapper.insertBlog(blog);
}
try {
TimeUnit.SECONDS.sleep(15);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Transactional
public void deleteBlogByCondition(BlogSearchParameter parameter) {
List<Blog> blogs = this.blogMapper.selectBlogByParameter(parameter);
for (Blog blog : blogs) {
this.blogMapper.deleteBlog(blog.getId());
}
// 拋出一個RuntimeException
throw new RuntimeException("deleteBlogByCondition拋出一個異常");
}
@Transactional
public void insertAndDeleteBlogList2(List<Blog> blogList, BlogSearchParameter parameter) {
// 插入數據
this.insertBlogList(blogList);
// 刪除數據
try {
this.deleteBlogByCondition(parameter);
} catch (Exception e) {
System.err.printf("Err:%s%n", e.getMessage());
}
System.out.println("繼續插入數據");
// 繼續插入數據
this.insertBlogList(blogList);
}
正常情況下,執行到"繼續插入數據"時會拋出一個"rollback only"的異常,然後事務回滾。
而現在的現象是:
- 三個操作都不會開啟事務,出現異常也不會回滾
- "刪除數據"操作會把符合條件的數據都刪除掉
- "繼續插入數據"操作會再插入數據
原因分析
在EnableTransactionManagement註解mode屬性的文檔中:
The default is AdviceMode.PROXY. Please note that proxy mode allows for interception of calls through the proxy only.
Local calls within the same class cannot get intercepted that way; an Transactional annotation on such a method within
a local call will be ignored since Spring's interceptor does not even kick in for such a runtime scenario.
For a more advanced mode of interception, consider switching this to AdviceMode.ASPECTJ.
大概意思是:mode屬性的預設值是AdviceMode.PROXY,這種方式僅允許通過代理對來調用事務方法,同一個類的本地調用無法被事務切麵攔截。如果要解決這個問題,可以使用AdviceMode.ASPECTJ模式。
其實這個問題的根本原因與spring-tx無關,而是spring-aop的實現方式造成的。
從spring-aop攔截器分析問題原因
在DynamicAdvisedInterceptor和JdkDynamicAopProxy中有一段類似的代碼:
其中target就是原始的業務層Bean對象。
在後續創建ReflectiveMethodInvocation/CglibMethodInvocation時又將此target傳遞了進去:
// JDK
MethodInvocation invocation =
new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
retVal = invocation.proceed();
// Cglib
retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
proceed方法中在攔截器鏈最後會調用目標方法:
public Object proceed() throws Throwable {
// We start with an index of -1 and increment early.
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
}
// 略
}
protected Object invokeJoinpoint() throws Throwable {
// 反射調用目標方法
// 這個target就是原始Bean對象
return AopUtils.invokeJoinpointUsingReflection(this.target, this.method, this.arguments);
}
所以如果在目標方法中使用this方法調用另一個需要被攔截的方法,將不會執行攔截邏輯。