Spring事物失效的几种场景

1. 业务概述

日常开发中事物的使用非常常见,就是为了保证数据库的四大特性ACID,原子,一致,隔离,持久中的原子性,尤其是一个接口涉及多个操作,又要保证操作的数据原子性,事物就是解决此问题的,但是在使用过程中会存在一些事物失效的场景。

在这里插入图片描述

2. 异常场景

2.1 事务不生效

2.1.1 访问权限问题

众所周知,java的访问权限主要有四种:private、default、protected、public,它们的权限从左到右,依次变大。

但如果我们在开发过程中,把有某些事务方法,定义了错误的访问权限,就会导致事务功能出问题,例如:

    /**
     * 事务不生效
     * 1.访问权限问题。
     * private void add(Goods goods) {}
     * 虽然这种情况理论上存在,但是实际开发中绝对不会存在!!!
     * 因为private修饰的方法其他类调用不了,编译就会不通过。
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    //private void addPrivate(Goods goods) {
    public void addPrivate(Goods goods) {
        insert(goods);
        goods.setGoodsName(goods.getGoodsName() + "up");
        updateException(goods);
    }

2.1.2 方法用final修饰

有时候,某个方法不想被子类重新,这时可以将该方法定义成final的。普通方法这样定义是没问题的,但如果将事务方法定义成final,例如:


    /**
     * 事务不生效
     * 2. 方法用final修饰
     * 提示:Methods annotated with '@Transactional' must be overridable
     * 虽然有提示事物注解不能有final修饰,但是可以编译通过
     * 事物不会回滚,新增成功,更新失败
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public final void addFinal(Goods goods) {
        insert(goods);
        goods.setGoodsName(goods.getGoodsName() + "up");
        updateException(goods);
    }

2.1.3 方法内部调用

有时候我们需要在某个Service类的某个方法中,调用另外一个事务方法,比如:


    /**
     * 事务不生效
     * 3.方法内部调用
     * 实现:有时候我们需要在某个Service类的某个方法中,调用另外一个事务方法
     * 验证:事物不会回滚,新增成功,更新失败
     * 如果有些场景,确实想在同一个类的某个方法中,调用它自己的另外一个方法,该怎么办呢?
     * 新加一个Service方法
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addMethod(Goods goods) {
        insert(goods);
        goods.setGoodsName(goods.getGoodsName() + "up");
        // 事物修改
        updateTransactional(goods);
    }

这个方法非常简单,只需要新加一个Service方法,把@Transactional注解加到新Service方法上,把需要事务执行的代码移到新方法中。具体代码如下:

@Servcie
public class ServiceA {
   @Autowired
   prvate ServiceB serviceB;

   public void save(User user) {
         queryData1();
         queryData2();
         serviceB.doSave(user);
   }
 }

 @Servcie
 public class ServiceB {

    @Transactional(rollbackFor=Exception.class)
    public void doSave(User user) {
       addData1();
       updateData2();
    }

 }

2.1.4 未被spring管理

在我们平时开发过程中,有个细节很容易被忽略。即使用spring事务的前提是:对象要被spring管理,需要创建bean实例。

通常情况下,我们通过@Controller、@Service、@Component、@Repository等注解,可以自动实现bean实例化和依赖注入的功能。


    /**
     * 事务不生效
     * 4.未被spring管理
     * 实现:对象要被spring管理,需要创建bean实例。去掉@Controller、@Service、@Component、@Repository等注解,可以自动实现bean实例化和依赖注入的功能。
     * 验证:这种情况下比如去掉@Service,在Controller或者其他service都无法注入,肯定异常,一般不会发生
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addNoSpring(Goods goods) {
        insert(goods);
        goods.setGoodsName(goods.getGoodsName() + "up");
        update(goods);
    }

2.1.5 多线程调用

在实际项目开发中,多线程的使用场景还是挺多的。如果spring事务用在多线程场景中


    /**
     * 事务不生效
     * 5.多线程调用
     * 实现:多线程情况下第一一条还没抛出异常,第二条可能已经成功提交,所以在事物内多线程操作事物失效
     * 验证:事物不会回滚,新增成功,更新失败
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addMulThread(Goods goods) {
        for (int i = 10; i < 15; i++) {
            int finalI = i;
            goods.setId((long) finalI);
            goods.setGoodsName(goods.getGoodsName() + finalI);

            // 异常数据
            if (i == 11 || i == 12) {
                pool.execute(() -> {
                    insertException(goods);
                });
            }
            //正常数据
            pool.execute(() -> {
                insert(goods);
            });
        }
    }

2.1.6 表不支持事务

周所周知,在mysql5之前,默认的数据库引擎是myisam。

它的好处就不用多说了:索引文件和数据文件是分开存储的,对于查多写少的单表操作,性能比innodb更好。

有些老项目中,可能还在用它。

在创建表的时候,只需要把ENGINE参数设置成MyISAM即可:

CREATE TABLE `category` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `one_category` varchar(20) COLLATE utf8mb4_bin DEFAULT NULL,
  `two_category` varchar(20) COLLATE utf8mb4_bin DEFAULT NULL,
  `three_category` varchar(20) COLLATE utf8mb4_bin DEFAULT NULL,
  `four_category` varchar(20) COLLATE utf8mb4_bin DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin

myisam好用,但有个很致命的问题是:不支持事务。

如果只是单表操作还好,不会出现太大的问题。但如果需要跨多张表操作,由于其不支持事务,数据极有可能会出现不完整的情况。

此外,myisam还不支持行锁和外键。

所以在实际业务场景中,myisam使用的并不多。在mysql5以后,myisam已经逐渐退出了历史的舞台,取而代之的是innodb。

2.1.7 未开启事务

如果你使用的是springboot项目,那么你很幸运。因为springboot通过DataSourceTransactionManagerAutoConfiguration类,已经默默的帮你开启了事务。

2.2 事务不回滚

2.2.1 错误的传播特性

其实,我们在使用@Transactional注解时,是可以指定propagation参数的。

该参数的作用是指定事务的传播特性,spring目前支持7种传播特性:

REQUIRED 如果当前上下文中存在事务,那么加入该事务,如果不存在事务,创建一个事务,这是默认的传播属性值。
SUPPORTS 如果当前上下文存在事务,则支持事务加入事务,如果不存在事务,则使用非事务的方式执行。
MANDATORY 如果当前上下文中存在事务,否则抛出异常。
REQUIRES_NEW 每次都会新建一个事务,并且同时将上下文中的事务挂起,执行当前新建事务完成以后,上下文事务恢复再执行。
NOT_SUPPORTED 如果当前上下文中存在事务,则挂起当前事务,然后新的方法在没有事务的环境中执行。
NEVER 如果当前上下文中存在事务,则抛出异常,否则在无事务环境上执行代码。
NESTED 如果当前上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务。
如果我们在手动设置propagation参数的时候,把传播特性设置错了,比如:


    /**
     * 事务不生效
     * 7.未开启事务
     * 实现:如果你使用的是springboot项目,那么你很幸运。因为springboot通过DataSourceTransactionManagerAutoConfiguration类,已经默默的帮你开启了事务。
     * 8.我们在使用@Transactional注解时,是可以指定propagation参数的。
     * 验证:事物不会回滚,新增成功,更新失败
     */
    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.NEVER)
    public void addSpread(Goods goods) {
        insert(goods);
        goods.setGoodsName(goods.getGoodsName() + "up");
        // 由于指定了事物传播属性,所以never下不会回滚
        updateException(goods);
    }

2.2.2 捕获异常

    /**
     * 事务不生效
     * 2.捕获异常
     * 实现:捕获异常后没有抛出,事物检测不到异常自然不会回滚
     * 验证:事物不会回滚,新增成功,更新失败
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addTry(Goods goods) {
        try {
            insert(goods);
            goods.setGoodsName(goods.getGoodsName() + "up");
            // 由于指定了事物传播属性,所以never下不会回滚
            updateException(goods);
        } catch (Exception e) {
            System.out.println("异常啦:" + e);
        }
    }

2.2.3 手动抛了别的异常

即使开发者没有手动捕获异常,但如果抛的异常不正确,spring事务也不会回滚。

    /**
     * 事务不生效
     * 3.手动抛了别的异常
     * 实现:手动捕获异常后抛出,事物检测不到异常,事物不会滚
     * 验证:事物不会回滚,新增成功,更新失败
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addThrow(Goods goods) throws Exception {
        try {
            insert(goods);
            goods.setGoodsName(goods.getGoodsName() + "up");
            // 由于指定了事物传播属性,所以never下不会回滚
            updateException(goods);
        } catch (Exception e) {
            System.out.println("异常啦:" + e);
            throw new Exception(e);
        }
    }

2.2.4 自定义了回滚异常

在使用@Transactional注解声明事务时,有时我们想自定义回滚的异常,spring也是支持的。可以通过设置rollbackFor参数,来完成这个功能。

    /**
     * 事务不生效
     * 4.自定义了回滚异常
     * 实现:指定或者自定义指定异常,事物只检测指定异常,其他异常不会滚
     * 验证:事物不会回滚,新增成功,更新失败
     */
    @Override
    @Transactional(rollbackFor = ArrayIndexOutOfBoundsException.class)
    public void addRollBack(Goods goods) {
        insert(goods);
        goods.setGoodsName(goods.getGoodsName() + "up");
        updateException(goods);
    }

2.2.5 嵌套事务回滚多了

    /**
     * 事务不生效
     * 5.嵌套事务回滚多了
     * 实现:
     * 验证:事物不会回滚,新增成功,更新失败
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addNested(Goods goods) {
        insert(goods);
        goods.setGoodsName(goods.getGoodsName() + "up");
        updateException(goods);
    }

2.3 其他情况

2.3.1 大事务问题

事物内容涉及到比较多的操作,导致超时问题等。

2.3.2 编程试事物

事物分为编程式事务与声明式事务,编程是比较灵狐,手动代码里处理,但是耦合性太强,不移维护。

@Autowired
private TransactionTemplate transactionTemplate;

public void ad(){
	 transactionTemplate.execute(()->{
		// 执行操作
	})
}

3. 实现验证

3.1 GoodsController

package com.example.category.controller;

import com.example.category.entity.Goods;
import com.example.category.service.GoodsService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import com.example.category.entity.Response;

import javax.annotation.Resource;

/**
 * 商品表(Goods)控制层
 * 事务不生效测试
 *
 * @author zrj
 * @since 2021-09-05 10:48:26
 */
@RestController
@Api(tags = "商品管理", description = "商品管理")
@RequestMapping("/goods")
public class GoodsController {

    @Resource
    private GoodsService goodsService;

    /**
     * 事物新增商品
     */
    @ApiOperation(value = "事物新增商品")
    @RequestMapping(value = "add", method = RequestMethod.POST)
    public Response<Integer> add(@RequestBody Goods goods) {
        //正常事物
        //goodsService.addNormal(goods);

        //1访问权限问题
        //goodsService.addPrivate(goods);

        //2方法用final修饰
        //goodsService.addFinal(goods);

        //3方法内部调用
        //goodsService.addMethod(goods);

        //4未被spring管理
        //goodsService.addNoSpring(goods);

        //5多线程调用
        //goodsService.addMulThread(goods);

        //6表不支持事物
        //goodsService.addTableNoSupport(goods);

        //7事物传播错误
        //goodsService.addSpread(goods);

        //8捕获异常
        goodsService.addTry(goods);

        //9手动抛出异常
        //goodsService.addThrow(goods);

        //10自定义回滚
        //goodsService.addRollBack(goods);

        //11嵌套事物回滚异常
        //goodsService.addNested(goods);
        return Response.success("新增成功", null);
    }

    /**
     * 新增商品
     */
    @ApiOperation(value = "新增商品")
    @RequestMapping(value = "insert", method = RequestMethod.POST)
    public Response<Integer> insert(@RequestBody Goods goods) {
        int result = goodsService.insert(goods);
        if (result > 0) {
            return Response.success("新增成功", result);
        }
        return Response.fail("新增失败");
    }

    /**
     * 修改商品
     */
    @ApiOperation(value = "修改商品")
    @RequestMapping(value = "update", method = RequestMethod.PUT)
    public Response<Integer> update(@RequestBody Goods goods) {
        int result = goodsService.update(goods);
        if (result > 0) {
            return Response.success("修改成功", result);
        }
        return Response.fail("修改失败");
    }
}



3.2 GoodsService

package com.example.category.service;

import com.example.category.entity.Goods;

import java.util.List;
import java.util.Map;

/**
 * 商品服务接口
 *
 * @author zrj
 * @since 2021-09-05
 */
public interface GoodsService {

    /**
     * 正常事物
     */
    void addNormal(Goods goods);

    /**
     * 访问权限问题
     */
    void addPrivate(Goods goods);

    /**
     * 方法用final修饰
     */
    void addFinal(Goods goods);

    /**
     * 方法内部调用
     */
    void addMethod(Goods goods);

    /**
     * 未被spring管理
     */
    void addNoSpring(Goods goods);

    /**
     * 多线程调用
     */
    void addMulThread(Goods goods);

    /**
     * 表不支持事物
     */
    void addTableNoSupport(Goods goods);

    /**
     * 错误事物传播特性
     */
    void addSpread(Goods goods);

    /**
     * 捕获异常
     */
    void addTry(Goods goods);

    /**
     * 手动抛出异常
     */
    void addThrow(Goods goods) throws Exception;

    /**
     * 自定义回归异常
     */
    void addRollBack(Goods goods);

    /**
     * 嵌套事物回滚异常
     */
    void addNested(Goods goods);

    /**
     * 新增数据
     */
    int insert(Goods goods);

    /**
     * 异常新增数据
     */
    int insertException(Goods goods);

    /**
     * 修改数据
     */
    int update(Goods goods);

    /**
     * 异常修改数据
     */
    int updateException(Goods goods);

    /**
     * 事物修改数据
     */
    int updateTransactional(Goods goods);

}



3.3 GoodsServiceImpl

package com.example.category.service.impl;

import com.example.category.entity.Goods;
import com.example.category.mapper.GoodsMapper;
import com.example.category.service.GoodsService;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.concurrent.*;

/**
 * 商品服务实现类
 *
 * @author zrj
 * @since 2021-09-05
 */
@Service("goodsService")
public class GoodsServiceImpl implements GoodsService {
    // 自定义线程名称
    private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("thread-%d").build();
    // 定义线程池
    private static ExecutorService pool = new ThreadPoolExecutor(30, 200, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());

    @Resource
    private GoodsMapper goodsMapper;

    /**
     * 正常事务
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addNormal(Goods goods) {
        insert(goods);
        goods.setGoodsName(goods.getGoodsName() + "up");
        update(goods);
    }

    /**
     * 事务不生效
     * 1.访问权限问题。
     * private void add(Goods goods) {}
     * 虽然这种情况理论上存在,但是实际开发中绝对不会存在!!!
     * 因为private修饰的方法其他类调用不了,编译就会不通过。
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    //private void addPrivate(Goods goods) {
    public void addPrivate(Goods goods) {
        insert(goods);
        goods.setGoodsName(goods.getGoodsName() + "up");
        updateException(goods);
    }

    /**
     * 事务不生效
     * 2. 方法用final修饰
     * 提示:Methods annotated with '@Transactional' must be overridable
     * 虽然有提示事物注解不能有final修饰,但是可以编译通过
     * 事物不会回滚,新增成功,更新失败
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public final void addFinal(Goods goods) {
        insert(goods);
        goods.setGoodsName(goods.getGoodsName() + "up");
        updateException(goods);
    }

    /**
     * 事务不生效
     * 3.方法内部调用
     * 实现:有时候我们需要在某个Service类的某个方法中,调用另外一个事务方法
     * 验证:事物不会回滚,新增成功,更新失败
     * 如果有些场景,确实想在同一个类的某个方法中,调用它自己的另外一个方法,该怎么办呢?
     * 新加一个Service方法
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addMethod(Goods goods) {
        insert(goods);
        goods.setGoodsName(goods.getGoodsName() + "up");
        // 事物修改
        updateTransactional(goods);
    }

    /**
     * 事务不生效
     * 4.未被spring管理
     * 实现:对象要被spring管理,需要创建bean实例。去掉@Controller、@Service、@Component、@Repository等注解,可以自动实现bean实例化和依赖注入的功能。
     * 验证:这种情况下比如去掉@Service,在Controller或者其他service都无法注入,肯定异常,一般不会发生
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addNoSpring(Goods goods) {
        insert(goods);
        goods.setGoodsName(goods.getGoodsName() + "up");
        update(goods);
    }

    /**
     * 事务不生效
     * 5.多线程调用
     * 实现:多线程情况下第一一条还没抛出异常,第二条可能已经成功提交,所以在事物内多线程操作事物失效
     * 验证:事物不会回滚,新增成功,更新失败
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addMulThread(Goods goods) {
        for (int i = 10; i < 15; i++) {
            int finalI = i;
            goods.setId((long) finalI);
            goods.setGoodsName(goods.getGoodsName() + finalI);

            // 异常数据
            if (i == 11 || i == 12) {
                pool.execute(() -> {
                    insertException(goods);
                });
            }
            //正常数据
            pool.execute(() -> {
                insert(goods);
            });
        }
    }

    /**
     * 事务不生效
     * 6.表不支持事务
     * 实现:在创建表的时候,只需要把ENGINE参数设置成MyISAM即可:
     * 验证:事物不会回滚,新增成功,更新失败
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addTableNoSupport(Goods goods) {
        insert(goods);
        goods.setGoodsName(goods.getGoodsName() + "up");
        update(goods);
    }

    /**
     * 事务不生效
     * 7.未开启事务
     * 实现:如果你使用的是springboot项目,那么你很幸运。因为springboot通过DataSourceTransactionManagerAutoConfiguration类,已经默默的帮你开启了事务。
     * 8.我们在使用@Transactional注解时,是可以指定propagation参数的。
     * 验证:事物不会回滚,新增成功,更新失败
     */
    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.NEVER)
    public void addSpread(Goods goods) {
        insert(goods);
        goods.setGoodsName(goods.getGoodsName() + "up");
        // 由于指定了事物传播属性,所以never下不会回滚
        updateException(goods);
    }

    /**
     * 事务不生效
     * 2.捕获异常
     * 实现:捕获异常后没有抛出,事物检测不到异常自然不会回滚
     * 验证:事物不会回滚,新增成功,更新失败
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addTry(Goods goods) {
        try {
            insert(goods);
            goods.setGoodsName(goods.getGoodsName() + "up");
            // 由于指定了事物传播属性,所以never下不会回滚
            updateException(goods);
        } catch (Exception e) {
            System.out.println("异常啦:" + e);
        }
    }

    /**
     * 事务不生效
     * 3.手动抛了别的异常
     * 实现:手动捕获异常后抛出,事物检测不到异常,事物不会滚
     * 验证:事物不会回滚,新增成功,更新失败
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addThrow(Goods goods) throws Exception {
        try {
            insert(goods);
            goods.setGoodsName(goods.getGoodsName() + "up");
            // 由于指定了事物传播属性,所以never下不会回滚
            updateException(goods);
        } catch (Exception e) {
            System.out.println("异常啦:" + e);
            throw new Exception(e);
        }
    }

    /**
     * 事务不生效
     * 4.自定义了回滚异常
     * 实现:指定或者自定义指定异常,事物只检测指定异常,其他异常不会滚
     * 验证:事物不会回滚,新增成功,更新失败
     */
    @Override
    @Transactional(rollbackFor = ArrayIndexOutOfBoundsException.class)
    public void addRollBack(Goods goods) {
        insert(goods);
        goods.setGoodsName(goods.getGoodsName() + "up");
        updateException(goods);
    }

    /**
     * 事务不生效
     * 5.嵌套事务回滚多了
     * 实现:
     * 验证:事物不会回滚,新增成功,更新失败
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addNested(Goods goods) {
        insert(goods);
        goods.setGoodsName(goods.getGoodsName() + "up");
        updateException(goods);
    }

    /**
     * 新增数据
     */
    @Override
    public int insert(Goods goods) {
        return this.goodsMapper.insert(goods);
    }

    /**
     * 异常新增数据
     */
    @Override
    public int insertException(Goods goods) {
        int result = 0;
        try {
            int res = 1 / 0;
            this.goodsMapper.insert(goods);
        } catch (Exception e) {
            throw new RuntimeException("更新失败啦,哈哈哈");
        }
        return result;
    }

    /**
     * 正常修改数据
     */
    @Override
    public int update(Goods goods) {
        return this.goodsMapper.update(goods);
    }

    /**
     * 异常修改数据
     */
    @Override
    public int updateException(Goods goods) {
        int result = 0;
        try {
            int res = 1 / 0;
            this.goodsMapper.update(goods);
        } catch (Exception e) {
            throw new RuntimeException("更新失败啦,哈哈哈");
        }
        return result;
    }

    /**
     * 事物修改数据
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int updateTransactional(Goods goods) {
        int result = 0;
        try {
            int res = 1 / 0;
            this.goodsMapper.update(goods);
        } catch (Exception e) {
            throw new RuntimeException("更新失败啦,哈哈哈");
        }
        return result;
    }

}

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值