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


334

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

但是,我有以下疑问:

  1. 听说Spring创建了代理类?有人可以更深入地解释这一点。该代理类中实际包含什么?实际班级会怎样?我怎么能看到Spring创建的代理类
  2. 我还在Spring文档中读到:

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

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

为什么只有外部方法调用属于事务处理而不是自调用方法?


2
相关的讨论是在这里:stackoverflow.com/questions/3120143/...
dma_k

Answers:


255

这是一个大话题。Spring参考文档对此进行了专门的介绍。我建议阅读有关面向方面的编程事务的内容,因为Spring的声明性事务支持使用AOP作为基础。

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

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

如您所见,通过代理机制,仅当调用来自某个外部对象时才起作用。在对象内进行内部调用时,实际上是通过“ this ”引用进行调用,引用绕过了代理。但是,有一些方法可以解决该问题。我在此论坛帖子中解释了一种方法,其中使用BeanFactoryPostProcessor在运行时将代理实例注入“自引用”类中。我将此引用保存到一个名为“ me ” 的成员变量。然后,如果需要进行需要更改线程事务状态的内部调用,则可以通过代理将调用定向(例如“ me.someMethod()“。)论坛帖子中有更详细的解释。请注意,BeanFactoryPostProcessor代码现在有所不同,因为它是在Spring 1.x的时间框架中写的。但是希望它能给您一个想法。我有一个更新的版本,我可能可以提供。


4
>>代理在运行时几乎不可见哦!我很好奇看到他们:)休息..您的回答很全面。这是您第二次在帮助我。谢谢您的帮助。
peakit

17
没问题。如果您逐步调试,则可以看到代理代码。那可能是最简单的方法。没有魔法;它们只是Spring包中的类。
罗布H

并且如果具有@Transaction批注的方法正在实现接口,则spring将使用动态代理API注入事务处理,而不使用代理。无论如何,我都希望我的事务化类实现接口。
Michael Wiles

1
我也找到了“我”方案(使用显式接线按照我的想法进行操作),但是我认为,如果按照这种方式进行操作,则最好进行重构,这样就不会不得不。但是,是的,有时可能会很尴尬!
多纳研究员

2
2019: 由于此答案越来越老,因此不再提供所引用的论坛帖子,该帖子描述了当您必须使用而不绕过代理进行对象内部调用时的情况BeanFactoryPostProcessor 。但是,在这个答案中有(我认为)非常相似的方法: stackoverflow.com/a/11277899/3667003 ...以及整个线程中的其他解决方案。
Z3d4s

195

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

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

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

有帮助吗?


36
>记住,Spring将您的bean包装在代理中,您的bean不了解它。真是个好答案。感谢您的帮助。
peakit

很好的解释,用于代理和拦截器。现在我了解spring实现了一个代理对象来拦截对目标bean的调用。谢谢!
dharag 2013年

我认为您正在尝试描述Spring文档的这张图片,看到这张图片对我有很大帮助:docs.spring.io/spring/docs/4.2.x/spring-framework-reference/…–
WesternGun

44

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

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

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


27

最简单的答案是:

无论在哪种方法上,您都声明@Transactional事务的边界开始,方法完成时边界结束。

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

假设您正在保存entity1,entity2和entity3。现在,在保存entry3 时发生了一个异常,然后当enitiy1和entity2进入同一事务时,entity1和entry2 将与entry3 回滚

交易:

  1. 实体1.保存
  2. 实体2.保存
  3. 实体3.保存

任何异常都将导致所有带有数据库的JPA事务回滚.Spring内部使用JPA事务。


2
“异常会导致与数据库的所有JPA事务回滚。” 注意只有RuntimeException会导致回滚。检查异常,不会导致回滚。
Arjun

2

可能已经晚了,但是我遇到了一些可以很好地解释您与代理相关的问题的信息(只有通过代理进入的“外部”方法调用会被拦截)。

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

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


0

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

要获得全面,实用的解释,您可能需要看一下Spring @Transactional In-Depth指南,该指南力求用大约4000个简单的单词来介绍事务管理,并提供许多代码示例。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.