Spring事务失效的几种原因

第一种  抛出受检异常

先来看看异常继承树

       这里把异常分为三类,Error,RuntimeException、CheckedException,其中CheckedException是受检异常,受检异常是由编译器强制执行的,必须捕获,也就是大概率会出异常,需要你在写代码的时候就处理一下,不然会爆红,例如IOException。Error,RuntimeException是非受检异常,代码运行的时候才可能出错,概率小一点。


   下面代码显示了@Transactional对哪些异常会进行补抓回滚。很明显,受检异常是无法被处理的

public boolean rollbackOn(Throwable ex) {
	return (ex instanceof RuntimeException || ex instanceof Error);
}

 比如你的代码如下,抛出了受检异常,此时程序是无法回滚的,@Transactional失效。

@Transactional
public void transactionTest() throws IOException{
    User user = new User();
    UserService.insert(user);
    throw new IOException();
}

 解决方案: 配置rollbackFor属性,例如@Transactional(rollbackFor = Exception.class),这样的话,@Transactional会自己补抓受检异常和非受检异常,自然也能回滚了。

第二种  自己补抓了异常

         事务底层是通过补抓异常来决定回滚的,如果你的代码自己捕获处理了异常,那么事务就补抓不到,自然会失效。

@Transactional(rollbackFor = Exception.class)
public void transactionTest() {
    try {
        User user = new User();
        UserService.insert(user);
        int i = 1 / 0;
    }catch (Exception e) {
        e.printStackTrace();
    }
}

解决办法:  不要自己处理,或者在catch里抛出非受检异常

第三种  类中方法互相调用

        事务是基于aop实现的,aop又是经过动态代理实现方法增强,也就是说事务的功能是通过代理增强的。当我们调用某个有@Transaction方法的时候,实际上是代理对象在调用该方法,对该方法实现了事务增强功能,如果如下面代码一样,方法调用方法,那么就是由DefaultTransactionService类的对象调用的方法,根本没走代理对象。事务自然失效。

@Service
public class DefaultTransactionService implement Service {

    public void saveUser() throws Exception {
        //do something
        doInsert();
    }

    @Transactional(rollbackFor = Exception.class)
    public void doInsert() throws IOException {
        User user = new User();
        UserService.insert(user);
        throw new IOException();

    }
}

解决办法: 

方案一:直接在启动类中添加@Transactional注解saveUser()

方案二:@EnableAspectJAutoProxy(exposeProxy = true)在启动类中添加,会由Cglib代理实现。

第四种  方法不是public

        如果方法不是public,Spring事务也会失败,因为Spring的事务管理源码AbstractFallbackTransactionAttributeSource中有判断computeTransactionAttribute()。如果目标方法不是公共的,则TransactionAttribute返回null。本质上还是因为非public不生成代理对象,不增强就无法添加事务功能。

// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
  return null;
}

解决方案:将当前方法访问级别更改为public。

 第五种  没被spring管理

        这种就好说了。不加@Controller、@Service、@Component、@Repository等注解,就无法被spring管理,自然不会生成事务。

第六种  方法被final修饰

        spring事务底层使用了aop,也就是通过jdk动态代理或者cglib,帮我们生成了代理类,在代理类中实现的事务功能。

        但如果某个方法用final修饰了,那么在它的代理类中,就无法重写该方法,而添加事务功能。

@Service
public class UserService {

    @Transactional
    public final void add(UserModel userModel){
        saveData(userModel);
        updateData(userModel);
    }
}

第七种  多线程调用

        在实际项目开发中,多线程的使用场景还是挺多的。如果spring事务用在多线程场景中,会有问题吗?

        

@Slf4j
@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;
    @Autowired
    private RoleService roleService;

    @Transactional
    public void add(UserModel userModel) throws Exception {
        userMapper.insertUser(userModel);
        new Thread(() -> {
            roleService.doOtherThing();
        }).start();
    }
}

@Service
public class RoleService {

    @Transactional
    public void doOtherThing() {
        System.out.println("保存role表数据");
    }
}

        从上面的例子中,我们可以看到事务方法add中,调用了事务方法doOtherThing,但是事务方法doOtherThing是在另外一个线程中调用的。

        这样会导致两个方法不在同一个线程中,获取到的数据库连接不一样,从而是两个不同的事务。如果想doOtherThing方法中抛了异常,add方法也回滚是不可能的。

        如果看过spring事务源码的朋友,可能会知道spring的事务是通过数据库连接来实现的。当前线程中保存了一个map,key是数据源,value是数据库连接。

        

private static final ThreadLocal<Map<Object, Object>> resources =

  new NamedThreadLocal<>("Transactional resources");

        我们说的同一个事务,其实是指同一个数据库连接,只有拥有同一个数据库连接才能同时提交和回滚。如果在不同的线程,拿到的数据库连接肯定是不一样的,所以是不同的事务。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

.ccc.。

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值