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

但是,我有以下疑问:

我听说Spring创建了一个代理类?有人能更深入地解释一下吗?代理类中实际驻留的是什么?实际的类发生了什么?以及如何查看Spring创建的代理类 我还在Spring文档中读到:

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

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

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


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

But at a very high level, Spring creates proxies for classes that declare @Transactional on the class itself or on members. The proxy is mostly invisible at runtime. It provides a way for Spring to inject behaviors before, after, or around method calls into the object being proxied. Transaction management is just one example of the behaviors that can be hooked in. Security checks are another. And you can provide your own, too, for things like logging. So when you annotate a method with @Transactional, Spring dynamically creates a proxy that implements the same interface(s) as the class you're annotating. And when clients make calls into your object, the calls are intercepted and the behaviors injected via the proxy mechanism.

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

As you observed, through, the proxy mechanism only works when calls come in from some external object. When you make an internal call within the object, you're really making a call through the this reference, which bypasses the proxy. There are ways of working around that problem, however. I explain one approach in this forum post in which I use a BeanFactoryPostProcessor to inject an instance of the proxy into "self-referencing" classes at runtime. I save this reference to a member variable called me. Then if I need to make internal calls that require a change in the transaction status of the thread, I direct the call through the proxy (e.g. me.someMethod().) The forum post explains in more detail.

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


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

However, the proxies can also be supplied with interceptors, and when present these interceptors will be invoked by the proxy before it invokes your target bean's method. For target beans annotated with @Transactional, Spring will create a TransactionInterceptor, and pass it to the generated proxy object. So when you call the method from client code, you're calling the method on the proxy object, which first invokes the TransactionInterceptor (which begins a transaction), which in turn invokes the method on your target bean. When the invocation finishes, the TransactionInterceptor commits/rolls back the transaction. It's transparent to the client code.

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

这有用吗?


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

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

(我被允许发布这张照片,条件是我必须提及照片的来源。作者:诺埃尔·维斯,网址:https://www.noelvaes.eu)


最简单的答案是:

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

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

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

事务:

entity1.save entity2.save 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/


所有现有的答案都是正确的,但我觉得不能公正地对待这个复杂的话题。

要获得全面、实用的解释,您可能想要看看Spring @Transactional深度指南,它尽量用大约4000个简单的单词介绍事务管理,并提供了大量的代码示例。