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 @Transactional11 public void child1(){12 User child = new User("child",19,BigDecimal.valueOf(1000));13 userMapper.insert(child);14 }
测试:
1 @Test2 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 @Transactional2 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()同时在一个事务里边,所以他们都成功了;
动态代理是什么?为什么会使spring事务失效呢?
接口:
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 @Override10 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 @Override15 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 @Override2 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包
org.springframework.boot spring-boot-starter-aop
修改方法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()没有异常,执行。