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
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
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()也是拋出了異常了的!因此他們兩個都會回滾!測試
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 }
由於動態代理的特性形成了場景C和場景D的本質如上述代碼。在場景C中,child()拋出異常沒有捕獲,至關於parent事務中拋出了異常,形成parent()一塊兒回滾,由於他們本質是同一個方法;在場景D中,child()拋出異常並進行了捕獲,parent事務中沒有拋出異常,parent()和child()同時在一個事務裏邊,因此他們都成功了;
接口:
1 public interface OrderService { 2 void test01(); 3 void test02(); 4 }
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 }
代理類:
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 }
測試:
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 }
輸出:
咱們模擬一下場景C和場景D在test1()中調用test2()
1 @Override 2 public void test01() { 3 System.out.println("===執行test01"); 4 test02(); 5 }
輸出:
根本就沒有走動態代理,而是一個普通的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 }
要添加jar包
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
修改方法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 }
這個時候,child()有異常,回滾,parent()沒有異常,執行。