温故而知新,Spring事务的失效场景以及新的发现

这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战

写在前面

我们都知道添加@Transactional注解便能实现事物(不考虑分布式事务),但是有些时候,却失效了,明明加着注解却没作用。

今天我们分为两部分: 温故、知新

温故,重新整理下那几种失效场景。知新,我们也能发现一点什么。接着往下看

我们先说 Spring事务的失效原因大概分为六种

  • 数据库引擎不支持事务(我们大部分用Mysql天然支持事务,所以该情况极少见)
  • 没有被 Spring 管理
  • 方法不是 public 的
  • 自身调用问题 (常见)
  • 异常被吃了 (常见)
  • 异常类型错误

下面我们来一一举例说明:

必须被 Spring 管理

1
2
3
4
5
6
7
8
java复制代码 // @Service
public class OrderServiceImpl implements OrderService {

@Transactional
public void updateOrder(Order order) {
// update order
}
}

如果此时把 @Service 注解注释掉,这个类就不会被加载成一个 Bean,那这个类就不会被 Spring 管理了,事务自然就失效了。

方法必须是 public 的

以下来自 Spring 官方文档:

When using proxies, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings. Consider the use of AspectJ (see below) if you need to annotate non-public methods.

大概意思就是 @Transactional 只能用于 public 的方法上,否则事务不会失效,如果要用在非 public 方法上,可以开启 AspectJ 代理模式。

自己不能再调自己

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码@Service
public class OrderServiceImpl implements OrderService {

public void update(Order order) {
updateOrder(order);
}

@Transactional
public void updateOrder(Order order) {
// update order 逻辑
}
}

update方法上面没有加 @Transactional 注解,调用有 @Transactional 注解的 updateOrder 方法,updateOrder 方法上的事务管用吗?

答案是不管用的,因为它们发生了自身调用,就调该类自己的方法,而没有经过 Spring 的代理类,默认只有在外部调用事务才会生效,这也是老生常谈的经典问题了。

解决方案:

1
2
3
4
5
6
java复制代码@Resource
private UpdateOrder updateOrder;

public void update(Order order) {
updateOrder.updateOrder(order);
}

这样就就交给spring管理了。

不能try catch

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码@Service
public class OrderServiceImpl implements OrderService {

@Transactional
public void updateOrder(Order order) {
try {
// update order
} catch {

}
}
}

我们经常这样写代码。但是事务会失效,原因是异常被吃掉了。人家事务就是根据你的异常去回滚事物的 结果你给吃掉了。

但是我们可以抛出来。在catch里面 抛出来 就可以解决 但是也不是任何抛异常都可以的。接下来看异常类型错误

异常类型需谨慎

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码@Service
public class OrderServiceImpl implements OrderService {

@Transactional
public void updateOrder(Order order) {
try {
// update order
} catch {
throw new Exception("更新错误");
}
}

}

这样事务也是不生效的,因为默认回滚的是:RuntimeException,如果你想触发其他异常的回滚,需要在注解上配置一下,如:

1
java复制代码@Transactional(rollbackFor = Exception.class)

好了 失效场景就讲到这里,我们接下来考虑一件事情,就是我们能不能在try catch情况下也能实现事务,答案是可以的。我们接下来看新的发现

新的解决方案

在上面事务回滚的前提是添加@Transactional注解的方法中不含有try{…}catch{…}捕获异常,使得程序运行过程中出现异常能顺利抛出,从而触发事务回滚。

但是在实际开发中,我们往往需要在方法中进行异常的捕获,从而对异常进行判断,为客户端返回提示信息。但是此时由于异常的被捕获,导致事务的回滚没有被触发,导致事务的失败

那么这该怎么做呢,下面给出三种办法

1、使用@Transactional注解,抛出@Transactional注解默认识别的RuntimeException

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码@Service
public class OrderServiceImpl implements OrderService {

@Transactional
public void updateOrder(Order order) {
try {
// update order
} catch {
logger.error("更改订单失败")
throw new RuntimeException("RuntimeException");
}
}
}

2、使用@Transactional(rollbackFor = { Exception.class }),也能抛出捕获的非RuntimeException异常

方法上使用@Transactional(rollbackFor = { Exception.class })注解声明事务回滚级别,在捕获到异常时在catch语句中直接抛出所捕获的异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码@Service
public class OrderServiceImpl implements OrderService {

@Transactional(rollbackFor = { Exception.class })
public void updateOrder(Order order) {
try {
// update order
} catch {
logger.error("更改订单失败")
throw e;
}
}
}

不知道有没有发现上面两个在catch{…}中抛出异常的方法都有个不足之处,就是不能在catch{…}中存在return子句,比如我们需要一些return一些信息,那么就必须设置手动回滚,当捕获到异常时,手动回滚,同时返回前台提示信息。

手动回滚

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码@Service
public class OrderServiceImpl implements OrderService {

@Transactional
public int updateOrder(Order order) {
try {
// update order
} catch {
logger.error("更改订单失败")
//手动回滚
TransactionAspectSupport.currentTransactionStatus()
.setRollbackOnly();
return 0;
}
return 1;
}
}

OK。今天的讲解就到这里,我们下期再见

弦外之音

感谢你的阅读,如果你感觉学到了东西,您可以点赞,关注。也欢迎有问题我们下面评论交流

加油! 我们下期再见!

给大家分享几个我前面写的几篇骚操作

copy对象,这个操作有点骚!

干货!SpringBoot利用监听事件,实现异步操作

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%