Spring中在方法上加@Transactional的注解,事务提交的时间点是在该方法执行结束后,还是执行结束前呢?

今天学习用ReentrantLock锁的时候,遇到一个问题。对售出商品业务的代码加上该锁。

@Service
public class ServiceOne{
    // 设置一把可重入的公平锁
    private Lock lock = new ReentrantLock(true);
    
    @Transactional(rollbackFor = Exception.class)
    public Result  func(long seckillId, long userId) {
        lock.lock();
        // 执行数据库操作——查询商品库存数量
        // 如果 库存数量 满足要求 执行数据库操作——减少库存数量——模拟卖出货物操作
        lock.unlock();
    }
}

前提是使用MySQL数据库可重复读隔离机制。如果是高并发的情况下,假设真的就有多个线程同时调用该方法。那么当事务的开启与提交能完整的包裹在lockunlock之间时,那么就不会出现超卖的问题。显然事务的开启一定是在lock之后的,故关键在于事务的提交是否一定在unlock之前。
如果在unlock之后,那么是真的有可能发生超卖的:比如线程一执行该方法完毕但事务却还没提交时,线程二获得锁也开始执行该方法,在可重复读的隔离机制下,线程二是读不到线程一对库存的操作结果的,从而超卖。
希望大家能够前来解惑答疑!感激不尽!

阅读 15.4k
3 个回答

上面两位同学已经解答得很好了,我再来补充一下答案,这个问题的解决使自己受益匪浅。
在StackOverFlow上有人这么回答:
Your method is not only what actually is executed by Spring. Spring uses proxies, so as you can quess for your class and for your method a proxy is created as well.

As already commented @Transactional is implemented using the aspect around meaning some code before your method's execution and some code after your method's execution is executed as well.

So if you have written

@Transactional
public void method1 () {
    doSomething1;
    doSomething2;
}

What the proxy of spring may actually seem like will be

public void method1 () {

        /*
          Extra code from spring to open a transaction.
        /*
    
        doSomething1;
        doSomething2;

        /*
          Extra code from spring to close a transaction.
        /*
    }

意思就是说,事务确实是在方法结束时提交的。

今天(2021/11/10)思否给我颁发了一个火爆问题的徽章,没错就是这个问题带来了这个徽章。在这个问题提出之后的日子又学习了一些新的相关知识,更加使我坚信这个回答的正确性。

也正因此,我想做些许补充。由于这里的事务是由Spring带来并管理的,那么了解这个原理就对该问题的分析大有裨益。实际上Spring采用一种动态代理的方式来对加了@Transactional注解的方法进行增强(即在该方法执行前,添加事务,并在方法执行完成后提交事务。)。因此,正如上面回答的一样,事务确实是在该方法结束时提交的。

那怎么处理呢?其实也很简单。

lock();
method();
unlock();

method方法就是带注解的方法,这样就能用锁包裹住事务了。当然我们一定要注意这个事务是否能生效,关于事务是否能生效的相关文章也写过,在我历史文章中就能找到!
-- 完结!!!

像你现在这种写法,最外面的func如果没有加入事务,事务提交在unlock之前;如果加了,那就是先unlick,再提交
image.png
image.png
事务拦截入口在TransactionInterceptor,它其实是拦截系列被事务注解标记的方法,如果你调用方没有加注解,也就是和你func方法一样,事务代理类给你拦截lock和unlock之前的代码以后,只要发现事务结束,就直接提交事务了,此时你的func其实是没有走到unlock的;

之前没搞明白题主的意思,事务的提交时在方法执行之后的。你的猜想时正确的,由于你的隔离级别设置,即使是释放了锁,依然不能读取到最新的数据。如果你在其中有判断,很容易就出现错误,导致超卖等的问题。 具体代码可以参考:TransactionAspectSupport.invokeWithTransaction方法,从其中可以看出,invocation.proceedWithInvocation 是在commitTransactionAfterReturning 前面的。对于声明式事务,解决办法是把锁加在方法外面。如果一定需要加在里面,也可以使用编程式事务。


        DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
        definition.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ);
        TransactionStatus status = transactionManager.getTransaction(definition);
        lock.lock();
        try {
            Demo demo = demoDao.findById(1).get();
            if (demo.getCount() > 50) {
                demo.setCount(demo.getCount() - i);
                demoDao.save(demo);
            }
            transactionManager.commit(status);
        }catch (Exception e){
            transactionManager.rollback(status);
        }finally {
            lock.unlock();
        }

使用TransactionTemplate也可以,就是不那么美观。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏