近日遇到一个问题,就是一个订单被两个用户抢了问题,排查后发现是由于 @Transactional和@synchronized注解的使用问题
一、问题点:数据重复读
@Transactional注解用于开启事务,当在高并发情况下我们可能为了保证数据的安全使用悲观锁,可以在方法上使用@synchronized使用悲观锁。一个线程执行完方法并释放锁后,事务并未提交,第二个线程又获得了该锁,导致数据出问题
@Transactional注解通过AOP实现事务管理,当标注该注解的方法执行完成后才提交事务,而synchronized代码块又是在一个事务内,就会出现第一个线程释放锁后但是事务还没提交,第二个线程就进入同步代码块获取到未提交的数据库数
@Transactional控制事务的范围比sychronized 大,如图:
思路:既然事务下不能使用锁,那我们把锁和事务进行分开。使得在锁环境下包含事务,最终依然是线程安全的
方法一:将锁替换成数据库的锁比如select for update或者版本号version
方法二:在service下将事务代码的抽取单独使用,无事务方法调用有事务的方法
既然问题出在事务未提交,那么只要把对应事务操作的代码单独抽取出来,封装成一个单独的方法,在synchronized中调用该方法即可
@Service
public class OrderServiceImpl implements OrderServiceI {
@Autowired
private OrderDao orderDao;
public sychronized Order updateOrder(int id) { //加锁
return updateOrderSafely(id); //调用数据库
}
@Transactional
public Order updateOrderSafely(int id) {
return orderDao.updateOrder(id);
}
}
看起来好像是解决了事务未提交的问题,但会存在新的问题,可能会出现@Transactional事务不生效的情况
二、问题点 >》方法二引申:@Transactional事务不生效
在同一个类内部调用@Transactional标注的方法事务也不会开启,原因是:
@Transactional事务管理是基于动态代理对象的代理逻辑实现的,那么如果在类内部调用类内部的事务方法,这个调用事务方法的过程并不是通过代理对象来调用的,而是直接通过this对象来调用方法,绕过的代理对象,肯定就是没有代理逻辑了
方法:将锁提取到controller层,不包含任何事务
Controller类
@RestController
public class TestController {
@Resource
private TestService testService;
@PostMapping("/test")
public synchronized void testInterface() {
testService.functionA(); // 调用数据库操作方法
}
}
Service类
@Service("testService")
public class TestServiceImpl implements TestService {
@Transactional(rollbackFor = Exception.class,
propagation = Propagation.REQUIRES_NEW)
public void functionA() {
//数据库读写操作
}
}
标签:事务,调用,synchronized,Transactional,public,方法,遇上
From: https://blog.csdn.net/qq_36451127/article/details/140489022