問題概述
在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方法呼叫另一個需要被攔截的方法,將不會執行攔截邏輯,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/552182.html
標籤:Java
上一篇:用go設計開發一個自己的輕量級登錄庫/框架吧(專案維護篇)
下一篇:返回列表
