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

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

但是,我有以下疑问:

  1. 我听说Spring创建了代理类?有人能解释更多的深度代理类中实际驻留的是什么?实际的类发生了什么?以及如何查看Spring创建的代理类
  2. 我还在Spring文档中读到:

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

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

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

255072 次浏览

这是一个很大的话题。Spring参考文档用了多个章节来介绍它。我建议阅读关于面向方面的编程交易的内容,因为Spring的声明性事务支持在其基础上使用AOP。

但在非常高的级别上,Spring为在类本身或成员上声明@Transactional的类创建代理。代理在运行时基本上是不可见的。它为Spring提供了一种方法,可以在方法调用之前、之后或前后向被代理的对象注入行为。事务管理只是可以连接的行为的一个例子。安全检查是另一个问题。您还可以提供自己的日志记录等功能。因此,当你用@Transactional注释一个方法时,Spring动态地创建一个代理,该代理实现了与你所注释的类相同的接口。当客户端对您的对象进行调用时,调用将被拦截,并通过代理机制注入行为。

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

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

注意,BeanFactoryPostProcessor代码现在略有不同,因为它是在Spring 1中编写的。x时间表。但希望它能给你一个概念。我有一个更新的版本,我可能可以提供。

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

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

至于“外部方法”,如果您的bean调用它自己的方法之一,那么它将不会通过代理进行调用。请记住,Spring将bean包装在代理中,您的bean不知道它。只有来自bean“外部”的调用才会通过代理。

这有用吗?

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

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

代理模式序列图 (我被允许发布这张照片,条件是我必须提及照片的来源。作者:Noel vays,网站:https://www.noelvaes.eu)

最简单的答案是:

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

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

假设你要保存entity1、entity2和entity3。现在,当entity3保存为异常发生时,当entity1和entity2进入同一事务时,entity1和entity2将与entity3一起保存为回滚

事务:

  1. entity1.save
  2. entity2.save
  3. entity3.save

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

可能有点晚了,但我发现了一些东西,很好地解释了你对代理的担忧(只有通过代理进入的“外部”方法调用才会被拦截)。

例如,你有一个这样的类

@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/ < / p >

现有的答案都是正确的,但我觉得无法给出这个复杂的话题。

对于一个全面的,实用的解释,你可能想看看这个Spring @事务深度指南,它尽最大努力用大约4000个简单的单词涵盖事务管理,并有很多代码示例。