转载

浅谈Spring事务中的7种传播特性

简单来讲,就是当系统中存在两个事务方法时(我们暂称为方法A和方法B),如果方法B在方法A中被调用,那么将 采用什么样的事务形式 ,就叫做事务的传播特性

比如,A方法调用了B方法(B方法必须使用事务注解),那么B事务可以是一个在A中嵌套的事务,或者B事务不使用事务,又或是使用与A事务相同的事务,这些均可以通过指定事务传播特性来实现

怎么配置事务传播特性?

首先使用 org.springframework.transaction.annotation 包下的 @Transactional 注解,在其中声明propagation属性即可(默认值为Propagation.REQUIRED):

@Transactional(propagation = Propagation.REQUIRED)
复制代码

事务传播特性有哪几种?

目前,Spring在 TransactionDefinition 类中定义了以下7种传播特性,具体特性我们接下来会分析:

  • PROPAGATION_REQUIRED :如果不存在外层事务,就主动创建事务;否则使用外层事务
  • PROPAGATION_SUPPORTS :如果不存在外层事务,就不开启事务;否则使用外层事务
  • PROPAGATION_MANDATORY :如果不存在外层事务,就抛出异常;否则使用外层事务
  • PROPAGATION_REQUIRES_NEW :总是主动开启事务;如果存在外层事务,就将外层事务挂起
  • PROPAGATION_NOT_SUPPORTED :总是不开启事务;如果存在外层事务,就将外层事务挂起
  • PROPAGATION_NEVER :总是不开启事务;如果存在外层事务,则抛出异常
  • PROPAGATION_NESTED :如果不存在外层事务,就主动创建事务;否则创建嵌套的子事务

为什么要指定事务传播特性?

有些人可能会觉得,为什么非要指定传播特性不可,我们所有方法执行都开启一个事务不可以吗?这里我暂时不做解释,看完下一个部分相信就有答案了

事务传播特性各自有什么特点?

在具体说明前,先来做一些准备工作

首先定义两张表 usernote ,user表有 idname 两个数据列,note表有 idcontent 两个数据列

然后新建springboot项目,创建对应的User/Note类,以及dao和service接口等等部分(为了方便演示,Service我直接创建的类,没有使用接口),就不再一一列出了

接着重点来了,我们先在UserService中定义 insertUser 方法:

@Transactional(rollbackFor = Exception.class)
    public void insertUser(String name) {
        User user = new User(name);
        userRepository.save(user);
        // 插入用户之后,我们插入一条用户笔记
        noteService.insertNote(name + "'s note");
    }
复制代码

对应的NoteService中的 insertNote 方法如下:

@Transactional(rollbackFor = Exception.class)
    public void insertUser(String name) {
        User user = new User(name);
        userRepository.save(user);
        noteService.insertNote(name + "'s note");
    }
复制代码

现在我们再定义一个测试方法,注意要把自动回滚关闭:

@Test
    @Rollback(value = false)
    public void test() {
        userService.insertUser("hikari");
    }
复制代码

看一下现在的执行结果:

浅谈Spring事务中的7种传播特性
浅谈Spring事务中的7种传播特性

REQUIRED

如果不存在外层事务,就主动开启事务;否则使用外层事务

虽然该类型是默认的传播特性,不过我们还是手动指定一下,要记住的是,传播特性是作用于 内层方法 上的,所以我们加在外层方法上是无效的:

@Transactional(rollbackFor = Exception.class,  propagation = Propagation.REQUIRED)
    public void insertNote(String content) {
        Note note = new Note(content);
        noteRepository.save(note);
    }
复制代码

但是目前这两个方法没有任何干扰,所以我们手动制造点异常:

外层方法关闭事务,内层方法抛出异常

// @Transactional(rollbackFor = Exception.class)    // ←
    public void insertUser(String name) {
        User user = new User(name);
        userRepository.save(user);
        noteService.insertNote(name + "'s note");
    }

    @Transactional(rollbackFor = Exception.class,  propagation = Propagation.REQUIRED)
    public void insertNote(String content) {
        Note note = new Note(content);
        noteRepository.save(note);
        
        throw new RuntimeException();   // ←
    }
复制代码

按照REQUIRED传播特性,如果外层方法没有使用事务,则内层方法会主动开启事务,运行结果如下:

浅谈Spring事务中的7种传播特性
浅谈Spring事务中的7种传播特性

我们可以发现外层方法没有使用事务(user表中有数据),而内层方法使用了事务(note表进行了回滚,所以无数据)

外层方法抛出异常

@Transactional(rollbackFor = Exception.class)
    public void insertUser(String name) {
        User user = new User(name);
        userRepository.save(user);
        noteService.insertNote(name + "'s note");
        throw new RuntimeException();   // ←
    }
    
    @Transactional(rollbackFor = Exception.class,  propagation = Propagation.REQUIRED)
    public void insertNote(String content) {
        Note note = new Note(content);
        noteRepository.save(note);
    }
复制代码

运行结果:

浅谈Spring事务中的7种传播特性
浅谈Spring事务中的7种传播特性

两个表中均无数据,说明外层方法和内层方法使用的是一个事务,所以发生异常一起回滚

SUPPORTS

如果不存在外层事务,就不开启事务;否则使用外层事务

为了避免阅读疲劳, 存在外层事务则使用同一个事务 这个特性就不演示了,我们演示前一个特性:

外层方法关闭事务,内层方法抛出异常

//  @Transactional(rollbackFor = Exception.class)   // ←
    public void insertUser(String name) {
        User user = new User(name);
        userRepository.save(user);
        noteService.insertNote(name + "'s note");
    }
    
    @Transactional(rollbackFor = Exception.class,  propagation = Propagation.SUPPORTS)
    public void insertNote(String content) {
        Note note = new Note(content);
        noteRepository.save(note);
        throw new RuntimeException();   // ←
    }
复制代码

结果如下:

浅谈Spring事务中的7种传播特性

其中note表有数据,说明内层方法没有启用事务

MANDATORY

如果不存在外层事务,就抛出异常;否则使用外层事务

外层方法关闭事务

//  @Transactional(rollbackFor = Exception.class)   // ←
    public void insertUser(String name) {
        User user = new User(name);
        userRepository.save(user);
        noteService.insertNote(name + "'s note");
    }
    
    @Transactional(rollbackFor = Exception.class,  propagation = Propagation.MANDATORY)
    public void insertNote(String content) {
        Note note = new Note(content);
        noteRepository.save(note);
    }
复制代码

运行结果:

org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'
复制代码

说明必须要有外部事务的存在才能执行

与上面类似, “如果外层方法事务存在,则内层方法使用同一个事务” 这个特性在这里也不赘述了

REQUIRES_NEW

总是主动开启事务;如果存在外层事务,就将外层事务挂起

外层方法抛出异常

@Transactional(rollbackFor = Exception.class)
    public void insertUser(String name) {
        User user = new User(name);
        userRepository.save(user);
        noteService.insertNote(name + "'s note");
        throw new RuntimeException();   // ←
    }
    
    @Transactional(rollbackFor = Exception.class,  propagation = Propagation.REQUIRES_NEW)
    public void insertNote(String content) {
        Note note = new Note(content);
        noteRepository.save(note);
    }
复制代码

运行结果:

浅谈Spring事务中的7种传播特性
浅谈Spring事务中的7种传播特性

我们可以看到,外层方法的事务被回滚,而内层方法的事务并没有跟着一起回滚,所以它们使用的不是同一个事务,两个事务不会相互影响

但是有些人可能会觉得,如果内层方法抛出异常,外层方法的事务应该也不会回滚吧?很遗憾并不是这样的,不要被事务迷惑了, 内层方法抛出异常(未被try-catch捕获),相当于外层方法抛出异常 ,所以外层方法的事务依然会回滚

NOT_SUPPORTED

总是不开启事务;如果存在外层事务,就将外层事务挂起

内层方法抛出异常

@Transactional(rollbackFor = Exception.class)
    public void insertUser(String name) {
        User user = new User(name);
        userRepository.save(user);
        noteService.insertNote(name + "'s note");
    }
    
    @Transactional(rollbackFor = Exception.class,  propagation = Propagation.NOT_SUPPORTED)
    public void insertNote(String content) {
        Note note = new Note(content);
        noteRepository.save(note);
        throw new RuntimeException();   // ←
    }
复制代码

运行结果:

浅谈Spring事务中的7种传播特性
浅谈Spring事务中的7种传播特性

user表中无数据,说明外层方法使用了事务,发生了回滚;而note表中有数据,说明没有回滚,没有启用事务

NEVER

总是不开启事务;如果存在外层事务,则抛出异常

外层方法开启事务

@Transactional(rollbackFor = Exception.class)
    public void insertUser(String name) {
        User user = new User(name);
        userRepository.save(user);
        noteService.insertNote(name + "'s note");
    }
    
    @Transactional(rollbackFor = Exception.class,  propagation = Propagation.NEVER)
    public void insertNote(String content) {
        Note note = new Note(content);
        noteRepository.save(note);
    }
复制代码

运行结果:

org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'
复制代码
浅谈Spring事务中的7种传播特性
浅谈Spring事务中的7种传播特性

MANDATORY 的特性正好相反, MANDATORY 是当外层方法不存在事务抛出异常,而 NEVER 是当外层方法存在事务抛出异常

NESTED

如果不存在外层事务,就主动创建事务;否则创建嵌套的子事务

外层方法抛出异常

@Transactional(rollbackFor = Exception.class)
    public void insertUser(String name) {
        User user = new User(name);
        userRepository.save(user);
        noteService.insertNote(name + "'s note");
        throw new RuntimeException();   // ←
    }
    
    @Transactional(rollbackFor = Exception.class,  propagation = Propagation.NESTED)
    public void insertNote(String content) {
        Note note = new Note(content);
        noteRepository.save(note);
    }
复制代码

运行结果:

org.springframework.transaction.NestedTransactionNotSupportedException: JpaDialect does not support savepoints - check your JPA provider's capabilities
复制代码

有些人可能会遇到这样的错误,是因为jpa/hibernate是不支持“savepoint”特性的,而 NESTED 依赖于 “savepoint” 机制,可以在内层方法执行前创建一个“暂存点”,然后开启嵌套的子事务,如果子事务发生异常,会直接回滚到这个暂存点,而不会导致整体事务的回滚

不过没关系,我们可以使用jdbcTemplate,修改后的方法如下:

@Transactional(rollbackFor = Exception.class)
    public void insertUser(String name) {
        User user = new User(name);
        userDao.insertUser(user);
        noteService.insertNote(name + "'s note");
        throw new RuntimeException();
    }
    
    @Transactional(rollbackFor = Exception.class,  propagation = Propagation.NESTED)
    public void insertNote(String content) {
        Note note = new Note(content);
        noteDao.insertNote(note);
    }
复制代码

这里就不做测试了( 其实是因为代码还是有bug,等调好了我会把执行结果放上来 ),执行结果是两表中均无数据,因为 嵌套的子事务依然属于外层的事务 ,所以外层事务的回滚会连带着嵌套的子事务一起回滚

NESTED/REQUIRES_NEWREQUIRED 有什么区别? NESTEDREQUIRES_NEW 有什么区别?

NESTED/REQUIRES_NEWREQUIRED 的区别

先来谈一下它们之间的相同点,如果外层方法不存在时,这两种方式均会创建一个新事务,这一点是一致的

不同点在于,如果外层方法存在事务时, REQUIRED 会使用外层同一个事务,而 NESTED 会创建一个嵌套的子事务,这两种方式最重要的区别就在这里: 如果内层方法抛出异常,当使用 REQUIRED 方式时,即使在外层方法捕获了该异常,也依然会导致外层事务回滚(因为使用的是同一个事务);而如果使用 NESTEDREQUIRES_NEW 的方式,只要在外层方法捕获了该异常,就不会导致外层事务回滚

@Transactional(rollbackFor = Exception.class)
    public void insertUser(String name) {
        User user = new User(name);
        userRepository.save(user);
        try {
            noteService.insertNote(name + "'s note");
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
    
    @Transactional(rollbackFor = Exception.class,  propagation = Propagation.REQUIRED)
    public void insertNote(String content) {
        Note note = new Note(content);
        noteRepository.save(note);
        throw new RuntimeException();
    }
复制代码

运行结果:

浅谈Spring事务中的7种传播特性
浅谈Spring事务中的7种传播特性

两张表均无数据,即使外层方法捕获了内层方法的异常,还是会导致整体回滚,因为使用的是同一个事务

NESTEDREQUIRES_NEW 的区别

这两种方式都相当于开了一个新事务,但是它们之间最重要的区别就是, NESTED 是一个嵌套的子事务,如果外层事务回滚,则这个子事务会被一起回滚,而 REQUIRES_NEW 的方法则不会

原文  https://juejin.im/post/5d764187e51d4561da6201c4
正文到此结束
Loading...