开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第15天,点击查看活动详情
前言
在java项目开发当中,我们基本上都会使用事务,最简便的就是通过Spring注解@Transactional,但是在使用Spring事务的过程中,我们还是有几个点需要特别注意的,否则就会造成事务失效的情况;
场景一:方法修饰符不是public
@Service
public class Service{
@Resource
private Mapper mapper;
@Transactional
private void update(Model model){
mapper.update(model);
}
}
上述代码中,我们将注解的方法修饰符写成了private,spring在代码这个方法时会判断方法是不是public修饰符,如果不是public,那么@Transactional将不会生效;
场景二:方法上使用final修饰
和第一个场景类似,我们在方法上加上final修饰符:
@Service
public class Service{
@Resource
private Mapper mapper;
@Transactional
public final void update(Model model){
mapper.update(model);
}
}
我们都知道,final代表这个方法不允许被重写,spring事务的实现是基于AOP实现的,AOP的底层是通过动态代理做的,动态代理的原理需要重写目标方法,所以这就从根本上限制了方法的重写,导致事务的失效;
场景三:同一个实例内调用其他事务方法
@Service
public class Service{
@Resource
private Mapper mapper;
@Transactional
public void update(Model model){
mapper.update(model);
updateStatus(status);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateStatus(int status){
mapper.updateStatus(status);
}
}
我们可以看出,被调用的updateStatus()是要在一个新事务中执行,但是直接在同一个实例中调用是不行的,最好还是额外再创建一个service,把updateStatus()方法挪到新的service中,通过调用新的service来达到目的;因为在同一个实例中调用,使用的是this.updateStatus(),并不是使用的代理类调用,所以也就导致了事务的失效;
场景四:多线程调用
@Service
public class ServiceA{
@Resource
private Mapper mapper;
@Autowired
private ServiceB serviceB;
@Transactional
public void update(Model model){
mapper.update(model);
new Thread(()->{
serviceB.add(model);
}).start();
}
}
@Service
public class ServiceB{
@Resource
private Mapper mapper;
@Transactional
public void add(Model model){
mapper.add(model);
}
}
在上面的代码中,我们在ServiceA中通过多线程的方式调用了ServiceB,这种情况ServiceA中的update()业务逻辑事务将失效;多线程中的serviceB中的事务成功与否不会对update()事务造成任何影响;因为在事务的实现中,是依赖于同一个DataSource的,不同线程中所持有的DataSource都不一样,也就是说,serviceA和serviceB不在一个事务中;
场景五:异常没有throw出来
@Service
public class Service{
@Resource
private Mapper mapper1;
@Resource
private Mapper mapper2;
@Transactional
public void update(Model model){
try{
mapper1.update(model);
mapper2.update(model);
}catch(Exception e){
log.error(e.getMessage(), e);
}
}
}
在上述代码中,我们自己把异常catch住了,这样意味着整个方法中并不会有异常抛出来,那么spring默认是会提交事务的,如果产生异常的部分是在mapper2.update(model),那么mapper1.update(model)依然会提交;
场景六:回滚异常不匹配
@Service
public class Service{
@Resource
private Mapper mapper1;
@Resource
private Mapper mapper2;
@Transactional
public void update1(Model model){
try{
mapper1.update(model);
mapper2.update(model);
}catch(Exception e){
log.error(e.getMessage(), e);
throw new Exception(e.getMessage());
}
}
@Transactional(rollbackFor = BusinessException.class)
public void update2(Model model){
try{
mapper1.update(model);
mapper2.update(model);
}catch(Exception e){
log.error(e.getMessage(), e);
throw new Exception(e.getMessage());
}
}
}
在上述代码中,我们在update1()方法中抛出来new Exception(),但是我们spring事务默认拦截的是RuntimeException才会回滚,所以update1()方法中的事务会失效;第二个update2()方法失效的原理一样,我们要求拦截到BusinessException才触发事务回滚,但是我们抛出的异常是new Exception(),同样也会导致事务失效,这就是因为触发回滚的异常与业务中抛出来的一样不一致导致的事务失效场景;