Spring事务传播行为

Spring事务的传播行为

当方法存在事务,然后调用其他方法的时候事务是怎么传播的,spring提供了七种传播行为,每次看过几天后就忘记了,这一次进行整理记录一下,不然每次寻找资料很麻烦。

Spring有七大传播行为如下图所示,光看以下描述可能不是很好理解,遇见自己无法理解的东西最好的办法就是自己通过代码去实践探索。


Spring传播行为.png

REQUIRED

spring中默认的传播行为,如果拥有事务的A方法调用拥有事务的B方法,B方法将会在A方法的事务中运行,如果A方法没有事务,将会运行在B方法的事务中。下面将以用户购买商品进行事务的探索:

/**
 *两个entity类
*/
public class People { //用户类
    @Id
    Integer id;
    int price; //拥有的钱财
}
public class Product { //物品类
    @Id
    Integer id; //id
    int price; //价格
    int count; //库存
}
/**
*数据库表
*/
create table product(
    id BIGINT PRIMARY KEY,
    price int,
    count int
)

create table people(
    id BIGINT PRIMARY KEY,
    price int unsigned  //加上unsigned代表price的值必须大于等于0
)

数据库中的数据 people表


people.png

product表


product.png

下面是service中购买商品的代码

@Service
public class ProductServiceImpl implements ProductService {
    @Autowired
    ProductMapper productMapper;
    @Autowired
    PeopleMapper peopleMapper;

    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public void buyOne(Integer productId) {
        System.out.println(TransactionSynchronizationManager.getCurrentTransactionName()); //这句代码可以用来打印当前事务
        Product product1 = productMapper.selectByPrimaryKey(productId);
        People people = peopleMapper.selectByPrimaryKey(1001);//固定人,有300元
        System.out.println(productMapper.selectByPrimaryKey(productId));
        System.out.println(people);
        product1.setCount(product1.getCount() - 1);
        productMapper.updateByPrimaryKey(product1);//更新库存,先更新库存,再更新价格,因为价格出错事务会回滚可以判断是否执行了事务
        people.setPrice(people.getPrice() - product1.getPrice());
        peopleMapper.updateByPrimaryKey(people);//更新价格
    }

    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public void buyMore(List productIds ) {
        for (Integer productId : productIds) {
            buyOne(productId);
        }
    }
}

可以看出上么两个方法 buyOne()和buyMore()都拥有事务。测试代码如下

    @Autowired
    ProductService productService;
    @Test
    void contextLoads() {
        List list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(2);
        productService.buyMore(list);
    }

运行后不出意外会报错,事务会进行回滚,此时数据库中的值
图片.png

图片.png

可以看出数据库并没有改变,购买商品1的时候价格足够,后面购买商品2的时候由于价格不足所以造成了事务的回滚。所以REQUIRED中,有事务的buyMore()方法调用有事务的buyOne()方法将会执行buyMore()的事务,如果buyMore()中没有事务运行结果又是什么样?去掉buyMore()中@Transactional然后运行结果如下

图片.png

图片.png

从上面运行的结果来看,买商品2的扣款失败,但是库存还是减少了,没有回滚,所以当buyMore()没有事务调用有事务的buyOne()时,buyOne()没有启动事务。这个结论和上面给的定义好像自相矛盾了,原因是因为在同一个类中,一个方法调用另外一个有注解(比如@Async,@Transational)的方法,注解不会生效,所以声明一个PeopleService类,将buyMore方法移动到这个类里面然后使用peopleService调用,后面的buyMore方法都是PeopleService中的这个方法
图片.png
图片.png

可以看出当buyMore()没有事务调用有事务的buyOne()方法,第一次购买商品1价钱足够成功购买,第二次购买商品2价钱不够所以回滚事务,此时两次购买商品使用的是自己开启的事务,是不同的事务,所以只回滚购买商品2。

SUPPORTS

supports表示如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。意思就是Spring不会自动为该方法开启事务,当有事务的A方法调用该方法就执行A的事务,如果A方法没有事务则非事务运行。 将注解改成@Transactional(propagation = Propagation.SUPPORTS)然后运行代码

注:每次运行代码前数据库都是改为最初始的时候的样子

图片.png

图片.png

通过以上可以看出第一次和第二次购买商品库存都减少了,所以没有进行回滚操作。

和REQUIRED的差别
不会自动开启事务,只有在有事务的方法调用才会在事务中运行,REQUIRED必须在事务中运行,如果调用方法没有事务则自己开启事务。

MANDOTARY

表示必须在事务中运行,如果事务不存在则抛出异常,将buyMore的事务注解注释掉,将buyOne的属性更改为mandotary然后执行。


图片.png

因为buyMore没有事务调用了buyOne所以程序抛出异常。

mandotary 不会自己开启事务,它要求该方法必须让有事务的方法调用,否则就会抛出错误

REQUIRED_NEW

表示无论是否存在事务都会开启事务,并且将老的事务挂起,先具体看看代码再进行描述。将buyMore的注解属性使用REQUIRED,buyOne的使用REQUIRED_NEW。运行如下:


图片.png

可以看出idea中调用buyOne是执行的自己的事务


图片.png

图片.png

数据库中的数据看出商品1购买成功,商品2购买失败回滚,可以看出两次调用buyone方法使用的事务是独立的,不是使用的buyMore的事务。在buyMore方法最后添加一条抛出异常的遇见throw new

Exception(),然后将数据库中的金钱改成300;目的是当buyMore发现异常的时候看看商品1和商品2的数据是否会回滚,运行结果如下:


图片.png

图片.png

抛出异常
图片.png

可以数据看出并没有回滚。将数据库中的数据改回原来的值,将buyMore和buyOne改为如下代码测试内部事务异常对外部的影响。
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
    public void buyMore(List productIds ) throws Exception {
        Product product = productMapper.selectByPrimaryKey(2);
        product.setPrice(product.getPrice() + 1000);
        System.out.println("----");
        System.out.println(product);
        System.out.println(productMapper.updateByPrimaryKey(product));
        System.out.println(TransactionSynchronizationManager.getCurrentTransactionName()); //打印事务
        for (Integer productId : productIds) {
            productService.buyOne(productId);
        }
    }

  public void buyOne(Integer productId) throws Exception {
        throw new Exception("test exception");
}

运行结果如下,商品2的价格并没有添加1000,说明有数据回滚(注解一点要加rollbackFor属性,transactional注解默认只捕获运行中异常和error)


图片.png

当用try catch块将buyOne给包起来时,运行结果如下


图片.png

所以可以得出结论

REQUIRED_NEW 会每次开启新的事务,将原来的事务挂起,执行自己的事务,外部事务的异常不会影响内部事务,内部事务的异常也不会影响其他内部事务,内部事务的异常会造成外部事务的回滚(如果有用try catch块就不会造成回滚)。所以当有A、B、C三个方法,其中A为外部事务,B、C为内部事务,当B发生错误(没用try-catch包起来),A会回滚,C不会回滚;当A发生错误,A会回滚,B、C不会回滚,所以在使用这种的时候要格外注意数据的问题。

NESTED

nested和REQUIRED_NEW 以及REQUIRED有很多的相似。将buyOne改成NESTED,运行和REQUIRED_NEW 得到的结果一样,REQUIRED和NESTED唯一的不同是将错误用try catch包起来时,REQUIRED还是会回滚,而NESTED不会回滚,具体的代码研究和上面一样,就省略。(这一个肯定没有我理解的这么简单,等后来好好研究一下在进行补充)。

other

其他的属性可以根据字面意思自己理解,就不用在继续进行探讨了。

你可能感兴趣的:(Spring事务传播行为)