问题概述
在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方法调用另一个需要被拦截的方法,将不会执行拦截逻辑。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |