一、前言
在实际开发当中,如果使用事务不当,会造成Spring事务的失效,那么可能就会引发一些问题,如何解决呢,首先需要了解导致Spring事务失效的场景。
二、Spring事务失效的场景
1.不正确的捕获异常导致事务失效。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class MyFirstService {
@Autowired
private ServiceMapper serviceMapper;
@Transactional
public void addService(MyService service){
try{
serviceMapper.insert(service);
updateData(service);
}catch (Exception e){
System.err.println(e.getMessage());
}
}
public void updateData(MyService service){
serviceMapper.updateByServiceId(service);
}
}
这种捕获了异常却并未抛出会导致事务失效。
解法1:异常原样抛出
在 catch 块添加 throw new RuntimeException(e);
解法2:手动设置 TransactionStatus.setRollbackOnly()
在 catch 块添加 TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
2.事务方法访问权限问题导致事务失效。
@Transactional
protected void addService(MyService service){
try{
serviceMapper.insert(service);
updateData(service);
}catch (Exception e){
System.err.println(e.getMessage());
}
}
public void updateData(MyService service){
serviceMapper.updateByServiceId(service);
}
上面定义addService方法的访问权限为protected ,这样最终会导致事务失效,spring要求被代理方法必须是public的。
想要了解代理方法必须是public的,我们需要看spring事务的源码,在
AbstractFallbackTransactionAttributeSource类的computeTransactionAttribute方法中有个判断,如果目标方法不是public,则TransactionAttribute返回null,即不支持事务。
解法:事务方法访问权限需定义为public。
3.方法用final修饰。
@Transactional
public final void addService(MyService service){
try{
serviceMapper.insert(service);
updateData(service);
}catch (Exception e){
System.err.println(e.getMessage());
}
}
public void updateData(MyService service){
serviceMapper.updateByServiceId(service);
}
我们可以看到add方法被定义成了final的,这样会导致事务失效。
看过spring事务的源码,就会知道spring事务底层使用了aop,也就是通过jdk动态代理或者cglib,帮我们生成了代理类,在代理类中实现的事务功能。
但如果某个方法用final修饰了,那么在它的代理类中,就无法重写该方法,而无法添加事务功能。
解法:事务方法不能定义为final。
4.方法内部调用
有时候我们需要在某个Service类的某个方法中,调用另外一个事务方法,例如:
@Service
public class MyFirstService {
@Autowired
private ServiceMapper serviceMapper;
public void addService(MyService service){
try{
updateData(service);
}catch (Exception e){
System.err.println(e.getMessage());
}
}
@Transactional
public void updateData(MyService service){
serviceMapper.insert(service);
serviceMapper.updateByServiceId(service);
}
}
我们看到在方法addService中,直接调用事务方法updateData。从前面的内容可以知道,updateData方法拥有事务的能力是因为spring aop生成代理了对象,但是这种方法直接调用了this对象的方法,所以updateData方法不会生成事务。
由此可见,在同一个类中的方法直接内部调用,会导致事务失效。
解法1: 新加一个Service方法
这个方法非常简单,、需要新加一个Service方法,把@Transactional注解加到新Service方法上,把需要事务执行的代码移到新方法中。
@Service
public class MyFirstService {
@Autowired
private MyFirstServiceB myFirstServiceB ;
public void addService(MyService service){
try{
myFirstServiceB.updateData(service);
}catch (Exception e){
System.err.println(e.getMessage());
}
}
}
@Service
public class MyFirstServiceB {
@Autowired
private ServiceMapper serviceMapper;
@Transactional
public void updateData(MyService service){
serviceMapper.insert(service);
serviceMapper.updateByServiceId(service);
}
}
解法2:在该Service类中注入自己
@Service
public class MyFirstService {
@Autowired
private MyFirstService myFirstService ;
@Autowired
private ServiceMapper serviceMapper;
public void addService(MyService service){
try{
myFirstService.updateData(service);
}catch (Exception e){
System.err.println(e.getMessage());
}
}
@Transactional
public void updateData(MyService service){
serviceMapper.insert(service);
serviceMapper.updateByServiceId(service);
}
}
5.多线程调用
在实际项目开发中,多线程的使用场景还是挺多的。如果spring事务用在多线程场景中,那么就会存在一些问题。
@Service
public class MyFirstService {
@Autowired
private ServiceMapper serviceMapper;
@Transactional
public void addService(MyService service){
try{
serviceMapper.insert(service);
new Thread(()->{
updateData(service);
}).start();
}catch (Exception e){
System.err.println(e.getMessage());
}
}
public void updateData(MyService service){
serviceMapper.updateByServiceId(service);
}
}
我们可以看到事务方法addService中,调用了updateData,但是方法updateData是在另外一个线程中调用的。
这样会导致两个方法不在同一个线程中,获取到的数据库连接不一样,从而是两个不同的事务。如果想updateData方法中抛了异常,addService方法也回滚是不可能的。
6.手动抛了别的异常
即使开发者没有手动捕获异常,但如果抛的异常不正确,spring事务也不会回滚。
@Transactional
public void addService(MyService service){
try{
serviceMapper.insert(service);
updateData(service);
}catch (Exception e){
throw new Exception("失败");
}
}
public void updateData(MyService service){
serviceMapper.updateByServiceId(service);
}
这样事务也是不生效的,因为默认回滚的是:RuntimeException,如果你想触发其他异常的回滚,需要在注解上配置一下,如:@Transactional(rollbackFor = Exception.class), 这个配置仅限于 Throwable 异常类及其子类。
以上就是其中比较常见的导致事务失效的一些场景,当然还有一些其他的,这里就不多介绍了,大家有兴趣可以去了解下。