Java中Spring对事务的支持详解
作者:蓝云飘飘2366
事务属性
- 事务传播行为;--Propagation(本文重点解读事务传播行为)
- 事务隔离级别;--Isolation
- 事务超时;--timeout
- 只读事务;--readOnly
- 设置出现哪些异常回滚事务;--rollbackFor
- 设置出现哪些异常不回滚事务;--noRollbackFor
事务的传播行为
一、什么是事务的传播行为
在service类中有a()方法和b()方法,a()方法上有事务,b()方法上也有事务,当a()方法执行过程中调用了b()方法,事务是如何传递的?合并到一个事务里?还是开启一个新的事务?这就是事务传播行为。
注意:以下事务传播属性都是打在内部方法b()方法上的事务注解
二、7种事务传播行为
Spring 可以通过 @Transactional 注解的 propagation 属性来设置不同的传播行为策略。
Spring 为此提供了一个枚举类 Propagation,源码如下:
package org.springframework.transaction.annotation; import org.springframework.transaction.TransactionDefinition; public enum Propagation { /** * 需要事务,它是默认传播行为,如果当前存在事务,就沿用当前事务, * 否则新建一个事务运行内部方法 */ REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED), /** * 支持事务,如果当前存在事务,就沿用当前事务, * 如果不存在,则继续采用无事务的方式运行内部方法 */ SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS), /** * 必须使用事务,如果当前没有事务,则会抛出异常, * 如果存在当前事务,则沿用当前事务 */ MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY), /** * 无论当前事务是否存在,都会创建新事务运行方法, * 这样新事务就可以拥有新的锁和隔离级别等特性,与当前事务相互独立 */ REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW), /** * 不支持事务,当前存在事务时,将挂起事务,运行方法 */ NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED), /** * 不支持事务,如果当前方法存在事务,则抛出异常,否则继续使用无事务机制运行 */ NEVER(TransactionDefinition.PROPAGATION_NEVER), /** * 在当前方法调用内部方法时,如果内部方法发生异常, * 只回滚内部方法执行过的 SQL ,而不回滚当前方法的事务 */ NESTED(TransactionDefinition.PROPAGATION_NESTED); ...... }
接下来我们通过对其中三种最常用的(REQUIRED、REQUIRES_NEW、NESTED)策略进行对比来更深入的理解。以下测试均在外部方法开启事务的情况下进行,因为在外部没有事务的情况下,三者都会新建事务,效果一样。
1. REQUIRED
当内部方法的事务传播行为设置为 REQUIRED 时,内部方法会加入外部方法的事务。我们在 UserServiceImpl 中添加如下方法:
@Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService { @Autowired private UserMapper mapper; @Override @Transactional(propagation = Propagation.REQUIRED) public void addWithRequired(User user) { mapper.insert(user); } @Override @Transactional(propagation = Propagation.REQUIRED) public void addWithRequiredAndException(User user) { mapper.insert(user); throw new RuntimeException(); } }
创建 TransactionServiceImpl 类,并添加如下方法:
@Slf4j @Service public class TransactionServiceImpl implements TransactionService { @Autowired private UserService userService; @Override public void noTransaction_required_required_externalException() { User xiaoShui = new User().setName("小水"); User xiaoJing = new User().setName("小镜"); userService.addWithRequired(xiaoShui); userService.addWithRequired(xiaoJing); throw new RuntimeException(); } @Override public void noTransaction_required_requiredException() { User xiaoShui = new User().setName("小水"); User xiaoJing = new User().setName("小镜"); userService.addWithRequired(xiaoShui); userService.addWithRequiredAndException(xiaoJing); } @Override @Transactional public void transaction_required_required_externalException() { User xiaoShui = new User().setName("小水"); User xiaoJing = new User().setName("小镜"); userService.addWithRequired(xiaoShui); userService.addWithRequired(xiaoJing); throw new RuntimeException(); } @Override @Transactional public void transaction_required_requiredException() { User xiaoShui = new User().setName("小水"); User xiaoJing = new User().setName("小镜"); userService.addWithRequired(xiaoShui); userService.addWithRequiredAndException(xiaoJing); } @Override @Transactional public void transaction_required_requiredException_try() { User xiaoShui = new User().setName("小水"); User xiaoJing = new User().setName("小镜"); userService.addWithRequired(xiaoShui); try { userService.addWithRequiredAndException(xiaoJing); } catch (Exception e) { log.error("发生异常,事务回滚!"); } } }
结果分析如下表所示:
前面四种情况都比较好理解,很多人不能理解最后一种情况:我都 try-catch 了你还想怎样?这里的关键点在于所有方法都处于同一个事务中,此时「小镜」的插入方法发生异常,那么这个方法所在的事务就会被 Spring 设置为 rollback 状态。因为异常被 catch 了,所以外部方法执行完要进行 commit 操作,这时却发现当前事务已经处于 rollback 状态了,虽然它不知道哪里出了问题,但也只能听从指挥,回滚所有操作了。
PS:由于外部方法不开启事务的情况,在每种传播行为下结果都是类似的,所以后面不再给出示例。
2. REQUIRES_NEW
当内部方法的传播行为设置为 REQUIRES_NEW 时,内部方法会先将外部方法的事务挂起,然后开启一个新的事务 。在 UserServiceImpl 中添加如下方法(UserServiceImpl 类中上面的方法还在哦):
@Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService { ... @Override @Transactional(propagation = Propagation.REQUIRES_NEW) public void addWithRequiredNew(User user) { mapper.insert(user); } @Override @Transactional(propagation = Propagation.REQUIRES_NEW) public void addWithRequiredNewAndException(User user) { mapper.insert(user); throw new RuntimeException(); } }
在 TransactionServiceImpl 中添加如下方法:
@Slf4j @Service public class TransactionServiceImpl implements TransactionService { ... @Override @Transactional public void transaction_required_requiredNew_externalException() { User xiaoShui = new User().setName("小水"); User xiaoJing = new User().setName("小镜"); userService.addWithRequired(xiaoShui); userService.addWithRequiredNew(xiaoJing); throw new RuntimeException(); } @Override @Transactional public void transaction_required_requiredNew_requiredNewException() { User xiaoShui = new User().setName("小水"); User xiaoJing = new User().setName("小镜"); User shuiJing = new User().setName("水镜"); userService.addWithRequired(xiaoShui); userService.addWithRequiredNew(xiaoJing); userService.addWithRequiredNewAndException(shuiJing); } @Override @Transactional public void transaction_required_requiredNewException_try() { User xiaoShui = new User().setName("小水"); User xiaoJing = new User().setName("小镜"); User shuiJing = new User().setName("水镜"); userService.addWithRequired(xiaoShui); userService.addWithRequiredNew(xiaoJing); try { userService.addWithRequiredNewAndException(shuiJing); } catch (Exception e) { log.error("发生异常,事务回滚!"); } } }
结果分析如下表所示:
3. NESTED
当内部方法的传播行为设置为 NESTED 时,内部方法会开启一个新的嵌套事务(子事务)。在 UserServiceImpl 中添加如下方法:
@Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService { ... @Override @Transactional(propagation = Propagation.NESTED) public void addWithNested(User user) { mapper.insert(user); } @Override @Transactional(propagation = Propagation.NESTED) public void addWithNestedAndException(User user) { mapper.insert(user); throw new RuntimeException(); } }
在 TransactionServiceImpl 中添加如下方法:
@Slf4j @Service public class TransactionServiceImpl implements TransactionService { ... @Override @Transactional public void transaction_nested_nested_externalException() { User xiaoShui = new User().setName("小水"); User xiaoJing = new User().setName("小镜"); userService.addWithNested(xiaoShui); userService.addWithNested(xiaoJing); throw new RuntimeException(); } @Override @Transactional public void transaction_nested_nestedException() { User xiaoShui = new User().setName("小水"); User xiaoJing = new User().setName("小镜"); userService.addWithNested(xiaoShui); userService.addWithNestedAndException(xiaoJing); } @Override @Transactional public void transaction_nested_nestedException_try() { User xiaoShui = new User().setName("小水"); User xiaoJing = new User().setName("小镜"); User shuiJing = new User().setName("水镜"); userService.addWithRequired(xiaoShui); userService.addWithNested(xiaoJing); try { userService.addWithNestedAndException(shuiJing); } catch (Exception e) { log.error("发生异常,事务回滚!",e); } } }
结果分析如下表所示:
每个 NESTED 事务执行前会将当前操作保存下来,叫做 savepoint (保存点),如果当前 NESTED 事务执行失败,则回滚到之前的保存点,保存点使得子事务的回滚不对主事务造成影响。NESTED 事务在外部事务提交以后自己才会提交。
4. 总结
REQUIRES_NEW 最为简单,不管当前有无事务,它都会开启一个全新事务,既不影响外部事务,也不会影响其他内部事务,真正的井水不犯河水,坚定而独立。
REQUIRED 在没有外部事务的情况下,会开启一个独立的新事务,且不会对其他同级事务造成影响;而当存在外部事务的情况下,则会与外部事务同生共死。
NESTED 在没有外部事务的情况下与 REQUIRED 效果相同;而当存在外部事务的情况下,当外部事务回滚时,它会创建一个嵌套事务(子事务)。外部事务回滚时,子事务会跟着回滚;但子事务的回滚不会对外部事务和其他同级事务造成影响。
三、事务的传播行为(理解记忆)
到此这篇关于Java中Spring对事务的支持详解的文章就介绍到这了,更多相关Spring对事务的支持内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!