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 属性是不是适用于私有方法?