spring事务及其失效原因分析


最近项目开发过程中,遇到了InnoDB锁行的事,苦于在找问题中,想来挖空心思的想,于是就记录下自己整个查找过程,也顺便给自己梳理一下spring 的事务。

spring 事务管理

数据库事务是指将一系列数据操作当做一个逻辑处理单元的操作,这个单元中的数据库操作要么全部执行,要么完全不执行,通过将一组相关操作组合为一个逻辑处理单元,可以简化错误恢复,并使应用逻辑更加可靠。

事务的特性

  • 原子性
  • 一致性
  • 隔离性
  • 持久性

事务的隔离级别

  • READ_UNCOMMITTED
  • READ_COMMITTED
  • REPEATABLE_READ
  • SERIALIZABLE

以上详细可参考我之前写的 mysql 锁中详细描述
https://blog.csdn.net/renguiriyue/article/details/103338569

spring 事务隔离级别

spring 对事务的支持提供了5中隔离级别
在这里插入图片描述

spring 事务传播行为

事务传播行为是用来描述由某一个事务传播行为修饰的方法被嵌套进另一个方法的时候,事务的传播特性。
在这里插入图片描述

spring 事务的失效原因

1,使用@Transactional的时候贪图简单没有写里面的rollbackFor = Exception.class,那只有当发生unchecked异常的时候才能事务回滚,checked的异常事务无效。

单一类举证异常回滚情况

	/**
     * 发生未被捕获(unchecked)的runtimeException异常时,回滚事务
     * 结果:project 对象未插入数据库
     */
    @Transactional
    @Override
    public void testTransaionByException() {
        Project project = new Project();
        project.setProjectName("测试数据1");
        projectMapper.insert(project);
        // 做异常操作
        int i = 1/0;
    }
	/**
     * 发生被捕获(checked)但没有抛出的情况的runtimeException异常时,事务回滚失效
     * 结果:project 对象插入数据库
     */
    @Transactional
    @Override
    public void testTransaionByException() {
        Project project = new Project();
        project.setProjectName("测试数据2");
        projectMapper.insert(project);
        // 做异常操作
        try {
            int i = 1/0;
        }catch (Exception e){
            e.printStackTrace();
        }
    }
	/**
     * 发生被捕获(checked)有抛出的情况的runtimeException异常时,事务回滚
     * 如果throws Exception 在方法上,事务回滚失效
     * 结果:project 对象未插入数据库
     */
    @Transactional
    @Override
    public void testTransaionByException() {
        Project project = new Project();
        project.setProjectName("测试数据2");
        projectMapper.insert(project);
        // 做异常操作
        try {
            int i = 1/0;
        }catch (Exception e){
            throw new ServiceException("error");
        }
    }

这里测试下来,会发现貌似这个rollbackFor = Exception.class 属性没啥用处啊,其实并非如此,这里展示的仅仅都是runtimeException 异常,他的父类是Exception ,但是异常还有其他很多,比如IOException、SQLException等。具体大家可参考以下文章
https://www.cnblogs.com/clwydjgs/p/9317849.html

多个类举证异常回滚情况

	/**
     * 发生被捕获(checked)但没有抛出的情况的runtimeException异常时,事务回滚失效
     * 结果:project 和 company 对象都插入数据库
	 *
	 * 同理:如果try catch 中 抛出异常,或 unchecked 异常,那么事务将回滚成功
     */
    @Transactional
    @Override
    public void testTransaionByException () {
        Project project = new Project();
        project.setProjectName("测试数据2");

        projectMapper.insert(project);
        // 做异常操作
        try {
            int i = 1/0;
        }catch (Exception e){
            e.printStackTrace();
//            throw new ServiceException("error");
        }

        Company company = new Company();
        company.setName("测试123");
        companyMapper.insert(company);

    }

## 事务中调用其他非事务方法

  • 1,测试事务方法中抛异常
  • 2,测试非事务方法中抛异常
  • 3,测试1-2中抛异常未抛出
  • 4,测试1-2中抛异常抛出
	/**
	 * 当 int i = 1/0; 无论放在哪个方法块中。
     * 发生未被捕获(unchecked)的runtimeException异常时,回滚事务
     * 结果:1和2测试case, project company 对象未插入数据库
	 * 
	 * 结果:3测试case,抛异常未抛出,project company 对象插入数据库
	 *
	 * 结果:4测试case,抛异常抛出,project company 对象未插入数据库
     */
    @Transactional
    @Override
    public void testTransaionByException () {
        Project project = new Project();
        project.setProjectName("测试数据2");
        project.setCompanyId(29L);
        project.setProjectAddress("测试地址2");
        projectMapper.insert(project);
        this.transaionTwo();
        // 做异常操作
        int i = 1/0;
    }
    
	//serviceImpl 中的私有方法
    private void transaionTwo(){
    	// int i = 1/0;
        Company company = new Company();
        company.setName("测试123");
        companyMapper.insert(company);
    }

再看看在两个事务中调用抛出异常,会发生什么事吧

  • 1,测试事务方法中各自抛异常,不捕获
  • 2,测试事务方法中各自抛异常,捕获,不抛出
  • 3,测试事务方法中各自抛异常,捕获,抛出
	/**
     * 当 int i = 1/0; 无论放在哪个方法块中。
     * 发生未被捕获(unchecked)的runtimeException异常时,回滚事务
     * 结果:1测试case, project company 对象未插入数据库
	 * 
	 * 结果:2测试case,抛异常未抛出,project company 对象都插入数据库
	 *
	 * 结果:3测试case,抛异常抛出,project company 对象未插入数据库
	 * 
     */
    @Transactional
    @Override
    public void testTransaionByException () {
        Project project = new Project();
        project.setProjectName("测试数据2");
        projectMapper.insert(project);
        transaionTwo();
        //try {
            int i = 1/0;
        //}catch (Exception e){
            //e.printStackTrace();
        //}
    }

    @Transactional
    @Override
    public void transaionTwo(){
		//try {
            int i = 1/0;
        //}catch (Exception e){
            //e.printStackTrace();
        //}
        Company company = new Company();
        company.setName("测试123");
        companyMapper.insert(company);

    }

事务未生效的可能还有很多种情况,但是日常开发中 不使用 rollbackFor = Exception.class 这个属性,我觉得是非常不应该的,包括我上面的例子也是偷懒,所以奉劝大家不要懒。
其他事务可能不生效的问题,大家可以参考以下博客
https://blog.csdn.net/qq32933432/article/details/50772004?utm_source=app

本来想讲讲事务之间的传递会出现什么意外,但是今天和我们公司的大牛聊了一下,他的建议和我讲的mysql 中发生死锁的情况相似,只要注意编码方面,就可以基本避免了。因为最近在弄的是hibernate 项目,所以我想hibernate 的东西整理一下,然后再理理。

发布了46 篇原创文章 · 获赞 6 · 访问量 2657

猜你喜欢

转载自blog.csdn.net/renguiriyue/article/details/103436782