Spring 事务注解 @Transactional 原本能够保证原子性,若是事务内有报错的话,整个事务能够保证回滚,可是加上try catch或者事务嵌套,可能会致使事务回滚失败。测试一波。
建两张表,模拟两个数据操做app
CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(20) DEFAULT NULL, `age` smallint(3) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; CREATE TABLE `role` ( `id` int(11) NOT NULL AUTO_INCREMENT, `role_name` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
根据排列组合原理,咱们进行四种测试:一、无try catch、无嵌套;二、有try catch、无嵌套;三、无try catch、有嵌套;四、都有。dom
若是咱们单纯@Transactional,事务能够正常回滚吗?测试
@GetMapping("/saveNormal0") @Transactional public void saveNormal0() throws Exception { int age = random.nextInt(100); User user = new User().setAge(age).setName("name:"+age); userService.save(user); throw new RuntimeException(); }
若是事务内报了RuntimeException错误,事务能够回滚。spa
@GetMapping("/saveNormal0") @Transactional public void saveNormal0() throws Exception { int age = random.nextInt(100); User user = new User().setAge(age).setName("name:"+age); userService.save(user); throw new Exception(); }
若是事务内报了Exception错误(非RuntimeException错误),事务不能够回滚。code
@GetMapping("/saveNormal0") @Transactional( rollbackFor = Exception.class) public void saveNormal0() throws Exception { int age = random.nextInt(100); User user = new User().setAge(age).setName("name:"+age); userService.save(user); throw new Exception(); }
若是是Exception错误(非RuntimeException),加上 rollbackFor = Exception.class 参数也能够实现回滚。orm
结论一:对于@Transactional能够保证RuntimeException错误的回滚,若是想保证非RuntimeException错误的回滚,须要加上rollbackFor = Exception.class 参数。事务
通过博主多种状况测试,发现try catch对回滚这个事自己没有什么影响,结论一照样成立。try catch只是对异常是否能够被@Transactional 感知 到有影响。若是错误抛到切面能够感知到的地步,那就能够起做用。ci
@GetMapping("/saveTryCatch") @Transactional( rollbackFor = Exception.class) public void saveTryCatch() throws Exception{ try{ int age = random.nextInt(100); User user = new User().setAge(age).setName("name:"+age); userService.save(user); throw new Exception(); }catch (Exception e){ throw e; } }
好比上面一段代码就回滚了。源码
@GetMapping("/saveTryCatch") @Transactional( rollbackFor = Exception.class) public void saveTryCatch() throws Exception{ try{ int age = random.nextInt(100); User user = new User().setAge(age).setName("name:"+age); userService.save(user); throw new Exception(); }catch (Exception e){ } }
然而,将catch中的错误不继续网上抛,切面没法感知到错误,没法进行处理,那么事务就没法回滚了。it
结论二:try catch只是对异常是否能够被@Transactional 感知 到有影响。若是错误抛到切面能够感知到的地步,那就能够起做用。
首先通过实验,结论一仍然成立,即,当不加上rollbackFor = Exception.class 的时候,不管内外报RuntimeException,都会回滚;不管内外报 非RuntimeException 错误,都不会回滚。若是加上rollbackFor = Exception.class,不管内外怎么报错,都会回滚。这些代码就不给出了。接下来,试下下面两种状况:
@GetMapping("/out") @Transactional( rollbackFor = Exception.class) public void out() throws Exception{ innerService.inner(); int age = random.nextInt(100); User user = new User().setAge(age).setName("name:" + age); userService.save(user); throw new Exception(); } @Transactional public void inner() throws Exception{ Role role = new Role(); role.setRoleName("roleName:"+new Random().nextInt(100)); roleService.save(role); // throw new Exception(); }
状况一,外面事务加上rollbackFor = Exception.class,里面事务不加,测试内外分别报错的状况(为了简化代码量,只给出了外面报错的代码),均可以回滚。由于,不管如何,错误都抛给了外面那个事务进行处理,而外面那个加上了rollbackFor = Exception.class,具有处理非RuntimeException错误的能力,因此均可以让事务进行正常回滚。
下面看状况二,里面的事务加上rollbackFor = Exception.class,外面不加,外面报错。
@GetMapping("/out") @Transactional public void out() throws Exception{ innerService.inner(); int age = random.nextInt(100); User user = new User().setAge(age).setName("name:" + age); userService.save(user); throw new Exception(); } @Transactional( rollbackFor = Exception.class) public void inner() throws Exception{ Role role = new Role(); role.setRoleName("roleName:"+new Random().nextInt(100)); roleService.save(role); }
事务都没法回滚,这是咱们有个疑问,里面的事务明明有很强的处理能力啊,为何和外面一块儿回滚失败呢,别着急,等等聊这个。
而后试下里面报错:
@GetMapping("/out") @Transactional public void out() throws Exception{ innerService.inner(); int age = random.nextInt(100); User user = new User().setAge(age).setName("name:" + age); userService.save(user); } @Transactional( rollbackFor = Exception.class) public void inner() throws Exception{ Role role = new Role(); role.setRoleName("roleName:"+new Random().nextInt(100)); roleService.save(role); throw new Exception(); }
咦,这回都进行了正常的回滚。个人天,这回外面没有处理能力,为何接受里面抛出来的错误,也进行了回滚!!!看上去,就好像里外事务老是同生共死的对不对?原来,@Transactional还有个参数,看下源码,这个注解还有默认值:
Propagation propagation() default Propagation.REQUIRED;
REQUIRED的意思是说,事务嵌套的时候,若是发现已经有事务存在了,就加入这个事务,而不是新建一个事务,因此根本就不存在两个事务,一直只有一个!至于,此参数其余值,本文不进行测试。回到上面的问题,当外面报错的时候,此时查看事务,没有增长rollbackFor = Exception.class参数,即没有处理非RuntimeException能力,因此代码走完,貌似“两个事务”,都回滚失败了。当里面报错的时候,事务已经添加上了处理非RuntimeException能力,因此,代码走完就回滚成功了。
结论三:因为REQUIRED属性,“两个事务”实际上是一个事务,处理能力看报错时刻,是否添加了处理非RuntimeException的能力。
在结论一二三成立的条件下,探索共同影响的问题就简单多了,因为状况太多,就不进行过多的代码展现了。
结论一:对于@Transactional能够保证RuntimeException错误的回滚,若是想保证非RuntimeException错误的回滚,须要加上rollbackFor = Exception.class 参数。
结论二:try catch只是对异常是否能够被@Transactional 感知 到有影响。若是错误抛到切面能够感知到的地步,那就能够起做用。
结论三:因为REQUIRED属性,“两个事务”实际上是一个事务,处理能力看报错时刻,是否添加了处理非RuntimeException的能力。