ChatGPT解决这个技术问题 Extra ChatGPT

Spring - @Transactional - 在后台发生了什么?

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

但是,我有以下疑问:

听说Spring创建了一个代理类?有人可以更深入地解释这一点。该代理类中实际存在什么?实际课程会发生什么?我怎样才能看到 Spring 创建的代理类,我也在 Spring 文档中读到:

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

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

为什么只有外部方法调用将在事务下而不是自调用方法?

相关讨论在这里:stackoverflow.com/questions/3120143/…

M
M. Deinum

这是一个很大的话题。 Spring 参考文档用多个章节介绍它。我建议阅读 Aspect-Oriented ProgrammingTransactions 上的内容,因为 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 。但是,此答案中描述了一种(在我看来)非常相似的方法:stackoverflow.com/a/11277899/3667003 ...以及整个线程中的进一步解决方案。
C
CodeSlave

当 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(应该由代理包装)时。
这是关于 Spring @Transactional 注释如何工作的一个很好的解释!!!
L
Lii

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

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

https://i.imgur.com/Ye9pEVI.jpg


C
CodeSlave

最简单的答案是:

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

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

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

交易 :

实体1.保存实体2.保存实体3.保存

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


“A̶n̶y̶ 异常将导致与 DB 的所有 JPA 事务回滚。”注意 只有 RuntimeException 会导致回滚。检查异常,不会导致回滚。
M
Marco Behler

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

如需全面、实用的说明,您可能需要查看此 Spring @Transactional In-Depth 指南,该指南尽最大努力用大约 4000 个简单的单词介绍事务管理,并提供大量代码示例。


一个真正复杂的问题的真正答案。另外,我就是喜欢你的博客。不是他的唯一,而是全部。
D
Danyal Sandeelo

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

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

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

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