当前位置: 首页 > article >正文

掌握 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 属性用于设置事务的超时时间,单位为秒,如果事务运行时间超过这个值,事务管理器将回滚事务。


关于rollbackForREQUIRED、REQUIRED_NEW的详细使用及事务失效场景,可参考上一篇:掌握 Spring 事务管理:深入理解 @Transactional 注解(一)


http://www.kler.cn/a/412104.html

相关文章:

  • 使用UKEY进行数字签名和加密 -- HSM、PKCS#11与Signtool
  • redmi 12c 刷机
  • 对于公平与效率的关系问题,材料中有两种不同倾向性的观点,请对这两种观点分别加以概述并谈谈你的看法。字数不超过500字。
  • 计算机网络的功能
  • 旋转磁体产生的场 - 实验视频资源下载
  • 0BB定位胶具有优异的焊带粘接性和可靠性,助力客户降本增效
  • HTTP 缓存技术
  • Cannot find a valid baseurl for repo: centos-sclo-rh/x86_64
  • Linux的前台进程和后台进程
  • Git旧文件覆盖引发思考
  • Day 27 贪心算法 part01
  • 排序算法(六)--堆排序
  • Linux17 Git 指令
  • NIO三大组件
  • OpenAI 是怎么“压力测试”大型语言模型的?
  • C#中面试的常见问题005
  • 【ArcGIS Pro实操第10期】统计某个shp文件中不同区域内的站点数
  • 探索Python自动化新境界:Helium库揭秘
  • 三六零[601360]行情数据接口
  • Angular面试题汇总系列一
  • 玩转 Burp Suite (1)
  • 硬菜!高精度!BO-Transformer贝叶斯优化编码器多特征分类预测/故障诊断
  • 【jupyter】linux服务器怎么使用jupyter
  • Android 网络通信(三)OkHttp实现登入
  • 【es6进阶】vue3中的数据劫持的最新实现方案的proxy的详解
  • java-使用HSSFWorkbook编辑excel文件