Spring - @Transactional - 在后台发生了啥?

Posted

技术标签:

【中文标题】Spring - @Transactional - 在后台发生了啥?【英文标题】:Spring - @Transactional - What happens in background?Spring - @Transactional - 在后台发生了什么? 【发布时间】:2010-11-09 02:23:55 【问题描述】:

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

但是,我有以下疑问:

    听说 Spring 创建了一个代理类?有人可以更深入解释这一点。 该代理类中实际存在什么?实际课程会发生什么?以及如何查看 Spring 创建的代理类 我还在 Spring 文档中读到:

注意:由于此机制基于代理,只有通过代理传入的“外部”方法调用会被拦截。这意味着“自调用”,即目标对象中的一个方法调用目标对象的某个其他方法,即使被调用的方法标有@Transactional!,在运行时也不会导致实际事务>

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

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

【问题讨论】:

相关讨论在这里:***.com/questions/3120143/… 【参考方案1】:

这是一个很大的话题。 Spring 参考文档用多个章节介绍它。我建议阅读 Aspect-Oriented Programming 和 Transactions 上的内容,因为 Spring 的声明式事务支持在其基础上使用 AOP。

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

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

正如您所观察到的,代理机制仅在调用来自某个外部对象时才起作用。当您在对象中进行内部调用时,您实际上是通过绕过代理的this 引用进行调用。但是,有一些方法可以解决这个问题。我在this forum post 中解释了一种方法,其中我使用BeanFactoryPostProcessor 在运行时将代理实例注入“自引用”类。我将此引用保存到名为me 的成员变量。然后,如果我需要进行需要更改线程事务状态的内部调用,我会通过代理(例如me.someMethod())直接调用。论坛帖子解释得更详细。

请注意,BeanFactoryPostProcessor 代码现在会有所不同,因为它是在 Spring 1.x 时间范围内写回的。但希望它能给你一个想法。我有一个更新版本,我可能会提供。

【讨论】:

>> 代理在运行时大部分是不可见的哦!!我很想看到他们:) 休息..您的回答非常全面。这是你第二次帮助我..感谢所有的帮助。 没问题。如果您使用调试器单步执行,您可以看到代理代码。这可能是最简单的方法。没有魔法;它们只是 Spring 包中的类。 如果具有@Transaction 注释的方法正在实现一个接口,那么spring 将使用动态代理API 来注入事务化并且 使用代理。在任何情况下,我都希望我的事务化类实现接口。 我也找到了“我”方案(使用显式接线来完成它,因为它适合我的想法),但我认为如果你这样做,你可能会更好关闭重构,这样您就不必这样做了。但是,是的,这有时可能会很尴尬! 2019: 由于这个答案已经过时,所引用的论坛帖子不再可用,这将描述 您必须在对象绕过代理,使用BeanFactoryPostProcessor。但是,这个答案中有一个(在我看来)非常相似的方法:***.com/a/11277899/3667003 ...以及整个线程中的进一步解决方案。【参考方案2】:

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

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

至于“外部方法”的事情,如果你的 bean 调用它自己的方法之一,那么它不会通过代理这样做。请记住,Spring 将您的 bean 包装在代理中,您的 bean 对此一无所知。只有来自“外部”bean 的调用通过代理。

这有帮助吗?

【讨论】:

>记住,Spring 将你的 bean 包装在代理中,你的 bean 对此一无所知这说明了一切。多么棒的答案。感谢您的帮助。 很好的解释,用于代理和拦截器。现在我了解spring实现了一个代理对象来拦截对目标bean的调用。谢谢! 我认为您正在尝试描述 Spring 文档的这张图片,看到这张图片对我有很大帮助:docs.spring.io/spring/docs/4.2.x/spring-framework-reference/… 派对迟到了 - These proxy objects are instances of classes that are auto-generated at runtime. 这到底是什么时候发生的。当应用程序加载到 JVM 或第一次调用 bean(应该由代理包装)时。【参考方案3】:

作为一个视觉人,我喜欢用代理模式的序列图来衡量。如果你不知道怎么读箭头,我读第一个是这样的:Client执行Proxy.method()

    客户端从他的角度调用目标上的方法,并被代理静默拦截 如果定义了 before 方面,代理将执行它 然后,执行实际的方法(目标) 后返回和后投掷是可选方面,它们是 在方法返回后和/或如果方法抛出一个 例外 之后,代理执行 after aspect(如果已定义) 代理最终返回调用客户端

(我被允许发布照片,条件是我提到了它的来源。作者:Noel Vaes,网站:https://www.noelvaes.eu)

【讨论】:

【参考方案4】:

最简单的答案是:

在您声明@Transactional 的任何方法上,事务的边界都会在方法完成时开始和边界结束。

如果您使用 JPA 调用,则 所有提交都在此事务边界内

假设您正在保存实体 1、实体 2 和实体 3。现在在保存 entity3 时,发生异常,然后由于 enitiy1 和 entity2 进入同一事务,因此 entity1 和 entity2 将与 entity3 一起回滚

交易:

    entity1.save entity2.save entity3.save

任何异常都将导致所有 JPA 事务与 DB 的回滚。内部 JPA 事务由 Spring 使用。

【讨论】:

“A̶n̶y̶ 异常将导致与 DB 的所有 JPA 事务回滚。” 注意 只有 RuntimeException 会导致回滚。检查异常,不会导致回滚。 dzone.com/articles/….【参考方案5】:

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

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

【讨论】:

真正复杂问题的真正答案。另外,我就是喜欢你的博客。不仅是他的,而且是全部。【参考方案6】:

可能已经晚了,但我遇到了一些可以很好地解释您对代理的担忧(只有通过代理传入的“外部”方法调用会被拦截)。

例如,你有一个看起来像这样的类

@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");    
  

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

@Component
@Aspect
public class CrossCuttingConcern 

    @Before("execution(* com.intertech.CoreBusinessSubordinate.*(..))")
    public void doCrossCutStuff()
        System.out.println("Doing the cross cutting concern now");
    

当你像这样执行它时:

 @Service
public class CoreBusinessKickOff 

    @Autowired
    CoreBusinessSubordinate subordinate;

    // getter/setters

    public void kickOff() 
       System.out.println("I do something big");
       subordinate.doSomethingBig();
       subordinate.doSomethingSmall(4);
   

以上给定代码调用 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

但是当您将代码更改为

@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);

你看,这个方法内部调用了另一个方法,所以它不会被拦截,输出看起来像这样:

I do something big
Doing the cross cutting concern now
I did something small
I also do something small but with an int

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

public void doSomethingBig() 
    System.out.println("I did something small");
    //doSomethingSmall(4);
    ((CoreBusinessSubordinate) AopContext.currentProxy()).doSomethingSmall(4);

代码 sn-ps 取自: https://www.intertech.com/Blog/secrets-of-the-spring-aop-proxy/

【讨论】:

以上是关于Spring - @Transactional - 在后台发生了啥?的主要内容,如果未能解决你的问题,请参考以下文章

Spring @Transactional 和 Spring @Lock 注解有啥关系?

Spring @Transactional 使用

Spring @Transactional 只读传播

Spring @Transactional 属性是不是适用于私有方法?

如何在 Spring Boot 中使用 @Transactional 注解

每日必读DZone Spring:Spring @Transactional 是如何真正工作的?