@Transactional注解失效场景之——同类中方法调用,事务失效



该篇博客为总结自己曾写下的Bug

一、亲身案例

当时的场景为:在controller层获取一笔交易单的信息(前台传给controller层为Map类型的键值对),然后controller层直接将这个Map参数对象转发给Service层,在Service层进行一个对象属性的封装,继而调用mapper层接口完成数据的持久化。


问题出现在Service层,由于这一笔交易单信息会涉及多张表的数据信息,为了代码结构的优美,我特地在Service层的方法中,调用同类中的另一个doSave()方法来完成数据持久化保存。最后再潇洒的为doSave()方法添加一个@Transactional注解,扬长而去。



悄悄的说,因为我自己在测试过程中就发现了问题,但是无奈自己不知道具体的原因。我便将最容易出错的那一个保存操作提到最前然,最不容易出错的放在了最后面。


将当时的代码抽象成下面的形式,此时的@Transactional注解,可以说是加了,但是又没有完全加上。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8MFgIyQQ-1628934558390)(@Transactional注解失效.assets/image-20210814173649318.png)]

@Component
public class MobianServiceImpl implements MobianService {
    
    

	@Override
	public int saveInfo(InfoVo infoVo) {
    
    
		User user = new User();
		Hobby hobby = new Hobby();

        // 组装对象过程。。。
		user.setId(infoVo.getUserId());
		user.setName(infoVo.getUserName());
		hobby.setHobby(infoVo.getHobbyName());
		hobby.setUserId(infoVo.getUserId());
        
        // 具体的保存操作。。。
		int i = doSave(user, hobby);
		return i;

	}

	@Transactional
	public int doSave(User user, Hobby hobby){
    
    
		int i = mobianMapper.saveUser(user);
        // 发生异常
		int err = 1/0;
		int i1 = mobianMapper.saveHobby(hobby);
		return i + i1;
	}
}





二、改进方式

1、根据@Transactional注解的原理出发(这个方式显得很呆)

@Component
public class MobianServiceImpl implements MobianService {
    
    

	@Autowired
	private MobianMapper mobianMapper;

	@Autowired
	private MobianServiceImpl mobianService;

	@Override
	public int saveInfo(InfoVo infoVo) {
    
    
		User user = new User();
		Hobby hobby = new Hobby();

        // 组装对象过程。。。
		user.setId(infoVo.getUserId());
		user.setName(infoVo.getUserName());
		hobby.setHobby(infoVo.getHobbyName());
		hobby.setUserId(infoVo.getUserId());
        
        // 具体的保存操作。。。
        // 获取Spring容器中的对象
		int i = mobianService.doSave(user, hobby);
		return i;
	}

	@Transactional
	public int doSave(User user, Hobby hobby){
    
    
		int i = mobianMapper.saveUser(user);
        // 发生异常
		int err = 1/0;
		int i1 = mobianMapper.saveHobby(hobby);
		return i + i1;
	}
}

2、将doSave方法写在其他的service类中,用一个service类调用另一个service类

3、直接在saveInfo方法上面添加@Transactional注解

4、int i = ((MobianServiceImpl ) AopContext.currentProxy()).doSave(user, hobby)

从底层触发,去AOP容器中获取对应的被动态处理后的doSave方法




三、原理分析

Spring中对于数据库事务的约定,其实现原理是AOP,而AOP又是通过动态代理去实现。(AOP是在Spring的生命周期中完成)。

以我们上面代码中为例,我们在方法内部调用doSave(),等同于this.doSave(),此处的this表示new MobianServiceImpl()对象,即调用这个类中的这个方法完成持久化。这是一个自调用的形式。



解决方法一,方式一之所以能够实现,是因为我们去Spring容器中获取到的mobianService容器对象不等于我们直接new出来的对象。有看过我之前文章的小伙伴就应该知道,他们为什么不是同一个:浅谈Spring中Bean的生命周期,Spring容器中获取的是new出来的对象的代理对象,前者包含后者。换句话说,Spring容器中的对象经过了AOP处理(处理@Transactional注解),所以具有事务的特点。但是我们直接new出来的对象,仅仅是一个对象,不具备事务的特点。


解决方法二,也是相同的原理来理解,因为我们由一个service调用另一个service时,另一个对象就是去Spring容器中获取的,这样就能以一种看似不起眼的方式转移解决方法一中看起来很瓜的方式。


解决方法三,直接在最顶层添加事务,这种方式很粗暴,可能会出现在某些复杂的业务场景中不适用的情况。




该Bug解决的场景为,写下Bug代码半年后的某天早晨,因为睡不着,起来看书突然就看到了这么一段对于这个注解的描述,瞬间茅塞顿开,后面去公司马上找到以前的代码,发现果然是这个问题。我真菜!!!

猜你喜欢

转载自blog.csdn.net/qq_44377709/article/details/119704165