简单来说,就是当系统中存在两个事务方法时(咱们暂称为方法A和方法B),若是方法B在方法A中被调用,那么将采用什么样的事务形式,就叫作事务的传播特性java
好比,A方法调用了B方法(B方法必须使用事务注解),那么B事务能够是一个在A中嵌套的事务,或者B事务不使用事务,又或是使用与A事务相同的事务,这些都可以经过指定事务传播特性来实现spring
首先使用org.springframework.transaction.annotation
包下的@Transactional
注解,在其中声明propagation属性便可(默认值为Propagation.REQUIRED):springboot
@Transactional(propagation = Propagation.REQUIRED)
复制代码
目前,Spring在TransactionDefinition
类中定义了如下7种传播特性,具体特性咱们接下来会分析:ide
有些人可能会以为,为何非要指定传播特性不可,咱们全部方法执行都开启一个事务不能够吗?这里我暂时不作解释,看完下一个部分相信就有答案了测试
在具体说明前,先来作一些准备工做spa
首先定义两张表user
和note
,user表有id
和name
两个数据列,note表有id
和content
两个数据列hibernate
而后新建springboot项目,建立对应的User/Note类,以及dao和service接口等等部分(为了方便演示,Service我直接建立的类,没有使用接口),就再也不一一列出了3d
接着重点来了,咱们先在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
方法以下:code
@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");
}
复制代码
看一下如今的执行结果:
若是不存在外层事务,就主动开启事务;不然使用外层事务
虽然该类型是默认的传播特性,不过咱们仍是手动指定一下,要记住的是,传播特性是做用于内层方法上的,因此咱们加在外层方法上是无效的:
@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传播特性,若是外层方法没有使用事务,则内层方法会主动开启事务,运行结果以下:
外层方法抛出异常
@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);
}
复制代码
运行结果:
两个表中均无数据,说明外层方法和内层方法使用的是一个事务,因此发生异常一块儿回滚
若是不存在外层事务,就不开启事务;不然使用外层事务
为了不阅读疲劳,存在外层事务则使用同一个事务
这个特性就不演示了,咱们演示前一个特性:
外层方法关闭事务,内层方法抛出异常
// @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(); // ←
}
复制代码
结果以下:
其中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.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'
复制代码
说明必需要有外部事务的存在才能执行
与上面相似,“若是外层方法事务存在,则内层方法使用同一个事务” 这个特性在这里也不赘述了
老是主动开启事务;若是存在外层事务,就将外层事务挂起
外层方法抛出异常
@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);
}
复制代码
运行结果:
咱们能够看到,外层方法的事务被回滚,而内层方法的事务并无跟着一块儿回滚,因此它们使用的不是同一个事务,两个事务不会相互影响
可是有些人可能会以为,若是内层方法抛出异常,外层方法的事务应该也不会回滚吧?很遗憾并非这样的,不要被事务迷惑了,内层方法抛出异常(未被try-catch捕获),至关于外层方法抛出异常,因此外层方法的事务依然会回滚
老是不开启事务;若是存在外层事务,就将外层事务挂起
内层方法抛出异常
@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(); // ←
}
复制代码
运行结果:
user表中无数据,说明外层方法使用了事务,发生了回滚;而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.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'
复制代码
和MANDATORY
的特性正好相反,MANDATORY
是当外层方法不存在事务抛出异常,而NEVER
是当外层方法存在事务抛出异常
若是不存在外层事务,就主动建立事务;不然建立嵌套的子事务
外层方法抛出异常
@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_NEW
和REQUIRED
有什么区别?NESTED
和REQUIRES_NEW
有什么区别?NESTED/REQUIRES_NEW
和REQUIRED
的区别先来谈一下它们之间的相同点,若是外层方法不存在事务时,这两种方式均会建立一个新事务,这一点是一致的
不一样点在于,若是外层方法存在事务时,REQUIRED
会和外层方法使用同一个事务,而NESTED
会建立一个嵌套的子事务,这两种方式最重要的区别就在这里:若是内层方法抛出异常,当使用REQUIRED
方式时,即便在外层方法捕获了该异常,也依然会致使外层事务回滚(由于使用的是同一个事务);而若是使用NESTED
或REQUIRES_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();
}
复制代码
运行结果:
两张表均无数据,即便外层方法捕获了内层方法的异常,仍是会致使总体回滚,由于使用的是同一个事务
NESTED
和REQUIRES_NEW
的区别这两种方式都至关于开了一个新事务,可是它们之间最重要的区别就是,NESTED
是一个嵌套的子事务,若是外层事务回滚,则这个子事务会被一块儿回滚,而REQUIRES_NEW
的方法则不会
不重要的项目/普通场景/懒得考虑那么多
什么都不填 / REQUIRED
内层方法与外层方法几乎没有关联,至关于独立执行
REQUIRES_NEW
内层方法依赖于外层方法,可是外层方法不但愿被内层方法影响
在插入用户信息后向日志表中插入一条记录
NESTED
内层方法须要和外层方法的操做同步,发生异常时要么都不回滚,要么一块儿回滚
SUPPORTS
内层方法不启用事务,可是能够容许外层方法启用事务
NOT_SUPPORTED
内层方法不启用事务,也不容许外层方法启用事务
NEVER
内层方法必须开启事务,同时在逻辑上依赖于外层方法
MANDATORY