分析事务失效场景,以及存在事务时锁失效原因

Ⅰ事务失效场景

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获取到的数据就是旧数据,也执行插入操作,导致出现大量的重复数据

 解决方法:在事务开启之前添加锁

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值