我想知道当您使用 @Transactional
注释方法时实际发生了什么?当然,我知道 Spring 会将该方法包装在 Transaction 中。
但是,我有以下疑问:
听说Spring创建了一个代理类?有人可以更深入地解释这一点。该代理类中实际存在什么?实际课程会发生什么?我怎样才能看到 Spring 创建的代理类,我也在 Spring 文档中读到:
注意:由于这种机制是基于代理的,只有通过代理传入的“外部”方法调用才会被拦截。这意味着“自调用”,即目标对象中的一个方法调用目标对象的其他方法,即使调用的方法标有@Transactional,也不会在运行时导致实际事务!
来源:http://static.springsource.org/spring/docs/2.0.x/reference/transaction.html
为什么只有外部方法调用将在事务下而不是自调用方法?
这是一个很大的话题。 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 加载您的 bean 定义并配置为查找 @Transactional
注释时,它将围绕您的实际 bean 创建这些 代理对象。这些代理对象是在运行时自动生成的类的实例。调用方法时这些代理对象的默认行为只是在“目标”bean(即您的bean)上调用相同的方法。
然而,代理也可以提供拦截器,当这些拦截器存在时,代理将在调用目标 bean 的方法之前调用这些拦截器。对于使用 @Transactional
注释的目标 bean,Spring 将创建一个 TransactionInterceptor
,并将其传递给生成的代理对象。因此,当您从客户端代码调用该方法时,您是在调用代理对象上的方法,它首先调用 TransactionInterceptor
(它开始一个事务),然后再调用目标 bean 上的方法。调用完成后,TransactionInterceptor
提交/回滚事务。它对客户端代码是透明的。
至于“外部方法”的事情,如果你的 bean 调用它自己的方法之一,那么它不会通过代理这样做。请记住,Spring 将您的 bean 包装在代理中,您的 bean 对此一无所知。只有来自“外部”你的 bean 的调用通过代理。
这有帮助吗?
These proxy objects are instances of classes that are auto-generated at runtime.
这到底是什么时候发生的。当应用程序加载到 JVM 或第一次调用 bean(应该由代理包装)时。
作为一个视觉的人,我喜欢用代理模式的序列图来衡量。如果您不知道如何阅读箭头,我会这样阅读第一个:Client
执行 Proxy.method()
。
客户端从他的角度调用目标上的一个方法,并被代理静默拦截如果定义了一个before aspect,代理将执行它然后,执行实际的方法(目标)后返回和后抛出是可选的在方法返回和/或方法抛出异常之后执行的方面之后,代理执行后方面(如果已定义) 最后代理返回到调用客户端
https://i.imgur.com/Ye9pEVI.jpg
最简单的答案是:
在您声明 @Transactional
的任何方法上,事务的边界都会在方法完成时开始和边界结束。
如果您使用 JPA 调用,则所有提交都在此事务边界内。
假设您正在保存实体 1、实体 2 和实体 3。现在在保存实体 3 时发生异常,然后由于实体 1 和实体 2 进入同一事务,因此实体 1 和实体 2 将与实体 3 一起回滚。
交易 :
实体1.保存实体2.保存实体3.保存
任何异常都将导致使用 DB 回滚所有 JPA 事务。Spring 内部使用 JPA 事务。
所有现有的答案都是正确的,但我觉得不能只给出这个复杂的话题。
如需全面、实用的说明,您可能需要查看此 Spring @Transactional In-Depth 指南,该指南尽最大努力用大约 4000 个简单的单词介绍事务管理,并提供大量代码示例。
可能已经晚了,但我遇到了一些东西,它很好地解释了您对代理的担忧(只有通过代理传入的“外部”方法调用才会被拦截)。
例如,您有一个看起来像这样的类
@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/
BeanFactoryPostProcessor
。但是,此答案中描述了一种(在我看来)非常相似的方法:stackoverflow.com/a/11277899/3667003 ...以及整个线程中的进一步解决方案。