我想知道用@Transactional
注释方法时实际发生了什么?
当然,我知道Spring会将该方法包装在Transaction中。
但是,我有以下疑问:
注意:由于此机制基于代理,因此只会拦截通过代理进入的“外部”方法调用。这意味着'自调用',即目标对象中调用目标对象的其他方法的方法,即使被调用的方法标有
@Transactional
,也不会在运行时导致实际的事务! / em>的
来源:http://static.springsource.org/spring/docs/2.0.x/reference/transaction.html
为什么只有外部方法调用才会在Transaction下而不是自调用方法?
答案 0 :(得分:221)
这是一个很大的话题。 Spring参考文档专门介绍了多个章节。我建议阅读Aspect-Oriented Programming和Transactions上的内容,因为Spring的声明式事务支持在其基础上使用了AOP。
但是在非常高的层次上,Spring为类本身或成员声明 @Transactional 的类创建代理。代理在运行时几乎不可见。它为Spring提供了一种方法,可以在方法调用到被代理对象之前,之后或周围注入行为。事务管理只是可以挂钩的行为的一个例子。安全检查是另一个。您也可以提供自己的日志,例如日志记录。因此,当您使用 @Transactional 注释方法时,Spring会动态创建一个代理,该代理实现与您正在注释的类相同的接口。当客户端调用您的对象时,会拦截调用并通过代理机制注入行为。
顺便说一句,EJB中的事务工作方式类似。
正如您所观察到的那样,代理机制仅在来自某个外部对象的调用时才起作用。当您在对象中进行内部调用时,您实际上正在通过“此”引用进行调用,该引用会绕过代理。但是,有办法解决这个问题。我在this forum post中解释了一种方法,其中我使用 BeanFactoryPostProcessor 在运行时将代理实例注入“自引用”类。我将此引用保存为名为“ me ”的成员变量。然后,如果我需要进行内部调用,需要更改线程的事务状态,我通过代理指示调用(例如“ me.someMethod()”。)论坛帖子解释更多详情。请注意, BeanFactoryPostProcessor 代码现在会有所不同,因为它是在Spring 1.x时间帧中写回来的。但希望它能给你一个想法。我有一个我可能提供的更新版本。
答案 1 :(得分:171)
当Spring加载你的bean定义,并且已经配置为查找@Transactional注释时,它将在你的实际bean周围创建这些代理对象。这些代理对象是在运行时自动生成的类的实例。调用方法时这些代理对象的默认行为只是在“目标”bean(即您的bean)上调用相同的方法。
但是,代理也可以提供拦截器,并且当它们存在时,代理将在调用目标bean的方法之前调用这些拦截器。对于使用@Transactional注释的目标bean,Spring将创建一个TransactionInterceptor,并将其传递给生成的代理对象。因此,当您从客户端代码调用该方法时,您将在代理对象上调用该方法,该方法首先调用TransactionInterceptor(它开始一个事务),然后调用目标bean上的方法。调用完成后,TransactionInterceptor将提交/回滚事务。它对客户端代码透明。
对于“外部方法”的事情,如果你的bean调用它自己的一个方法,那么它不会通过代理这样做。请记住,Spring将bean包装在代理中,您的bean不知道它。只有来自bean外部的调用才能通过代理。
这有帮助吗?
答案 2 :(得分:34)
作为一个视觉人,我喜欢用代理模式的序列图来衡量。如果您不知道如何阅读箭头,我会按照以下方式阅读第一个箭头:Client
执行Proxy.method()
。
(我被允许在我提到它的起源的情况下张贴照片。作者:Noel Vaes,网站:www.noelvaes.eu)
答案 3 :(得分:12)
最简单的答案是,对于声明@Transactional的任何方法,事务开始的边界开始,边界在方法完成时结束。
如果您正在使用JPA调用,那么所有提交都在此事务边界中。假设您正在保存entity1,entity2和entity3。现在,在保存entity3时会发生异常,因为enitiy1和entity2都在同一个事务中,因此entity1和entity2将与entity3一起回滚。
交易:(entity1.save,entity2.save,entity3.save)。任何异常都将导致使用DB回滚所有JPA事务。 Spring使用内部JPA事务。
答案 4 :(得分:3)
所有现有答案都是正确的,但我觉得不能仅给出这个复杂的话题。
要获得全面,实用的解释,您可能需要看一下这本Spring @Transactional In-Depth指南,该指南将尽最大努力以大约4000个简单的单词介绍事务管理,并提供许多代码示例。
答案 5 :(得分:0)
可能已经晚了,但是我碰到了一些可以解释您与代理有关的问题(只有通过代理进入的“外部”方法调用会被拦截)。</ p>
例如,您有一个看起来像这样的类
@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/