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方法调用。
当业务需求是:被调用方有异常时,不能影响调用方主业务的执行,这时需采用代理方式