403

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

但是,我有以下疑问:

  1. 我听说 Spring 创建了一个代理类有人可以更深入地解释这一点。该代理类中实际存在什么?实际课程会发生什么?以及如何查看 Spring 创建的代理类
  2. 我还在 Spring 文档中读到:

注意:由于这种机制是基于代理的,只有通过代理传入的“外部”方法调用才会被拦截。这意味着“自调用”,即目标对象中的一个方法调用目标对象的某个其他方法,即使调用的方法标有@Transactional

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

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

4

6 回答 6

294

这是一个很大的话题。Spring 参考文档用多个章节介绍它。我推荐阅读Aspect-Oriented ProgrammingTransactions上的文章,因为 Spring 的声明式事务支持在其基础上使用 AOP。

@Transactional但是在非常高的层次上,Spring 为在类本身或成员上声明的类创建代理。代理在运行时大部分是不可见的。它为 Spring 提供了一种在方法调用之前、之后或周围将行为注入被代理的对象的方法。事务管理只是可以挂钩的行为的一个例子。安全检查是另一个例子。你也可以提供你自己的,比如日志记录。因此,当您使用 注释方法时@Transactional,Spring 会动态创建一个代理,该代理实现与您正在注释的类相同的接口。当客户端调用你的对象时,调用会被拦截并通过代理机制注入行为。

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

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

请注意,现在BeanFactoryPostProcessor代码会有所不同,因为它是在 Spring 1.x 时间范围内写回的。但希望它能给你一个想法。我有一个更新版本,我可能会提供。

于 2009-07-08T16:50:34.223 回答
221

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

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

至于“外部方法”的事情,如果你的 bean 调用它自己的方法之一,那么它不会通过代理这样做。请记住,Spring 将您的 bean 包装在代理中,您的 bean 对此一无所知。只有来自“外部”你的 bean 的调用通过代理。

这有帮助吗?

于 2009-07-08T16:59:09.683 回答
58

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

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

代理模式序列图 (我被允许发布照片,条件是我提到了它的来源。作者:Noel Vaes,网站:https ://www.noelvaes.eu )

于 2016-10-21T09:40:48.420 回答
48

最简单的答案是:

在您声明@Transactional事务边界的任何方法上,当方法完成时边界结束。

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

假设您正在保存实体 1、实体 2 和实体 3。现在在保存 entity3 时发生异常,然后由于 enitiy1 和 entity2 出现在同一个事务中,因此 entity1 和 entity2 将与 entity3一起回滚。

交易 :

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

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

于 2018-01-30T09:14:44.073 回答
7

所有现有的答案都是正确的,但我觉得不能只给出这个复杂的话题。

要获得全面、实用的解释,您可能想看看这个Spring @Transactional In-Depth指南,它尽最大努力用大约 4000 个简单的单词介绍事务管理,并附有大量代码示例。

于 2020-03-03T15:09:22.280 回答
5

可能已经晚了,但我遇到了一些东西,它很好地解释了您对代理的担忧(只有通过代理传入的“外部”方法调用才会被拦截)。

例如,您有一个看起来像这样的类

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

于 2019-12-18T14:42:13.583 回答