春天 - @Transactional - 后台会发生什么?

时间:2009-07-08 16:02:53

标签: java spring spring-aop spring-jdbc transactional

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

但是,我有以下疑问:

  1. 我听说Spring创建了一个代理类?有人可以用更多深度来解释这一点。 实际存在于该代理类中的是什么?实际班级会发生什么?我怎样才能看到Spring创建的代理类
  2. 我还在Spring文档中读到:
  3.   

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

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

    为什么只有外部方法调用才会在Transaction下而不是自调用方法?

6 个答案:

答案 0 :(得分:221)

这是一个很大的话题。 Spring参考文档专门介绍了多个章节。我建议阅读Aspect-Oriented ProgrammingTransactions上的内容,因为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()

  1. 客户端从他的角度调用目标上的方法,并由代理
  2. 静默拦截
  3. 如果定义了before aspect,代理将执行它
  4. 然后,执行实际方法(目标)
  5. 返回后和投掷后是可选方面 在方法返回后执行和/或方法抛出一个 例外
  6. 之后,代理执行after aspect(如果已定义)
  7. 最后,代理返回调用客户端
  8. Proxy Pattern Sequence Diagram (我被允许在我提到它的起源的情况下张贴照片。作者: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/