事務的應用

 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     }

測試:java

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

是沒有任何問題的。能夠插入到數據庫中spring

場景二: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();
    }

也能插入,沒有任何問題。app

場景三: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     }

測試:ide

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

兩個都沒有插入到數據庫中。spring-boot

疑問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     }

測試:this

兩個都插入到數據庫中了。spa

看到這裏不少小夥伴均可能會問,按照咱們的邏輯來想的話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()沒有異常,執行。

相關文章
相關標籤/搜索