Java Spring - @Transactional - 后台会发生什么?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1099025/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 23:44:09  来源:igfitidea点击:

Spring - @Transactional - What happens in background?

javaspringspring-aopspring-jdbctransactional

提问by peakit

I want to know what actually happens when you annotate a method with @Transactional? Of course, I know that Spring will wrap that method in a Transaction.

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

But, I have the following doubts:

但是,我有以下疑问:

  1. I heard that Spring creates a proxy class? Can someone explain this in more depth. What actually resides in that proxy class? What happens to the actual class? And how can I see Spring's created proxied class
  2. I also read in Spring docs that:
  1. 我听说 Spring 创建了一个代理类?有人可以更深入地解释这一点。什么实际上驻留在该代理类中?实际的班级会发生什么?我怎么能看到 Spring 创建的代理类
  2. 我还在 Spring 文档中读到:

Note: Since this mechanism is based on proxies, only 'external' method calls coming in through the proxy will be intercepted. This means that 'self-invocation', i.e. a method within the target object calling some other method of the target object, won't lead to an actual transaction at runtime even if the invoked method is marked with @Transactional!

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

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

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

Why only external method calls will be under Transaction and not the self-invocation methods?

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

采纳答案by Rob H

This is a big topic. The Spring reference doc devotes multiple chapters to it. I recommend reading the ones on Aspect-Oriented Programmingand Transactions, as Spring's declarative transaction support uses AOP at its foundation.

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

But at a very high level, Spring creates proxies for classes that declare @Transactionalon 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.

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

Transactions in EJB work similarly, by the way.

顺便说一下,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 postin which I use a BeanFactoryPostProcessorto 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. Note that the BeanFactoryPostProcessorcode would be a little different now, as it was written back in the Spring 1.x timeframe. But hopefully it gives you an idea. I have an updated version that I could probably make available.

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

回答by skaffman

When Spring loads your bean definitions, and has been configured to look for @Transactionalannotations, it will create these proxy objectsaround your actual bean. These proxy objects are instances of classes that are auto-generated at runtime. The default behaviour of these proxy objects when a method is invoked is just to invoke the same method on the "target" bean (i.e. your bean).

当 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 TransactionInterceptorcommits/rolls back the transaction. It's transparent to the client code.

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

As for the "external method" thing, if your bean invokes one of its own methods, then it will not be doing so via the proxy. Remember, Spring wraps your bean in the proxy, your bean has no knowledge of it. Only calls from "outside" your bean go through the proxy.

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

Does that help?

这有帮助吗?

回答by progonkpa

As a visual person, I like to weigh in with a sequence diagram of the proxy pattern. If you don't know how to read the arrows, I read the first one like this: Clientexecutes Proxy.method().

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

  1. The client calls a method on the target from his perspective, and is silently intercepted by the proxy
  2. If a before aspect is defined, the proxy will execute it
  3. Then, the actual method (target) is executed
  4. After-returning and after-throwing are optional aspects that are executed after the method returns and/or if the method throws an exception
  5. After that, the proxy executes the after aspect (if defined)
  6. Finally the proxy returns to the calling client
  1. 客户端从他的角度调用目标上的方法,被代理默默拦截
  2. 如果定义了 before 方面,代理将执行它
  3. 然后,执行实际的方法(目标)
  4. After-returning 和 after-throwing 是在方法返回和/或方法抛出异常后执行的可选方面
  5. 之后,代理执行 after 方面(如果已定义)
  6. 最后代理返回给调用客户端

Proxy Pattern Sequence Diagram(I was allowed to post the photo on condition that I mentioned its origins. Author: Noel Vaes, website: www.noelvaes.eu)

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

回答by RoshanKumar Mutha

The simplest answer is:

最简单的答案是:

On whichever method you declare @Transactionalthe boundary of transaction starts and boundary ends when method completes.

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

If you are using JPA call then all commits are with in this transaction boundary.

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

Lets say you are saving entity1, entity2 and entity3. Now while saving entity3 an exception occur, then as enitiy1 and entity2 comes in same transaction so entity1 and entity2 will be rollbackwith entity3.

假设您正在保存实体 1、实体 2 和实体 3。现在,在保存 entity3 时发生异常,然后当 enitiy1 和 entity2 进入同一事务时,entity1 和 entity2 将与 entity3一起回滚

Transaction :

交易 :

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

Any exception will result in rollback of all JPA transactions with DB.Internally JPA transaction are used by Spring.

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

回答by Danyal Sandeelo

It may be late but I came across something which explains your concern related to proxy (only 'external' method calls coming in through the proxy will be intercepted) nicely.

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

For example, you have a class that looks like this

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

@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");    
  }
}

and you have an aspect, that looks like this:

你有一个方面,看起来像这样:

@Component
@Aspect
public class CrossCuttingConcern {

    @Before("execution(* com.intertech.CoreBusinessSubordinate.*(..))")
    public void doCrossCutStuff(){
        System.out.println("Doing the cross cutting concern now");
    }
}

When you execute it like this:

当你像这样执行它时:

 @Service
public class CoreBusinessKickOff {

    @Autowired
    CoreBusinessSubordinate subordinate;

    // getter/setters

    public void kickOff() {
       System.out.println("I do something big");
       subordinate.doSomethingBig();
       subordinate.doSomethingSmall(4);
   }

}

}

Results of calling kickOff above given code above.

在上面给定的代码上调用 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

but when you change your code to

但是当您将代码更改为

@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);
}

You see, the method internally calls another method so it won't be intercepted and the output would look like this:

你看,这个方法在内部调用了另一个方法,所以它不会被拦截,输出看起来像这样:

I do something big
Doing the cross cutting concern now
I did something small
I also do something small but with an int

You can by-pass this by doing that

你可以通过这样做绕过这个

public void doSomethingBig() {
    System.out.println("I did something small");
    //doSomethingSmall(4);
    ((CoreBusinessSubordinate) AopContext.currentProxy()).doSomethingSmall(4);
}

Code snippets taken from: https://www.intertech.com/Blog/secrets-of-the-spring-aop-proxy/

代码片段取自:https: //www.intertech.com/Blog/secrets-of-the-spring-aop-proxy/

回答by Marco Behler

All existing answers are correct, but I feel cannot give just this complex topic.

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

For a comprehensive, practical explanation you might want to have a look at this Spring @Transactional In-Depthguide, which tries its best to cover transaction management in ~4000 simple words, with a lot of code examples.

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