Spring事务的传播行为
当方法存在事务,然后调用其他方法的时候事务是怎么传播的,spring提供了七种传播行为,每次看过几天后就忘记了,这一次进行整理记录一下,不然每次寻找资料很麻烦。
Spring有七大传播行为如下图所示,光看以下描述可能不是很好理解,遇见自己无法理解的东西最好的办法就是自己通过代码去实践探索。
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表
product表
下面是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);
}
运行后不出意外会报错,事务会进行回滚,此时数据库中的值
可以看出数据库并没有改变,购买商品1的时候价格足够,后面购买商品2的时候由于价格不足所以造成了事务的回滚。所以REQUIRED中,有事务的buyMore()方法调用有事务的buyOne()方法将会执行buyMore()的事务,如果buyMore()中没有事务运行结果又是什么样?去掉buyMore()中@Transactional然后运行结果如下
从上面运行的结果来看,买商品2的扣款失败,但是库存还是减少了,没有回滚,所以当buyMore()没有事务调用有事务的buyOne()时,buyOne()没有启动事务。这个结论和上面给的定义好像自相矛盾了,原因是因为在同一个类中,一个方法调用另外一个有注解(比如@Async,@Transational)的方法,注解不会生效,所以声明一个PeopleService类,将buyMore方法移动到这个类里面然后使用peopleService调用,后面的buyMore方法都是PeopleService中的这个方法。
可以看出当buyMore()没有事务调用有事务的buyOne()方法,第一次购买商品1价钱足够成功购买,第二次购买商品2价钱不够所以回滚事务,此时两次购买商品使用的是自己开启的事务,是不同的事务,所以只回滚购买商品2。
SUPPORTS
supports表示如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。意思就是Spring不会自动为该方法开启事务,当有事务的A方法调用该方法就执行A的事务,如果A方法没有事务则非事务运行。 将注解改成@Transactional(propagation = Propagation.SUPPORTS)然后运行代码
注:每次运行代码前数据库都是改为最初始的时候的样子
通过以上可以看出第一次和第二次购买商品库存都减少了,所以没有进行回滚操作。
和REQUIRED的差别
不会自动开启事务,只有在有事务的方法调用才会在事务中运行,REQUIRED必须在事务中运行,如果调用方法没有事务则自己开启事务。
MANDOTARY
表示必须在事务中运行,如果事务不存在则抛出异常,将buyMore的事务注解注释掉,将buyOne的属性更改为mandotary然后执行。
因为buyMore没有事务调用了buyOne所以程序抛出异常。
mandotary 不会自己开启事务,它要求该方法必须让有事务的方法调用,否则就会抛出错误
REQUIRED_NEW
表示无论是否存在事务都会开启事务,并且将老的事务挂起,先具体看看代码再进行描述。将buyMore的注解属性使用REQUIRED,buyOne的使用REQUIRED_NEW。运行如下:
可以看出idea中调用buyOne是执行的自己的事务
数据库中的数据看出商品1购买成功,商品2购买失败回滚,可以看出两次调用buyone方法使用的事务是独立的,不是使用的buyMore的事务。在buyMore方法最后添加一条抛出异常的遇见throw new
Exception(),然后将数据库中的金钱改成300;目的是当buyMore发现异常的时候看看商品1和商品2的数据是否会回滚,运行结果如下:
抛出异常
可以数据看出并没有回滚。将数据库中的数据改回原来的值,将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)
当用try catch块将buyOne给包起来时,运行结果如下
所以可以得出结论
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
其他的属性可以根据字面意思自己理解,就不用在继续进行探讨了。