事务的应用

 1 /**
 2      * 学习spring事务,
 3      * 场景一:parent()  和child() 都具有事务,同时调用两个,如果不出现异常,都是可以插入到数据库中的。
 4      */
 5     @Transactional
 6     public void parent1(){
 7         User parent = new User("parent",19,BigDecimal.valueOf(1000));
 8         userMapper.insert(parent);
 9     }
10     @Transactional
11     public void child1(){
12         User child = new User("child",19,BigDecimal.valueOf(1000));
13         userMapper.insert(child);
14     }

测试:

1 @Test
2     public void test05(){
3         userService.parent1();
4         userService.child1();
5     }

是没有任何问题的。可以插入到数据库中

场景二:parent()  中使用child()

 1  /**
 2      * 学习spring事务,
 3      * 场景二:parent()中调用child()
 4      */
 5     @Transactional
 6     public void parent2(){
 7         User parent = new User("parent",19,BigDecimal.valueOf(1000));
 8         userMapper.insert(parent);
 9         child2();
10     }
11 
12     /**
13      * 如果当前存在事务,则挂起当前事务并且开启一个新事物继续执行,新事物执行完毕之后,
14      * 然后在缓刑之前挂起的事务,如果当前不存在事务的话,则开启一个新事物。
15      */
16     @Transactional(propagation = Propagation.REQUIRES_NEW)
17     public void child2(){
18         User child = new User("child",19,BigDecimal.valueOf(1000));
19         userMapper.insert(child);
20     }

测试:

@Test
    public void test06(){
        userService.parent2();
       // userService.child2();
    }

也能插入,没有任何问题。

场景三:child()这个事务 中抛出了异常

 1 @Transactional
 2     public void parent3(){
 3         User parent = new User("parent",19,BigDecimal.valueOf(1000));
 4         userMapper.insert(parent);
 5         child3();
 6     }
 7     @Transactional(propagation = Propagation.REQUIRES_NEW)
 8     public void child3(){
 9         User child = new User("child",19,BigDecimal.valueOf(1000));
10         userMapper.insert(child);
11         throw new RuntimeException("此处抛出了异常");
12     }

测试:

@Test
    public void test06(){
        userService.parent3();
    }

两个都没有插入到数据库中。

疑问1:场景C中child()抛出了异常,但是parent()没有抛出异常,按道理是不是应该parent()提交成功而child()回滚?

可能有的小伙伴要说了,child()抛出了异常在parent()没有进行捕获,造成了parent()也是抛出了异常了的!所以他们两个都会回滚!

场景四:在parent()中捕获异常

 1  @Transactional
 2     public void parent4(){
 3         User parent = new User("parent",19,BigDecimal.valueOf(1000));
 4         userMapper.insert(parent);
 5 
 6         try {
 7             child4();
 8         } catch (Exception e) {
 9             e.printStackTrace();
10         }
11     }
12     @Transactional(propagation = Propagation.REQUIRES_NEW)
13     public void child4(){
14         User child = new User("child",19,BigDecimal.valueOf(1000));
15         userMapper.insert(child);
16         throw new RuntimeException("此处抛出了异常");
17     }

测试:

两个都插入到数据库中了。

看到这里很多小伙伴都可能会问,按照我们的逻辑来想的话child()中抛出了异常,parent()没有抛出并且捕获了child()抛出了异常!执行的结果应该是child()回滚,parent()提交成功的啊!

问题的本质。

Spring事务管理是通过JDK动态代理的方式进行实现的(另一种是使用CGLib动态代理实现的),也正是因为动态代理的特性造成了上述parent()方法调用child()方法的时候造成了child()方法中的事务失效!简单的来说,在场景四中parent()方法调用child()方法的时候,child()方法的事务是不起作用的,此时的child()方法像一个没有加事务的普通方法,其本质上就相当于下边的代码:

1 @Transactional
2     public void parent3(){
3         User parent = new User("parent",19,BigDecimal.valueOf(1000));
4         userMapper.insert(parent);
5         User child = new User("child",19,BigDecimal.valueOf(1000));
6         userMapper.insert(child);
7         throw new RuntimeException("此处抛出了异常");
8        // child3();
9     }

场景4本质:

 1 @Transactional
 2     public void parent4(){
 3         User parent = new User("parent",19,BigDecimal.valueOf(1000));
 4         userMapper.insert(parent);
 5 
 6         try {
 7             User child = new User("child",19,BigDecimal.valueOf(1000));
 8             userMapper.insert(child);
 9             throw new RuntimeException("此处抛出了异常");
10             //child4();
11         } catch (Exception e) {
12             e.printStackTrace();
13         }
14     }
View Code

因为动态代理的特性造成了场景C和场景D的本质如上述代码。在场景C中,child()抛出异常没有捕获,相当于parent事务中抛出了异常,造成parent()一起回滚,因为他们本质是同一个方法;在场景D中,child()抛出异常并进行了捕获,parent事务中没有抛出异常,parent()和child()同时在一个事务里边,所以他们都成功了;

动态代理是什么?为什么会使spring事务失效呢?

接口:

1 public interface OrderService {
2     void test01();
3     void test02();
4 }
View Code
 1 package com.demo.proxy;
 2 
 3 public class OrderServiceImpl implements OrderService {
 4     @Override
 5     public void test01() {
 6         System.out.println("===执行test01");
 7     }
 8 
 9     @Override
10     public void test02() {
11         System.out.println("===执行test02");
12     }
13 }
View Code

代理类:

 1 package com.demo.proxy;
 2 
 3 import java.lang.reflect.InvocationHandler;
 4 import java.lang.reflect.Method;
 5 import java.lang.reflect.Proxy;
 6 
 7 public class OrderProxy implements InvocationHandler {
 8 
 9     private Object target;
10 
11     public OrderProxy(Object target) {
12         this.target = target;
13     }
14     @Override
15     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
16         if (method.getName().startsWith("test")) {
17             System.out.println("===使用了动态代理");
18         }
19         return method.invoke(target,args);
20     }
21 
22     public Object getProxy(){
23         return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
24                 target.getClass().getInterfaces(), this);
25     }
26 }
View Code

测试:

 1 package com.demo.proxy;
 2 
 3 public class Test {
 4     public static void main(String[] args) {
 5         OrderService orderService = new OrderServiceImpl();
 6         OrderProxy orderProxy = new OrderProxy(orderService);
 7          orderService = (OrderService)orderProxy.getProxy();
 8          orderService.test01();
 9          orderService.test02();
10     }
11 }
View Code

输出:

我们模拟一下场景C和场景D在test1()中调用test2()

1  @Override
2     public void test01() {
3         System.out.println("===执行test01");
4         test02();
5     }
View Code

输出:

根本就没有走动态代理,而是一个普通的test02()方法。

只有代理对象proxy 直接调用的那一个方法才是真正的走代理的

解决方案:

通过AopProxy上下文获取代理对象:

(1)SpringBoot配置方式:注解开启 exposeProxy = true,暴露代理对象 (否则AopContext.currentProxy()) 会抛出异常。

 1 @SpringBootApplication
 2 @MapperScan(value = "com.demo.mapper")
 3 @EnableAspectJAutoProxy(exposeProxy = true)
 4 public class DemoApplication {
 5 
 6     public static void main(String[] args) {
 7         SpringApplication.run(DemoApplication.class, args);
 8     }
 9 
10 }
View Code

要添加jar包

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

修改方法parent()

 1 /**
 2      * 利用AopContext 上下文获取代理对象
 3      */
 4     @Transactional
 5     public void parent5(){
 6         User parent = new User("parent",19,BigDecimal.valueOf(1000));
 7         userMapper.insert(parent);
 8 
 9         try {
10             ((UserServiceImpl)AopContext.currentProxy()).child4();
11         } catch (Exception e) {
12             e.printStackTrace();
13         }
14     }
View Code

这个时候,child()有异常,回滚,parent()没有异常,执行。



原文地址:https://www.cnblogs.com/bulrush/p/10768841.html