Article From:https://www.cnblogs.com/zhangmingcheng/p/9967364.html

Scene Analysis

Recent projects have encountered a very strange problem. The general business scenario is as follows: First, we set up two transactions, transaction parent and transaction child, and call both methods in Controller. The sample code is as follows:

1、Scene A:

@RestController
@RequestMapping(value = "/test")
public class OrderController {

    @Autowired
    private TestService userService;

    @GetMapping
    public void test() {
        //Call parent and child at the same timeUserService. parent ();UserService. child ();}}@ServicePublicClass TestService Impl implements TestService {@AutowiredPrivate User Mapper User Mapper;@OverrideTransactionalPublic void parent () {User parent = new User ("Zhang Dazhuang Paren"T","123456","45";UserMapper. insert (parent);}@OverrideTransactionalPuBlic void child () {User child = new User ("Zhang Dazhuang Child", "654321", 25);UserMapper.insERT (child);}}

In fact, two transactions are executed separately. The result of execution is that both methods can insert data! As follows:

2、Scene B:

Modify the above code as follows:

@RestController
@RequestMapping(value = "/test")
public class OrderController {

    @Autowired
    private TestService userService;

    @GetMapping
    public void test() {
        userService.parent();
    }
}
@Service
public class TestServiceImpl implements TestService {

    @Autowired
    private UserMapper userMapper;

    @Override
    @Transactional
    public void parent() {
        User parent = new User("Zhang Dazhuang's Parent, 123456, 45;UserMapper. insert (parent);// Call child inside parentChILD ();}@OverrideTransactional (propagation = Propagation. REQUIRES_NEW)Public VoID child () {User child = new User ("Zhang Dazhuang Child", "654321", 25);UserMapper. insert (chi)LD);}}

Propagation.REQUIRES_NEWMeaning: If there is a transaction at present, then hang up the current transaction and open a new transaction to continue execution. After the execution of the new transaction, then hang up the transaction before probation. If there is no transaction at present, then open a new transaction.

The result of execution is that both methods can insert data! The results are as follows:

Scenario A and Scenario B are executed normally, and no rollbacks occur during this period, if an exception occurs in the child () method!

3、Scene C

The code for modifying child () is as follows, and the other code is the same as scenario B:

@Override
    @Transactional
    public void parent() {
        User parent = new User("Zhang Dazhuang's Parent, 123456, 45;UserMapper. insert (parent);Child ();}@OverrideTransactional (propagation = Propagation. REQUIRES_NEW)Public void child () {User CHild= new User ("Zhang Dazhuang Child", "654321", 25);UserMapper. insert (child);Throw new RuntiMeException ("child Exception........................");}

The execution results are as follows, there will be exceptions, and the data is not inserted in:

Question 1: In scenario C, child () throws an exception, but parent () does not throw an exception. Should parent () submit successfully and child () roll back?

The child () throws an exception and the parent () does not catch it. This causes the parent () to throw an exception too! So they both roll back!

4、Scene D

At this point, if the parent () method is modified to capture the exception thrown in child (), the other code is the same as scenario C.

@Override
    @Transactional
    public void parent() {
        User parent = new User("Zhang Dazhuang's Parent, 123456, 45;UserMapper. insert (parent);Try {Child ();} catch (Exception) {E. printStackTrace ();}}@Override@TRansaction (propagation = Propagation. REQUIRES_NEW)Public void child () {User child =New User ("Zhang Dazhuang Child", "654321", 25);UserMapper. insert (child);Throw new Runtime Excel"Child Exception........................";}

Then execute again, and the result is that both are inserted into the database:

See here many small partners may ask, according to our logic, child () throws an exception, parent () does not throw and catch child () throws an exception! The result of execution should be child () rollback and parent () submitted successfully.Ah!

Question 2: Why is scenario D not child () rollback and parent () submission successful?

The above scenarios C and D seem to merge into one problem, either succeeding or failing! It’s totally different from what we expected! See here, this is the theme we are going to discuss today, “JDK dynamic proxy for Spring transaction buried pit!” Next, let’s analyze SpringThe underlying reason why transactions cannot be rolled back in this particular scenario!

2. The Essence of the Problem

We know that Spring transaction management is implemented by JDK dynamic proxy (another is implemented by CGLib dynamic proxy). It is also because of the characteristics of dynamic proxy that the parent () method invokes the child () method and causes chi.The transaction in LD () method is invalid! Simply put, when the parent () method calls the child () method in scenario D, the transaction of the child () method does not work. At this time, the child () method is like a common method without adding transactions, which is essentially the same.The following code:

Scenario C essence:

Scene D essence:

As with the above code, we can easily explain Question 1 and Question 2, because the characteristics of dynamic proxy result in the nature of scenario C and D, such as the above code. In scenario C, child () throws an exception that is not caught, which is equivalent to throwing an exception in a parent transaction, causing pareNT () rolls back together because they are essentially the same method; in scenario D, child () throws an exception and catches it; no exception is thrown in the parent transaction; parent () and child () are both in one transaction, so they succeed;

See here, then what exactly is this feature of dynamic proxy that causes Spring transactions to fail?

3. What is the characteristic of dynamic agent?

First, let’s look at a simple implementation of dynamic proxy:

//InterfacePublic interface OrderService {Void test1 ();Void test2 ();}//Interface Implementation ClassPublic class OrdErService Impl implements OrderService {@OverridePublic void test1 () {System.out.Println ("--execute test1--");}@OverridePublic void test2 () {System. out. println ("-- Execute test2 - ";}}/ / agent classPublic class OrderProxy implements Invocation Handler {Private statIC final String METHOD_PREFIX = test;Private Object target;Public OrderProxy (Object ta)Rget) {This. target = target;}@OverridePublic Object invoke (Object proxy, Me)Thod method, Object [] args) throws Throwable {// We use this flag to identify whether to use proxy or method OntologyIf (metho)D. getName (). startsWith (METHOD_PREFIX){System. out. println ("============== delimiter========");}Return method. invoke (target, args);}Public Object getProxy () {RetuRN Proxy. newProxyInstance (Thread. current Thread (). getContextClassLoader (),Target.getClass (). getInterfaces (), this;}}//Testing methodsPublic class ProxyDemo {Public static void main(String [] args) {OrderService orderService = new OrderService Impl ();OrderProxy ProXy = new OrderProxy (orderService);OrderService = proxy. getProxy ();ORderService. test1 ();OrderService. test2 ();}}

At this point, we execute the following test methods, noting that test1 () and test2 () are called at the same time, and the results are as follows:

As you can see, in the OrderServiceImpl class, because test1 () does not call test2 (), their methods are executed using proxy, that is to say, both test1 and test2 are invoke () methods invoked through proxy objects.Similar to our scenarios A and B.

If we simulate scenario C and D calling test2 () in test1 (), the code is modified as follows:

The results are as follows:

It can be clearly seen here that test1 () takes the proxy, while test2 () takes the ordinary method, without proxy! Did you suddenly understand when you saw this?

This should be a good understanding of why it’s like this! uuuuuuuuuuu This is because calling the method in test1 () in Java is essentially equivalent to putting the body of the method in test2 () into test1 (), that is, the internal method, no matter how many nests you have.Few layers, only proxy objectsproxyThe method invoked directly is the real proxy, as follows:

The test method is the same as the above test method, and the results are as follows:

Remember: only the method that proxy calls directly is the real proxy!

Fourth, how to solve this pit?

In the analysis above, we have seen why the use of Spring transactions in this particular scenario can not cause transactions to roll back. Here we discuss several solutions:

1、We can choose to avoid this problem! We can solve the problem without using the above transaction nesting method. The simplest way is to refer the problem to Service or more advanced logic. Service. xxtransaction will not appear.That’s the problem.

2、Get the proxy object from the AopProxy context:

(1)SpringBootConfiguration: Annotation OpenexposeProxy = true,Exposing the proxy object (otherwise AopContext. currentProxy ()) throws an exception.

Adding dependencies:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

Add notes:

Modify the execution of the original code as follows:

At this time, the execution results are as follows:

Obviously, the child method has been rolled back because of the exception, and the parent can submit correctly, which is the result we want! Notice that the exception is caught by try / catch when parent calls child!

(2)The traditional Spring XML configuration file only needs to add a dependency configuration as follows, using the same way:

<aop:aspectj-autoproxy expose-proxy="true"/>

3、Resolve through the context of Application Context:

@Service
public class TestServiceImpl implements TestService {

    @Autowired
    private UserMapper userMapper;

    /**
     * SpringApplication context* /@AutowiredPrivate Application Context context;Private TestService proxy;PostConstructPublic void init () {// Get AOP proxy objects from Spring contextProxy = contextGetBean (TestService. class);}@OverrideTransactionalPublic void parent () {User parent = new User ("Zhang Dazhuang Parent", "123456", 45);UserMapper. insert (parent);TRy {Proxy. child ();} catch (Exception) {E. printStackTrace ();}}@OverrideTransactional (propagation = Propagation. REQUIRES_NEW)Public VoID child () {User child = new User ("Zhang Dazhuang Child", "654321", 25);UserMapper. insert (chi)LD);Throw new Runtime Exception ("child Exception.....................");}}

The results of implementation are in line with our expectations:

Five, summary

So far, let’s briefly introduce the Spring transaction management. If there are scenarios like scenario C or D in the business, if it is not clear that JDK dynamic agent causes the Spring transaction can not roll back, then it may be a development accident, maybe it will deduct salary!

Above, I have outlined the basic problem of using and causing irreversible transactions in several scenarios. Of course, it is still a superficial phenomenon, and there is no in-depth principle to analyze it. Nevertheless, if you can explain your own understanding of this problem during the interview, it is also a plus point.

From: https://zhuanlan.zhihu.com/p/35483036

Leave a Reply

Your email address will not be published. Required fields are marked *