这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战
写在前面
我们都知道添加@Transactional注解便能实现事物(不考虑分布式事务),但是有些时候,却失效了,明明加着注解却没作用。
今天我们分为两部分: 温故、知新
温故,重新整理下那几种失效场景。知新,我们也能发现一点什么。接着往下看
我们先说 Spring事务的失效原因大概分为六种。
-
数据库引擎不支持事务(我们大部分用Mysql天然支持事务,所以该情况极少见)
-
没有被 Spring 管理
-
方法不是 public 的
-
自身调用问题 (常见)
-
异常被吃了 (常见)
-
异常类型错误
下面我们来一一举例说明:
必须被 Spring 管理
// @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 代理模式。
自己不能再调自己
@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 的代理类,默认只有在外部调用事务才会生效,这也是老生常谈的经典问题了。
解决方案:
@Resource
private UpdateOrder updateOrder;
public void update(Order order) {
updateOrder.updateOrder(order);
}
这样就就交给spring管理了。
不能try catch
@Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void updateOrder(Order order) {
try {
// update order
} catch {
}
}
}
我们经常这样写代码。但是事务会失效,原因是异常被吃掉了。人家事务就是根据你的异常去回滚事物的 结果你给吃掉了。
但是我们可以抛出来。在catch里面 抛出来 就可以解决 但是也不是任何抛异常都可以的。接下来看异常类型错误
异常类型需谨慎
@Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void updateOrder(Order order) {
try {
// update order
} catch {
throw new Exception("更新错误");
}
}
}
这样事务也是不生效的,因为默认回滚的是:RuntimeException,如果你想触发其他异常的回滚,需要在注解上配置一下,如:
@Transactional(rollbackFor = Exception.class)
好了 失效场景就讲到这里,我们接下来考虑一件事情,就是我们能不能在try catch情况下也能实现事务,答案是可以的。我们接下来看新的发现
新的解决方案
在上面事务回滚的前提是添加@Transactional注解的方法中不含有try{...}catch{...}捕获异常,使得程序运行过程中出现异常能顺利抛出,从而触发事务回滚。
但是在实际开发中,我们往往需要在方法中进行异常的捕获,从而对异常进行判断,为客户端返回提示信息。但是此时由于异常的被捕获,导致事务的回滚没有被触发,导致事务的失败。
那么这该怎么做呢,下面给出三种办法
1、使用@Transactional注解,抛出@Transactional注解默认识别的RuntimeException
@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语句中直接抛出所捕获的异常。
@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一些信息,那么就必须设置手动回滚,当捕获到异常时,手动回滚,同时返回前台提示信息。
手动回滚
@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。今天的讲解就到这里,我们下期再见
弦外之音
感谢你的阅读,如果你感觉学到了东西,您可以点赞,关注。也欢迎有问题我们下面评论交流
加油! 我们下期再见!
给大家分享几个我前面写的几篇骚操作