Spring中在方法上加@Transactional的注解,事务提交的时间点是在该方法执行结束后,还是执行结束前呢?
今天学习用ReentrantLock
锁的时候,遇到一个问题。对售出商品业务的代码加上该锁。
@Servicepublic class ServiceOne{
// 设置一把可重入的公平锁
private Lock lock = new ReentrantLock(true);
@Transactional(rollbackFor = Exception.class)
public Result func(long seckillId, long userId) {
lock.lock();
// 执行数据库操作——查询商品库存数量
// 如果 库存数量 满足要求 执行数据库操作——减少库存数量——模拟卖出货物操作
lock.unlock();
}
}
前提是使用MySQL数据库
的可重复读
隔离机制。如果是高并发的情况下,假设真的就有多个线程同时调用该方法。那么当事务的开启与提交能完整的包裹在lock
与unlock
之间时,那么就不会出现超卖的问题。显然事务的开启一定是在lock
之后的,故关键在于事务的提交
是否一定在unlock
之前。
如果在unlock
之后,那么是真的有可能发生超卖的:比如线程一执行该方法完毕但事务却还没提交时,线程二获得锁也开始执行该方法,在可重复读的
隔离机制下,线程二是读不到线程一对库存的操作结果的,从而超卖。
希望大家能够前来解惑答疑!感激不尽!
回答:
上面两位同学已经解答得很好了,我再来补充一下答案,这个问题的解决使自己受益匪浅。
在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
@Transactionalpublic 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,再提交
事务拦截入口在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
也可以,就是不那么美观。
以上是 Spring中在方法上加@Transactional的注解,事务提交的时间点是在该方法执行结束后,还是执行结束前呢? 的全部内容, 来源链接: utcz.com/qa/257078.html