最近在开发过程中遇到了一个问题,当在Controller中调用Service中A()方法,A方法内部又调用Service中B()方法,由于A方法中只有查询操作所以没有加事务控制,B方法中含有多次修改操作所以增加了@Transactional注解,结果在A方法调用完B方法后,程序报错了,但是B方法中修改操作的数据竟然成功了,我擦~什么鬼,于是开启了探索Spring事务之路,直接上示例。
示例1:A方法无事务,B方法加事务
@RestController
public class Controller{
@Autowired
private StudentcardService studentcardService;
@RequestMapping(value = "/test/{id}}", method = RequestMethod.GET)
public Response queryStudentCard(@PathVariable("id") String id) {
studentcardService.updateA(id);
}
}
@Service
public class StudentcardServiceImpl implements StudentcardService {
@Resource
private StudentCardMapper studentCardMapper;
@Override
public void updateA(String id) {
//先去调用内部方法B
this.updateB(id);
StudentCard sc =new StudentCard();
sc.setScId(id);
//修改问题字段
sc.setQuestion("AAAAA");
studentCardMapper.update(sc);
}
@Override
@Transactional
public void updateB(String id) {
StudentCard sc =new StudentCard();
sc.setScId(id);
//修改答案字段
sc.setAnswer("BBBBB");
studentCardMapper.update(sc);
//修改完数据后报错
double i=1/0;
}
}
访问后执行结果如下:
然而事务并没有起作用~接着进行测试
示例2:将A方法加事务,B方法不加事务
@Service
public class StudentcardServiceImpl implements StudentcardService {
@Resource
private StudentCardMapper studentCardMapper;
@Override
@Transactional
public void updateA(String id) {
//先去调用内部方法B
this.updateB(id);
StudentCard sc =new StudentCard();
sc.setScId(id);
//修改问题字段
sc.setQuestion("AAAAA");
studentCardMapper.update(sc);
}
@Override
public void updateB(String id) {
StudentCard sc =new StudentCard();
sc.setScId(id);
//修改答案字段
sc.setAnswer("BBBBB");
studentCardMapper.update(sc);
//修改完数据后报错
double i=1/0;
}
}
访问后执行结果如下:
事务起作用了,都没有修改成功!接下来我们来个增强版,加上try后看下会有怎样的效果
示例3:A方法加事务,B方法没有事务,但是在A调用B方法时用try进行包裹,B方法中有错误
@Service
public class StudentcardServiceImpl implements StudentcardService {
@Resource
private StudentCardMapper studentCardMapper;
@Override
@Transactional
public void updateA(String id) {
//先去调用内部方法B
try {
this.updateB(id);
}catch (Exception e){}
StudentCard sc =new StudentCard();
sc.setScId(id);
//修改问题字段
sc.setQuestion("AAAAA");
studentCardMapper.update(sc);
}
@Override
public void updateB(String id) {
StudentCard sc =new StudentCard();
sc.setScId(id);
//修改答案字段
sc.setAnswer("BBBBB");
studentCardMapper.update(sc);
//修改完数据后报错
double i=1/0;
}
}
访问后执行结果如下:
由于报错被try包起来了,所以数据都插入了!那如果将报错信息放到执行完方法B后呢,会怎样呢?
示例4:A方法加事务,B方法没有事务,但是在A调用B方法时用try进行包裹,A方法中有错误
@Service
public class StudentcardServiceImpl implements StudentcardService {
@Resource
private StudentCardMapper studentCardMapper;
@Override
@Transactional
public void updateA(String id) {
//先去调用内部方法B
try {
this.updateB(id);
}catch (Exception e){}
StudentCard sc =new StudentCard();
sc.setScId(id);
//修改问题字段
sc.setQuestion("AAAAA");
studentCardMapper.update(sc);
//修改完数据后报错
double i=1/0;
}
@Override
public void updateB(String id) {
StudentCard sc =new StudentCard();
sc.setScId(id);
//修改答案字段
sc.setAnswer("BBBBB");
studentCardMapper.update(sc);
}
}
访问后执行结果如下:
哇塞,数据都没有插入呢!这是因为在事务提交前报错了,事务全部rollback了,下面言归正传,示例1为何不能成功呢?于是查询各种资料终于找到了缘由,并对示例1进行改造
示例5:A方法无事务,B方法加事务
@Service
@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
public class StudentcardServiceImpl implements StudentcardService {
@Resource
private StudentCardMapper studentCardMapper;
@Override
public void updateA(String id) {
//先去调用内部方法B
StudentcardService studentcardService = (StudentcardService) AopContext.currentProxy();
studentcardService.updateB(id);
//this.updateB(id);
StudentCard sc =new StudentCard();
sc.setScId(id);
//修改问题字段
sc.setQuestion("AAAAA");
studentCardMapper.update(sc);
}
@Override
@Transactional
public void updateB(String id) {
StudentCard sc =new StudentCard();
sc.setScId(id);
//修改答案字段
sc.setAnswer("BBBBB");
studentCardMapper.update(sc);
//修改完数据后报错
double i=1/0;
}
}
访问后执行结果如下:
哈哈,数据没有进行修改,事务起作用了!
下面说下具体对原因:
示例1 事务没有起作用,是由于Spring事务本质是基于AOP代理来实现的,当Controller调用Service的方法A是基于proxy的,所以会切入,但是方法A在调用方法B时,属于类内部调用,即使方法B上加上了@Transactional注解,但没有Spring代理了,所以不受事务控制,自然事务不会生效。
示例2 事务可以起作用是由于事务的传播行为导致的,默认事务的传播行为为:PROPAGATION_REQUIRED 。方法A标注了注解@Transactional ,执行的时候传播给方法B,因为方法A开启了事务,线程内的connection的属性autoCommit=false,并且执行到方法B时,事务传播依然是生效的,得到的还是方法A的connection,autoCommit还是为false,所以事务生效;反之,如果方法A没有注解@Transactional 时,是不受事务管理的,autoCommit=true,那么传播给方法B的也为true,执行完自动提交,即使B标注了@Transactional 事务也是不起作用的。
示例5 事务又可以起作用的,是由于我们在方法A调用方法B时,先获取到了Service的当前代理,然后用当前代理去调用方法B,所以事务当然会生效了~
顺便补充下事务的传播行为,事务的传播行为是为了解决业务层方法之间相互调用,产生的事务应该如何进行传递的问题。spring有如下7种传播行为:
1、PROPAGATION_REQUIRED:支持当前事务,如果当前不存在事务则新建一个。
2、PROPAGATION_SUPPORTS:支持当前事务,如果不存在,就不使用事务。
3、PROPAGATION_MANDATORY:支持当前事务,如果不存在,则抛出异常。
4、PROPAGATION_REQUIRES_NEW:如果当前有事务存在,挂起当前事务,创建一个新的事务。
5、PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前有事务存在,挂起当前事务。
6、PROPAGATION_NEVER:以非事务方式运行,如果当前有事务存在,抛出异常。
7、PROPAGATION_NESTED:如果当前存在一个事务,则该方法运行在一个嵌套的事务中。被嵌套的事务可以从当前事务中单独的提交和回滚。如果当前不存在事务,则开始一个新的事务。各厂商对这种传播行为的支持参差不齐,使用时需注意。