掌握 Spring 事务管理:深入理解 @Transactional 注解(二)
在 Spring 框架中,@Transactional
注解是一个强大的工具,用于声明方法或类级别的事务边界。通过这个注解,我们可以轻松地管理数据库事务,确保数据的一致性和完整性。
常用属性
propagation
propagation
属性定义了事务的传播行为,即当前方法如何与现有事务相互作用。
以下是一些常用的传播行为:
REQUIRED(默认)
如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
场景一:当前存在事务
@Service
@Slf4j
public class AdminService {
@Autowired
private AdminRepository adminRepository;
@Autowired
private AddressService addressService;
@Transactional
public void saveAdminWrong3(String name) {
//1.保存用户信息
saveAdmin(name);
//2.保存扩展信息
addressService.saveAddress(name);
}
private void saveAdmin(String name) {
Admin admin = new Admin();
admin.setName(name);
adminRepository.save(admin);
log.info("save admin success");
}
}
@Service
@Slf4j
public class AddressService {
@Autowired
private AddressRepository addressRepository;
@Transactional
public void saveAddress(String name) {
Address address = new Address();
address.setName(name);
log.info("saveAddress start");
addressRepository.save(address);
throw new RuntimeException("模拟 save address 失败");
}
}
- 当调用saveAdminWrong3时,会开启一个事务A;
- 在saveAdminWrong3中去调用saveAddress,saveAddress不会开启新事务,而是加入到事务 A 中;
- 此时这两个方法在同一个事务 A 中,无论哪个方法出现异常,事务都会回滚。
场景二:当前无事务
修改代码,删除saveAdminWrong3
上的@Transactional
。
public void saveAdminWrong3(String name) {
//1.保存用户信息
saveAdmin(name);
//2.保存扩展信息
addressService.saveAddress(name);
}
- 调用saveAdminWrong3时,不会开启事务;
- 在saveAdminWrong3中去调用saveAddress,saveAddress会开启一个新事务T;
- saveAddress 执行出现异常时,事务 T 会回滚;
- saveAdminWrong3没有开启事务,其操作不会被回滚。
REQUIRES_NEW
总是会创建一个新的事务,如果当前存在事务,则将当前事务挂起。
修改代码如下:
@Transactional
public void saveAdminWrong3(String name) {
//1.保存用户信息
saveAdmin(name);
//2.保存扩展信息
try {
addressService.saveAddress(name);
} catch (Exception e) {
log.error("扩展信息异常:",e);
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveAddress(String name) {
Address address = new Address();
address.setName(name);
log.info("saveAddress start");
addressRepository.save(address);
throw new RuntimeException("模拟 save address 失败");
}
- 当调用saveAdminWrong3时,会开启一个事务 T1;
- 在saveAdminWrong3中调用saveAddress时,saveAddress会开启一个新事务 T2;
- 事务 T1 被挂起,直到事务 T2 完成;
- 任何一个方法出现异常,只会影响当前方法所在事务,触发回滚。
SUPPORTS
如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行。
场景一:当前存在事务
@Transactional
public void saveAdminWrong3(String name) {
//1.保存用户信息
saveAdmin(name);
//2.保存扩展信息
try {
addressService.saveAddress(name);
} catch (Exception e) {
log.error("扩展信息异常:",e);
}
}
@Transactional(propagation = Propagation.SUPPORTS)
public void saveAddress(String name) {
Address address = new Address();
address.setName(name);
log.info("saveAddress start");
addressRepository.save(address);
throw new RuntimeException("模拟 save address 失败");
}
- 当调用saveAdminWrong3时,会开启一个事务 T1;
- 在saveAdminWrong3中调用saveAddress时,saveAddress不会开启新事务,而是会加入到事务 T1 中;
此时这两个方法在同一个事务T1 中,无论哪个方法出现异常,事务都会回滚。
场景二:当前无事务
public void saveAdminWrong3(String name) {
//1.保存用户信息
saveAdmin(name);
//2.保存扩展信息
try {
addressService.saveAddress(name);
} catch (Exception e) {
log.error("扩展信息异常:",e);
}
}
@Transactional(propagation = Propagation.SUPPORTS)
public void saveAddress(String name) {
Address address = new Address();
address.setName(name);
log.info("saveAddress start");
addressRepository.save(address);
throw new RuntimeException("模拟 save address 失败");
}
- 当调用saveAdminWrong3时,不会开启事务;
- 在saveAdminWrong3中调用saveAddress时,saveAddress也不会开启事务;
- 两个方法在执行过程中,出现任何异常都不会回滚。
MANDATORY
如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
场景一:当前存在事务
@Transactional
public void saveAdminWrong3(String name) {
//1.保存用户信息
saveAdmin(name);
//2.保存扩展信息
try {
addressService.saveAddress(name);
} catch (Exception e) {
log.error("扩展信息异常:",e);
}
}
@Transactional(propagation = Propagation.MANDATORY)
public void saveAddress(String name) {
Address address = new Address();
address.setName(name);
log.info("saveAddress start");
addressRepository.save(address);
throw new RuntimeException("模拟 save address 失败");
}
- 当调用saveAdminWrong3时,会开启一个事务T1;
- 在saveAdminWrong3中去调用saveAddress,saveAddress不会开启新事务,而是加入到事务T1中;
此时这两个方法在同一个事务T1中,无论哪个方法出现异常,事务都会回滚。
场景二:当前无事务
public void saveAdminWrong3(String name) {
//1.保存用户信息
saveAdmin(name);
//2.保存扩展信息
try {
addressService.saveAddress(name);
} catch (Exception e) {
log.error("扩展信息异常:",e);
}
}
@Transactional(propagation = Propagation.MANDATORY)
public void saveAddress(String name) {
Address address = new Address();
address.setName(name);
log.info("saveAddress start");
addressRepository.save(address);
throw new RuntimeException("模拟 save address 失败");
}
- 当调用saveAdminWrong3时,不会开启事务;
- 在saveAdminWrong3中去调用saveAddress,saveAddress会抛出异常;
异常信息:
org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'
NEVER
如果当前存在事务,则抛出异常;如果当前没有事务,则以非事务方式执行。
场景一:当前存在事务
@Transactional
public void saveAdminWrong3(String name) {
//1.保存用户信息
saveAdmin(name);
//2.保存扩展信息
try {
addressService.saveAddress(name);
} catch (Exception e) {
log.error("扩展信息异常:",e);
}
}
@Transactional(propagation = Propagation.NEVER)
public void saveAddress(String name) {
Address address = new Address();
address.setName(name);
log.info("saveAddress start");
addressRepository.save(address);
}
- 当调用saveAdminWrong3时,会开启一个事务 T1;
- 在saveAdminWrong3中去调用saveAddress,saveAddress会抛出异常;
- 即saveAddress不能被存在事务的方法调用。
场景二:当前无事务
public void saveAdminWrong3(String name) {
//1.保存用户信息
saveAdmin(name);
//2.保存扩展信息
try {
addressService.saveAddress(name);
} catch (Exception e) {
log.error("扩展信息异常:",e);
}
}
@Transactional(propagation = Propagation.NEVER)
public void saveAddress(String name) {
Address address = new Address();
address.setName(name);
log.info("saveAddress start");
addressRepository.save(address);
}
- 当调用saveAdminWrong3时,不会开启事务;
- 在saveAdminWrong3中去调用saveAddress,saveAddress也不会开启事务,以普通方法执行。
NOT_SUPPORTED
总是非事务地执行,如果当前存在事务,则将当前事务挂起。
场景一:当前存在事务
@Transactional
public void saveAdminWrong3(String name) {
//1.保存用户信息
saveAdmin(name);
//2.保存扩展信息
try {
addressService.saveAddress(name);
} catch (Exception e) {
log.error("扩展信息异常:",e);
}
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void saveAddress(String name) {
Address address = new Address();
address.setName(name);
log.info("saveAddress start");
addressRepository.save(address);
throw new RuntimeException("模拟 save address 失败");
}
- 当调用saveAdminWrong3时,会开启一个事务 T1;
- 在saveAdminWrong3中去调用saveAddress,saveAddress不会开启新事务,也不会加入事务 T1,而是事务 T1 会被挂起,直到saveAddress执行完成;
- 如果saveAdminWrong3出现异常,事务 T1 回滚,而saveAddress不会受到影响。
场景二:当前无事务
public void saveAdminWrong3(String name) {
//1.保存用户信息
saveAdmin(name);
//2.保存扩展信息
try {
addressService.saveAddress(name);
} catch (Exception e) {
log.error("扩展信息异常:",e);
}
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void saveAddress(String name) {
Address address = new Address();
address.setName(name);
log.info("saveAddress start");
addressRepository.save(address);
throw new RuntimeException("模拟 save address 失败");
}
- 当调用saveAdminWrong3时,不会开启事务;
- 在saveAdminWrong3中去调用saveAddress,saveAddress也不会开启事务;
- 两个方法都是以普通方法执行的,出现任何错误都不会回滚。
NESTED
如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则表现得像 REQUIRED,创建一个新的事务。
场景一:当前存在事务
@Transactional
public void saveAdminWrong3(String name) {
//1.保存用户信息
saveAdmin(name);
//2.保存扩展信息
try {
addressService.saveAddress(name);
} catch (Exception e) {
log.error("扩展信息异常:",e);
}
}
@Transactional(propagation = Propagation.NESTED)
public void saveAddress(String name) {
Address address = new Address();
address.setName(name);
log.info("saveAddress start");
addressRepository.save(address);
throw new RuntimeException("模拟 save address 失败");
}
- 当调用saveAdminWrong3时,会开启一个事务 T1;
- 在saveAdminWrong3中去调用saveAddress,saveAddress会开启新的事务 T1_1;
- 如果saveAdminWrong3出现异常,事务 T1 和事务 T1_1 都会回滚;
- 如果saveAddress出现异常,事务T1_1 回滚;
- 如果saveAdminWrong3捕获了saveAddress的异常,事务 T1不会回滚;
- 如果saveAdminWrong3没有捕获saveAddress的异常,事务 T1也会回滚。
场景二:当前无事务
public void saveAdminWrong3(String name) {
//1.保存用户信息
saveAdmin(name);
//2.保存扩展信息
try {
addressService.saveAddress(name);
} catch (Exception e) {
log.error("扩展信息异常:",e);
}
}
@Transactional(propagation = Propagation.NESTED)
public void saveAddress(String name) {
Address address = new Address();
address.setName(name);
log.info("saveAddress start");
addressRepository.save(address);
throw new RuntimeException("模拟 save address 失败");
}
- 当调用saveAdminWrong3时,不会开启事务;
- 在saveAdminWrong3中去调用saveAddress,saveAddress会开启新的事务 T1;
- 如果saveAddress执行异常,则事务 T1 会回滚;
isolation
isolation
属性定义了事务的隔离级别,它控制事务在并发环境下如何与其他事务隔离。
以下是一些常用的隔离级别:
- DEFAULT:使用底层数据库的默认隔离级别,是
spring
默认设置。 - READ_UNCOMMITTED:最低的隔离级别,允许读取未提交的数据。
- READ_COMMITTED:保证读取的数据是已提交的,但可能会出现脏读。
- REPEATABLE_READ:保证在同一事务中多次读取同样的记录结果是一致的。
- SERIALIZABLE:最高的隔离级别,完全串行化的事务执行,避免脏读、不可重复读和幻读。
rollbackFor
rollbackFor
属性用于指定哪些异常需要回滚事务。
默认情况下,所有未检查的异常(RuntimeException
的子类)和错误(Error
)都会触发回滚。通过 rollbackFor
,可以指定特定的异常类,使得只有当这些异常发生时,事务才会回滚。
noRollbackFor
与 rollbackFor
相反,noRollbackFor
用于指定哪些异常不需要回滚事务。
这通常用于处理业务逻辑中的已检查异常,这些异常不应该导致事务回滚。
timeout
timeout
属性用于设置事务的超时时间,单位为秒,如果事务运行时间超过这个值,事务管理器将回滚事务。
关于rollbackFor
、REQUIRED、REQUIRED_NEW
的详细使用及事务失效场景,可参考上一篇:掌握 Spring 事务管理:深入理解 @Transactional 注解(一)