春天 - @Transactional - 后台会发生什么?

pea*_*kit 313 java spring transactional spring-jdbc spring-aop

我想知道当你用方法注释方法时实际发生了@Transactional什么?当然,我知道Spring会将该方法包装在Transaction中.

但是,我有以下疑问:

  1. 我听说Spring创建了一个代理类?有人可以更深入地解释这一点.实际存在于该代理类中的是什么?实际班级会发生什么?我怎样才能看到Spring创建的代理类
  2. 我还在Spring文档中读到:

注意:由于此机制基于代理,因此只会拦截通过代理进入的"外部"方法调用.这意味着'自调用',即目标对象中调用目标对象的其他方法的方法,即使被调用的方法被标记,也不会在运行时导致实际的事务@Transactional!

资料来源:http://static.springsource.org/spring/docs/2.0.x/reference/transaction.html

为什么只有外部方法调用才会在Transaction下而不是自调用方法?

Rob*_*b H 245

这是一个很大的话题.Spring参考文档专门介绍了多个章节.我推荐阅读面向方面编程事务的方法,因为Spring的声明式事务支持在其基础上使用AOP.

但是在非常高的层次上,Spring为类本身或成员声明@Transactional的类创建代理.代理在运行时几乎不可见.它为Spring提供了一种方法,可以在方法调用到被代理对象之前,之后或周围注入行为.事务管理只是可以挂钩的行为的一个例子.安全检查是另一个.您也可以提供自己的日志,例如日志记录.因此,当您使用@Transactional注释方法时,Spring会动态创建一个代理,该代理实现与您正在注释的类相同的接口.当客户端调用对象时,会拦截调用并通过代理机制注入行为.

顺便说一句,EJB中的事务工作方式类似.

正如您所观察到的那样,代理机制仅在来自某个外部对象的调用时才起作用.当您在对象内进行内部调用时,您实际上是通过绕过代理的" this "引用进行调用.但是,有办法解决这个问题.我在这个论坛帖子中解释了一种方法,其中我使用BeanFactoryPostProcessor在运行时将代理实例注入"自引用"类.我将此引用保存为名为" me " 的成员变量.然后,如果我需要进行需要更改线程事务状态的内部调用,我通过代理指示调用(例如" me.someMethod() ".)论坛帖子更详细地解释.请注意,BeanFactoryPostProcessor代码现在会有所不同,因为它是在Spring 1.x时间帧中写回来的.但希望它能给你一个想法.我有一个我可能提供的更新版本.

  • 没问题.如果您使用调试器单步执行,则可以看到代理代码.这可能是最简单的方法.没有魔力; 它们只是Spring包中的类. (16认同)
  • >>代理在运行时大多是不可见的哦!我好奇地看到他们:)休息..你的答案非常全面.这是你第二次帮助我......感谢所有的帮助. (4认同)
  • **2019:** 由于这个答案已经过时,所引用的论坛帖子不再可用,它描述了 _you 必须在对象内进行内部调用 ** 不** 绕过代理的情况,使用 `BeanFactoryPostProcessor` _ . 但是,在这个答案中描述了一个(在我看来)非常相似的方法:/sf/answers/789452961/ ...以及整个线程中的进一步解决方案。 (2认同)

ska*_*man 185

当Spring加载你的bean定义,并且已经配置为查找@Transactional注释时,它将在你的实际bean周围创建这些代理对象.这些代理对象是在运行时自动生成的类的实例.调用方法时这些代理对象的默认行为只是在"目标"bean(即您的bean)上调用相同的方法.

但是,代理也可以提供拦截器,并且当它们存在时,代理将在调用目标bean的方法之前调用这些拦截器.对于使用@Transactional注释的目标bean,Spring将创建一个TransactionInterceptor,并将其传递给生成的代理对象.因此,当您从客户端代码调用该方法时,您将在代理对象上调用该方法,该方法首先调用TransactionInterceptor(它开始一个事务),然后调用目标bean上的方法.调用完成后,TransactionInterceptor将提交/回滚事务.它对客户端代码透明.

至于"外部方法"的事情,如果你的bean调用它自己的一个方法,那么它不会通过代理这样做.请记住,Spring将bean包装在代理中,您的bean不知道它.只有来自bean外部的调用才能通过代理.

这有帮助吗?

  • >记住,Spring将你的bean包装在代理中,你的bean不知道它**这就说明了一切.真是个好消息.谢谢你的帮助.** (33认同)
  • 对于代理和拦截器的很好的解释。现在我明白了 spring 实现了一个代理对象来拦截对目标 bean 的调用。谢谢你! (2认同)
  • 我认为您正在尝试描述 Spring 文档的这张图片,并且看到这张图片对我很有帮助:https://docs.spring.io/spring/docs/4.2.x/spring-framework-reference/html/images/ TX.png (2认同)

pro*_*kpa 38

作为一个视觉人,我喜欢用代理模式的序列图来衡量.如果您不知道如何阅读箭头,我会读到第一个如下:Client执行Proxy.method().

  1. 客户端从他的角度调用目标上的方法,并由代理静默拦截
  2. 如果定义了before aspect,则代理将执行它
  3. 然后,执行实际方法(目标)
  4. 返回后和抛出后是在方法返回后和/或方法抛出异常时执行的可选方面
  5. 之后,代理执行after方面(如果已定义)
  6. 最后,代理返回到调用客户端

代理模式序列图 (我被允许在我提到它的起源的情况下张贴照片.作者:Noel Vaes,网站:www.noelvaes.eu)


Ros*_*tha 21

最简单的答案是,在声明@Transactional的任何方法中,事务开始的边界和边界在方法完成时结束.

如果您正在使用JPA调用,则所有提交都在此事务边界中.假设您正在保存entity1,entity2和entity3.现在,在保存entity3时会发生异常,因为enitiy1和entity2属于同一个事务,因此entity1和entity2将与entity3一起回滚.

事务:(entity1.save,entity2.save,entity3.save).任何异常都将导致使用DB回滚所有JPA事务.Spring内部使用JPA事务.

  • “A̶n̶y̶异常将导致所有 JPA 事务与 DB 的回滚。” **注意** 只有 RuntimeException 才会导致回滚。检查异常,不会导致回滚。 (6认同)

Dan*_*elo 9

可能已经晚了,但我发现了一些东西,它很好地解释了您与代理相关的问题(只有通过代理传入的“外部”方法调用才会被拦截)。

例如,您有一个如下所示的类

@Component("mySubordinate")
public class CoreBusinessSubordinate {
    
    public void doSomethingBig() {
        System.out.println("I did something small");
    }
    
    public void doSomethingSmall(int x){
        System.out.println("I also do something small but with an int");    
  }
}
Run Code Online (Sandbox Code Playgroud)

你有一个方面,看起来像这样:

@Component
@Aspect
public class CrossCuttingConcern {
    
    @Before("execution(* com.intertech.CoreBusinessSubordinate.*(..))")
    public void doCrossCutStuff(){
        System.out.println("Doing the cross cutting concern now");
    }
}
Run Code Online (Sandbox Code Playgroud)

当你像这样执行它时:

 @Service
public class CoreBusinessKickOff {
    
    @Autowired
    CoreBusinessSubordinate subordinate;
 
    // getter/setters
    
    public void kickOff() {
       System.out.println("I do something big");
       subordinate.doSomethingBig();
       subordinate.doSomethingSmall(4);
   }
Run Code Online (Sandbox Code Playgroud)

}

上面给出的代码调用 kickOff 的结果。

I do something big
Doing the cross cutting concern now
I did something small
Doing the cross cutting concern now
I also do something small but with an int
Run Code Online (Sandbox Code Playgroud)

但是当你将代码更改为

@Component("mySubordinate")
public class CoreBusinessSubordinate {
    
    public void doSomethingBig() {
        System.out.println("I did something small");
        doSomethingSmall(4);
    }
    
    public void doSomethingSmall(int x){
       System.out.println("I also do something small but with an int");    
   }
}


public void kickOff() {
  System.out.println("I do something big");
   subordinate.doSomethingBig();
   //subordinate.doSomethingSmall(4);
}
Run Code Online (Sandbox Code Playgroud)

您会看到,该方法在内部调用另一个方法,因此它不会被拦截,并且输出如下所示:

I do something big
Doing the cross cutting concern now
I did something small
I also do something small but with an int
Run Code Online (Sandbox Code Playgroud)

你可以通过这样做绕过这个

public void doSomethingBig() {
    System.out.println("I did something small");
    //doSomethingSmall(4);
    ((CoreBusinessSubordinate) AopContext.currentProxy()).doSomethingSmall(4);
}
Run Code Online (Sandbox Code Playgroud)

代码片段取自: https: //www.intertech.com/Blog/secrets-of-the-spring-aop-proxy/ 该页面不再存在。


mar*_*rco 5

所有现有答案都是正确的,但我觉得不能只给出这个复杂的话题。

要获得全面、实用的解释,您可能想看看这个Spring @Transactional In-Depth指南,它尽力用大约 4000 个简单的单词来介绍事务管理,并附有大量代码示例。