Spring事物你真的懂了吗?

1.Spring事物你真的懂了吗?

问题1:spring在什么情况下,会导致事物失效?

2.需求

数据库Mysql,采用默认的传播行为

要求:parent()方法调用child()方法。parent()为核心任务,必须执行。child()方法可有可无

3.模拟场景

3.1 模拟2个默认的传播行为

首先:都定义2个为默认的传播行为
先去看下数据库:
在这里插入图片描述

@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class TestTransactional1 {

    @Autowired
    private StudentDao studentMapper;

    /*
    * parent()调用child(),且child()抛出异常,那么执行结果是?
    * 猜想:
    * 一:都成功
    * 二:都失败
    * 三:parent()成功  child()失败
    * 
    * 真实执行结果:都失败。
    * */

    @Transactional
    @Test
    public void parent(){
        //调用child()方法。
        this.child();

        //核心任务必须执行
        StudentDO studentDO = new StudentDO();
        studentDO.setName("张三");
        studentMapper.save(studentDO);
    }

    //可有可无的业务
    @Transactional
    @Test
    public void child(){
        StudentDO studentDO = new StudentDO();
        studentDO.setName("李四");
        studentMapper.save(studentDO);
        //抛出异常
        int i = 1/0;
    }
}

当执行之后:
在这里插入图片描述
竟然都失败了。
这是为什么呢?咱们往下继续看

3.1.1 加try{ }catch{ }

这时肯定该有人说了,那就加try{ } catch{ } ,捕获组异常不就好了吗?哈哈,那咱们试试

@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class TestTransactional6 {

    @Autowired
    private StudentDao studentMapper;

    /*
    * parent()调用child(),且child()抛出异常,那么执行结果是?
    * 真实执行结果:都失败。
    * */
    @Transactional
    @Test
    public void parent(){
        //调用child()方法。
        try {
            this.child();
        }catch (Exception e){

        }

        //核心任务必须执行
        StudentDO studentDO = new StudentDO();
        studentDO.setName("张三");
        studentMapper.save(studentDO);
    }

    //解释:如果当前线程存在事物则将该事物挂起,并且自己新建一个事物继续运行,当新事物执行完毕后,唤醒之前挂起的事物,继续执行
    //如果当前不存在事物,则新建一个事物执行
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Test
    public void child(){
        StudentDO studentDO = new StudentDO();
        studentDO.setName("李四");
        studentMapper.save(studentDO);
        //抛出异常
        int i = 1/0;
    }
}

在这里插入图片描述
结果:都失败。 结论:当使用 this调用时, 加 try{ } catch { } 是控制不了事物的

3.1.2 调用方parent()有事物,被调用者child()没有事物,继续验证
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class TestTransactional2 {

    @Autowired
    private StudentDao studentMapper;

    /*
    * parent()调用child(),且child()抛出异常,那么执行结果是?
    * 猜想:
    * 一:都成功
    * 二:都失败
    * 三:parent()成功  child()失败
    *
    * 真实执行结果:都失败。
    * */

    @Transactional
    @Test
    public void parent(){
        //调用child()方法。
        this.child();

        //核心任务必须执行
        StudentDO studentDO = new StudentDO();
        studentDO.setName("张三");
        studentMapper.save(studentDO);
    }

    @Test
    public void child(){
        StudentDO studentDO = new StudentDO();
        studentDO.setName("李四");
        studentMapper.save(studentDO);
        //抛出异常
        int i = 1/0;
    }
}

执行结果:都失败
在这里插入图片描述

3.1.3 调用者parent()没有事物,被调用者child()有事物,继续验证】
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class TestTransactional3 {

    @Autowired
    private StudentDao studentMapper;

    /*
    * parent()调用child(),且child()抛出异常,那么执行结果是?
    * 真实执行结果:parent()成功  child()失败。
    * */
    @Test
    public void parent(){
        //调用child()方法。
        this.child();

        //核心任务必须执行
        StudentDO studentDO = new StudentDO();
        studentDO.setName("张三");
        studentMapper.save(studentDO);
    }

    @Transactional
    @Test
    public void child(){
        StudentDO studentDO = new StudentDO();
        studentDO.setName("李四");
        studentMapper.save(studentDO);
        //抛出异常
        int i = 1/0;
    }
}

在这里插入图片描述
真实执行结果:parent()成功 child()失败。

出现这种情形,各位应该都不意外。因为调用方parent()没有被事物控制,所以就算有异常,那么也不会回滚。
那么由此推算,前2种情形,是不是因为通过this.child()方法调用,让child()加入到parent()事物中了,所以当出现异常时,才会回滚呢? 继续往下看…

3.2 更改传播行为

根据child()设置的传播行为Propagation.REQUIRES_NEW,我们可能立马想到,
parent()成功、child()失败。
哈哈哈,如果你要是真要这么想,那你就错了,往下继续看

@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class TestTransactional4 {

    @Autowired
    private StudentDao studentMapper;

    /*
    * parent()调用child(),且child()抛出异常,那么执行结果是?
    * 真实执行结果:都失败。
    * */
    @Transactional
    @Test
    public void parent(){
        //调用child()方法。
        this.child();

        //核心任务必须执行
        StudentDO studentDO = new StudentDO();
        studentDO.setName("张三");
        studentMapper.save(studentDO);
    }

    //解释:如果当前线程存在事物则将该事物挂起,并且自己新建一个事物继续运行,当新事物执行完毕后,唤醒之前挂起的事物,继续执行
    //如果当前不存在事物,则新建一个事物执行
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Test
    public void child(){
        StudentDO studentDO = new StudentDO();
        studentDO.setName("李四");
        studentMapper.save(studentDO);
        //抛出异常
        int i = 1/0;
    }
}

在这里插入图片描述

真实执行结果:都失败。

结论:当主方法有事物,且使用this方式调用被调用者时,无论被调用者有没有事物,都会被加入到调用者中,任何一方出现异常那么都会回滚。

疑问:

这个结果是不是有一点疑惑,为什么通过this调用child()方法时,传播行为 @Transactional(propagation = Propagation.REQUIRES_NEW) 没有生效呢?

结论

其实这个地方涉及到aop的动态代理,this.child()调用方法时,其实this这个对象并不是代理的对象,而是当前的真实对象,所以this.child()的@Transactional(propagation = Propagation.REQUIRES_NEW) 传播行为才没有生效。

4.怎么解决?

@Service
public class StudentServiceImpl implements StudentService {

	@Autowired
	private StudentDao studentMapper;


	private StudentServiceImpl proxy;

	@Autowired
	private ApplicationContext applicationContext;

	@PostConstruct
	public void init(){
		proxy = applicationContext.getBean(StudentServiceImpl.class);
	}
	/*
	 * parent()调用child(),且child()抛出异常,那么执行结果是?
	 * 真实执行结果:都失败。
	 * */
	@Transactional
	@Test
	public void parent(){
		//解决方案(1):从spring应用上下文中获取代理对象
		proxy.child();

		//核心任务必须执行
		StudentDO studentDO = new StudentDO();
		studentDO.setName("张三");
		studentMapper.save(studentDO);
	}

	@Transactional(propagation = Propagation.REQUIRES_NEW)
	@Test
	public void child(){
		StudentDO studentDO = new StudentDO();
		studentDO.setName("李四");
		studentMapper.save(studentDO);
		//抛出异常
		int i = 1/0;
	}
}

解决方案(1):从spring应用上下文中获取代理对象。本示例是第一张

解决方案(2):从AOP中获取代理对象

StudentServiceImpl proxy= (StudentServiceImpl )AopContext.currentProxy();
proxy.child();

工具类

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SpringUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public static <T>T getBean(String beanName){
        return (T) applicationContext.getBean(beanName);
    }

    public static <T>T getBean(Class<T> clazz){
        return (T) applicationContext.getBean(clazz);
    }
}

总结:就是获取当前的代理对象,让aop进行管控事物

5.总结

当业务需求是:任何一方有异常,都要进行回滚时,可以使用this方法调用。
当业务需求是:被调用方有异常时,不能影响调用方主业务的执行,这时需采用代理方式

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值