项目中上锁后仍出现重复数据
Ⅰ事务失效场景
1.数据库引擎采用的是MyISAM,MyISAM不支持事务,InnoDB支持事务
2.Spring的事务管理默认只对出现运行期异常(java.lang.RuntimeException及其子类)进行回滚,业务自定义的异常不支持回滚
@Transactional
@Override
public void dealMessage(){
save();
ExceptionUtil.throwServiceException(StatusCodeExtendEnum.MISSING_SOME_COMMUNITY_BLOCK);
}
3.类没有被代理
@Transactional
public void dealMessage(){
save();
int i = 1/0;
}
如上,dealMessage是普通方法,没有注入spring容器中,为什么没有注入spring容器中,事务就不生效呢?
原因:spring启动的时候,会扫描所有注入spring容器中的bean,判断bean的方法上是否有@Transactional注解,如果有,便为这个bean自动生成一个代理类,在代理类调用之前开启事务
而上面的例子中,dealMessage没有注入容器中,那么spring不会给他生成代理类、开启事务
4.service方法中调用本类中的另一个方法
@Service
public class MessageServiceImpl implements IMessageServiceImpl {
@Override
public void dealMessage(Message message){
save(message);
}
@Transactional
@Override
public void save(Message message){
messageRepository.save(message);
int i = 1/0;
}
}
如上,controller层并没有直接调用save方法,而是通过dealMessage调用的save方法,那么为什么同一个类中通过普通的方法去调用带有事务注解的方法,会导致事务失效?
原因:spring启动扫描bean的时候,判断方法上是否有@Transactional注解,如果有,便为这个bean自动生成一个代理类,当这个bean被调用的时候,是通过代理类来调用的,代理类调用之前就会开启事务
但是上面的例子,在同一个类中通过没有事务注解的方法去调用有事务注解的方法,那么此时的调用没有通过代理类,也不会开启事务
解决方案:
将dealMessage和save放在不同类中
service自己注入自己,自己调用save方法
@Service
public class MessageServiceImpl implements IMessageServiceImpl {
@Autowired
private IMessageServiceImpl messageServiceImpl;
@Override
public void dealMessage(Message message){
messageServiceImpl.save(message);
}
@Transactional
@Override
public void save(Message message){
messageRepository.save(message);
int i = 1/0;
}
}
5.内部使用了try catch,吃掉异常,导致事务不生效
@Transactional
@Override
public void dealMessage(Message message){
try {
save(message);
}catch (Exception e){
}
}
6.在事务的方法内部,另起子线程执行 db 操作
@Transactional
@Override
public void dealMessage(Message message){
new Thread(() -> {
save(message);
}).start();
}
Ⅱ有事务时,锁失效的场景
@Transactional
@Override
public synchronized void dealMessage(Integer messageId){
Message message = getMessage(messageId);
save(message);
}
如上,在多线程的情况下会插入大量重复的数据,从表面上来看, synchronized关键字保证了dealMessage方法的可见性、原子性、有序性,但还是出现了大量重复数据,这是为什么?
上面我们知道了,事务的开启是通过代理类,那我们先看一下TransactionAspectSupport 类中的 invokeWithinTransaction()方法
@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass, TransactionAspectSupport.InvocationCallback invocation) throws Throwable {
// 获取对应事务属性
TransactionAttributeSource tas = this.getTransactionAttributeSource();
TransactionAttribute txAttr = tas != null ? tas.getTransactionAttribute(method, targetClass) : null;
// 根据事务的属性获取beanFactory中的PlatformTransactionManager(spring事务管理器的顶级接口)
PlatformTransactionManager tm = this.determineTransactionManager(txAttr);
// 目标方法唯一标识
String joinpointIdentification = this.methodIdentification(method, targetClass, txAttr);
Object result;
// 编程式事务处理
if (txAttr != null && tm instanceof CallbackPreferringPlatformTransactionManager) {
...
} else {
// ➀ 根据事务传播行为,看是否有必要创建一个事务
TransactionAspectSupport.TransactionInfo txInfo = this.createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
result = null;
try {
// ➁ 执行目标方法
result = invocation.proceedWithInvocation();
} catch (Throwable var17) {
// ➂ 异常回滚
this.completeTransactionAfterThrowing(txInfo, var17);
throw var17;
} finally {
// ➃ 清除信息
this.cleanupTransactionInfo(txInfo);
}
// ➄ 提交事务
this.commitTransactionAfterReturning(txInfo);
return result;
}
}
通过源码得知,事务是在整个方法执行结束以后才会提交事务,也就是说,当线程A执行完dealMessage方法后,线程B随后进入dealMeaage()方法,此时A执行后的事务还未提交,那么B获取到的数据就是旧数据,也执行插入操作,导致出现大量的重复数据
解决方法:在事务开启之前添加锁
原文:https://blog.csdn.net/weixin_42068745/article/details/110957768