事务失效问题

如果只对其中的一段代码添加事务,比方只对其中的**this.save(orders);**一行添加事务

想实现的话有两种方案:

  1. 使用Spring的编程式事务,这种方式控制粒度更精准,但是缺点是代码耦合度高,需要自己写代码处理事务
  2. 将需要控制事务的那段代码单独提到一个方法中去,然后仅仅对新抽取的方法控制事务
1
2
3
4
5
6
7
8
9
10
11
12
@Override
public PlaceOrderResDTO placeOrder(PlaceOrderReqDTO placeOrderReqDTO) {
//... 省略代码
this.saveOrders(orders);
//... 省略代码
}

//新编写一个方法,将需要事务控制事务的方法,提到这个位置来
@Transactional
public void saveOrders(Orders orders){
this.save(orders);
}

上面的方法可以很好的解决长事务的问题,但是它也引入了一个新的问题,那就是事务失效

1
2
3
4
5
6
7
@Transactional
public void saveOrders(Orders orders){
this.save(orders);

//手动模拟一个异常,看看事务会不会回滚
int i = 1 / 0;
}

解决方案

运行上面的代码,会发现事务不再自动回滚,原因很简单:那就是这个方法的调用者是原始对象,不是代理对象

而原始对象是没有事务控制能力的,那解决方案就是将调用者再想办法还原为代理对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Autowired
private IOrdersCreateService owner;

@Override
public PlaceOrderResDTO placeOrder(PlaceOrderReqDTO placeOrderReqDTO) {
//... 省略代码
owner.saveOrders(orders);
//... 省略代码
}

@Transactional
public void saveOrders(Orders orders){
this.save(orders);

//模拟异常
//int i = 1 / 0;
}

接口中也需要对应的暴露新增的方法

1
2
3
4
5
6
/**
* 保存订单
*
* @param orders 订单
*/
void saveOrders(Orders orders);

重新测试,事务问题得到解决

总结

事务失效的情况有哪些?

  • 非事务方法内部调用事务方法
  • 事务方法没有使用public修饰
  • 事务方法的异常在方法内被捕获处理
  • 事务方法抛出的异常与rollbackFor属性指定的异常不匹配
  • 事务方法配置的事务传播行为有误

① 非事务方法内部调用事务方法

在下面方法中,insertOrderAndReduceStock()方法使用的是原始对象调用的,而不是代理对象调用的

而事务管理功能是代理对象负责的,因此事务失效!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Service
public class OrderService {

public void createOrder(){
// ... 准备订单数据
// 生成订单并扣减库存
insertOrderAndReduceStock();
}

@Transactional
public void insertOrderAndReduceStock(){
// 生成订单
insertOrder();
// 扣减库存
reduceStock();
}
}

② 事务方法没有使用public修饰

Spring的声明式事务是基于AOP方式结合动态代理来实现的,在其内部有一个类

AbstractFallbackTransactionAttributeSource会检查方法是否使用public修饰,如果不是,则不能进行功能增强

在下面方法中,createOrder()方法没有使用public修饰,因此无法进行事务增强,事务失效!

1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class OrderService {

@Transactional
private void createOrder(){
// ... 准备订单数据
// 生成订单
insertOrder();
// 扣减库存
reduceStock();
}
}

③ 事务方法的异常在方法内被捕获处理

在下面方法中,createOrder方法执行过程中即便出现了异常也不会向外抛出

而Spring的事务管理就是要感知业务方法的异常,当捕获到异常后才会回滚事务

现在事务被捕获,就会导致Spring无法感知事务异常,自然不会回滚,事务就失效了!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Service
public class OrderService {
@Transactional
private void createOrder(){
// ... 准备订单数据
try {
// 生成订单
insertOrder();
// 扣减库存
reduceStock();
} catch (Exception e) {
// 处理异常
}
}
}

④ 事务方法抛出的异常与rollbackFor属性指定的异常不匹配

Spring的@Transactional使用rollbackFor属性指定当前事务管理器感知的异常类型(默认为RuntimeException)

下面方法createOrder方法抛出了一个IOException,不会被Spring捕获,事务就失效了!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Service
public class OrderService {

@Transactional(rollbackFor = RuntimeException.class)
public void createOrder() throws IOException {
// ... 准备订单数据
try {
// 生成订单
insertOrder();
// 扣减库存
reduceStock();
} catch (Exception e) {
// 处理异常
throw new IOException();
}
}
}

⑤ 事务方法配置的事务传播行为有误

下面方法reduceStock方法配置了事务传播行为Propagation.REQUIRES_NEW,他代表当前方法会开启一个新事物

也就是与createOrder和insertOrder不在同一个事物中了,因此事务失效!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Service
public class OrderService1 {

@Autowired
private OrderService2 orderService2;

@Transactional
public void createOrder() {
// 生成订单
orderService2.insertOrder(); // 使用默认的Propagation.REQUIRED传播行为
// 扣减库存
orderService2.reduceStock();// 使用Propagation.REQUIRES_NEW传播行为
}

}

@Service
public class OrderService2 {

@Transactional // 默认传播行为:Propagation.REQUIRED
public void insertOrder() {
// 订单数据插入操作
}

//Propagation.REQUIRES_NEW 代表必须新事物
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void reduceStock() {
// 库存扣减操作
}
}