开端
之前在面试的时候,被面试官问到了JDK动态代理和Transaction可能会带来什么问题,当时脑子里突然懵了,不知道面试官想说什么,再细问的时候,面试官就一带而过了,不过现在想想面试官也是在考研我们的实战经验,做了一番查询后发现,在某些场景下可能会有事务失效的问题。
一、问题重现
我们先上例子
例子一:
@Override
@Transactional
public void save1() {
Account account = new Account();
account.setName("zhangsan");
account.setMoney(100f);
dao.insert(account);
save2();
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void save2() {
Account account = new Account();
account.setName("lisi");
account.setMoney(200f);
dao.insert(account);
}
现在有两个方法,save1()和save2(),在某些业务中,我们需要save1()调用save2(),但是save2方法执行失败的话,不能影响到save1(),这个时候我们使用Propagation.REQUIRES_NEW来重开一个事务,测试代码如下
@Test
public void save1() {
accountService.save1();
}
执行save1()后,数据库正常保存。
,然后我们在save2()显示的抛出一个异常,代码如下
例子二:
@Override
@Transactional
public void save1() {
Account account = new Account();
account.setName("zhangsan");
account.setMoney(100f);
dao.insert(account);
save2();
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void save2() {
Account account = new Account();
account.setName("lisi");
account.setMoney(200f);
dao.insert(account);
System.out.println(1/0);
}
这个时候程序会报错,但是我们看下数据库
save1()的方法也没有执行成功,这是为什么呢,正常来说我们已经在save2()重新开了一个事务,save2()报错不应该影响save1()才对啊,这个时候想到了可能是save2()方法报错并没有catch住,导致save1()方法也报错了,我们再改下代码
例子三:
@Override
@Transactional
public void save1() {
Account account = new Account();
account.setName("zhangsan");
account.setMoney(100f);
dao.insert(account);
save2();
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void save2() {
Account account = new Account();
account.setName("lisi");
account.setMoney(200f);
dao.insert(account);
try {
System.out.println(1/0);
}catch (ArithmeticException e){
System.out.println("不能除以零");
}
}
这个时候执行完成发现两条数据都插进了数据库,这又是怎么回事?完全不按照套路出牌啊,我是谁,我从哪里来,我要到哪里去....
二、分析问题本质
我们都知道Spring事务管理用的是动态代理的方式实现的,而动态代理有jdk动态代理和cglib动态代理两种,默认情况下且被代理类是接口的实现类的情况下,spring使用的是jdk代理。
当我们使用在调用save方法时,spring使用的是代理类来给管理我们的事务,如果我们使用spring注入的方式自然是没有问题的,可是如果在本类直接调用的话,并不是代理类调用,自然也就不会走代理了,save2()方法配置的事务也就不生效了,其实例子三相当于如下代码
例子四:
@Override
@Transactional
public void save1() {
Account account = new Account();
account.setName("zhangsan");
account.setMoney(100f);
dao.insert(account);
Account account = new Account();
account.setName("lisi");
account.setMoney(200f);
dao.insert(account);
try {
System.out.println(1/0);
}catch (ArithmeticException e){
System.out.println("不能除以零");
}
}
所以他们是在同一个事务中,不管怎么操作,都是同时提交或者同时回滚。