1.注解的方法不是用public修饰
@Service
public class UserService {@Transactionalprivate void add(UserModel userModel) {saveData(userModel);updateData(userModel);}
}
我们可以看到add方法的访问权限被定义成了private,这样会导致事务失效,spring要求被代理方法必须得是public的。
spring事务底层使用了aop,也就是通过jdk动态代理或者cglib,帮我们生成了代理类,在代理类中实现的事务功能。但如果某个方法用private修饰了,那么在它的代理类中,就无法重写该方法,而添加事务功能。
2.方法用final修饰
同理,如果方法用final修饰,也是没法被动态代理的类重写的,因此注解也会失效。
@Service
public class UserService {@Transactionalpublic final void add(UserModel userModel){saveData(userModel);updateData(userModel);}
}
3.同一个类中的方法直接在方法内部调用
public void test(){QueryWrapper<StoreRebateCalculateLog> queryWrapper;queryWrapper = new QueryWrapper<>();queryWrapper.eq("delete_flag", 0);List<StoreRebateCalculateLog> storeRebateCalculateLogs1 = storeRebateCalculateLogDao.selectBatchIds(Arrays.asList(90010, 90011, 90012, 90013));updateTest(storeRebateCalculateLogs1);}@Transactional(rollbackFor = Exception.class)public void updateTest(List<StoreRebateCalculateLog> storeRebateCalculateLogs1) {for (StoreRebateCalculateLog log: storeRebateCalculateLogs1) {log.setNeedRepeatCalFlag("0");this.storeRebateCalculateLogDao.updateById(log);if(log.getCalculateLogId() == 90012){throw new IllegalArgumentException("lll");}}}
如上,如果是在同一个类中调用被@Transactional注解的方法,那么也不会生效,因为调用的是this对象的方法,而不是代理对象的方法。
可以打断点调试看到这个现象。原因是在InvocationHandlerImpl#invoke中method.invoke(subject, args);这里调用的是目标类subject的方法,直接执行目标类方法,不会执行代理类的方法。
那么如果有些时候一定想在同一个类中调用,有没有办法呢?答案是可以的。
3.1新加一个Service方法
这个方法非常简单,只需要新加一个Service方法,把@Transactional注解加到新Service方法上,把需要事务执行的代码移到新方法中。具体代码如下:
@Servcie
public class ServiceA {@Autowiredprvate ServiceB serviceB;public void save(User user) {queryData1();queryData2();serviceB.doSave(user);}}@Servciepublic class ServiceB {@Transactional(rollbackFor=Exception.class)public void doSave(User user) {addData1();updateData2();}}
这种方法还是有点麻烦,需要多建一个类,那么可不可以不用新建类呢?
3.2直接在Service中注入自己
@Servcie
public class ServiceA {@Autowiredprvate ServiceA serviceA;//注入自己public void save(User user) {queryData1();queryData2();serviceA.doSave(user);}@Transactional(rollbackFor=Exception.class)public void doSave(User user) {addData1();updateData2();}}
这种办法虽然可以解决问题,但是看起来有点别扭。
3.3使用AopContext类直接获取当前类代理对象
@Overridepublic void test(){QueryWrapper<StoreRebateCalculateLog> queryWrapper;queryWrapper = new QueryWrapper<>();queryWrapper.eq("delete_flag", 0);List<StoreRebateCalculateLog> storeRebateCalculateLogs1 = storeRebateCalculateLogDao.selectBatchIds(Arrays.asList(90010, 90011, 90012, 90013));//使用当前类的代理对象调用((StoreRebateCalculateLogServiceImpl) AopContext.currentProxy()).updateTest(storeRebateCalculateLogs1);}@Transactional(rollbackFor = Exception.class)public void updateTest(List<StoreRebateCalculateLog> storeRebateCalculateLogs1) {for (StoreRebateCalculateLog log: storeRebateCalculateLogs1) {log.setNeedRepeatCalFlag("0");this.storeRebateCalculateLogDao.updateById(log);if(log.getCalculateLogId() == 90012){throw new IllegalArgumentException("lll");}}}
可以打断点调试发现,此时执行的是当前类的代理对象:
@Servcie
public class ServiceA {public void save(User user) {queryData1();queryData2();((ServiceA)AopContext.currentProxy()).doSave(user);}@Transactional(rollbackFor=Exception.class)public void doSave(User user) {addData1();updateData2();}}
4.类没有被spring管理
这种正常情况一般不会出现,但是在忘记了的情况下可能出现。如:类上面的@Service注解没有
//@Service
public class UserService {@Transactionalpublic void add(UserModel userModel) {saveData(userModel);updateData(userModel);}
}
5.调用的方法与被调用的注解的方法不是同一个线程
@Slf4j
@Service
public class UserService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate RoleService roleService;@Transactionalpublic void add(UserModel userModel) throws Exception {userMapper.insertUser(userModel);new Thread(() -> {roleService.doOtherThing();}).start();}
}@Service
public class RoleService {@Transactionalpublic void doOtherThing() {System.out.println("保存role表数据");}
}
事务方法add中,调用了事务方法doOtherThing,但是事务方法doOtherThing是在另外一个线程中调用的。
这样会导致两个方法不在同一个线程中,获取到的数据库连接不一样,从而是两个不同的事务。如果想doOtherThing方法中抛了异常,add方法也回滚是不可能的。
如果看过spring事务源码的朋友,可能会知道spring的事务是通过数据库连接来实现的。当前线程中保存了一个map,key是数据源,value是数据库连接。
private static final ThreadLocal<Map<Object, Object>> resources =new NamedThreadLocal<>("Transactional resources");
6.错误的传播特性
@Service
public class UserService {@Transactional(propagation = Propagation.NEVER)public void add(UserModel userModel) {saveData(userModel);updateData(userModel);}
}
add方法的事务传播特性定义成了Propagation.NEVER
,这种类型的传播特性是不支持事务的,如果有事务则会抛异常。
目前只有这三种传播特性才会创建新事务:REQUIRED
,REQUIRES_NEW
,NESTED
。
关于事务的传播特性,可以参考:
事务传播行为总结
7.异常被自己捕获了没有往外抛
@Slf4j
@Service
public class UserService {@Transactionalpublic void add(UserModel userModel) {try {saveData(userModel);updateData(userModel);} catch (Exception e) {log.error(e.getMessage(), e);}}
}
这种情况下spring事务当然是不会回滚,因为开发者自己捕获了异常,又没有手动抛出,换句话说就是把异常吞掉了。
另外如果想要spring事务能够正常回滚,必须抛出它能够处理的异常。如果没有抛异常,则spring认为程序是正常的。
8.异常不是运行时异常
@Slf4j
@Service
public class UserService {@Transactionalpublic void add(UserModel userModel) throws Exception {try {saveData(userModel);updateData(userModel);} catch (Exception e) {log.error(e.getMessage(), e);throw new Exception(e);}}
}
上面的这种情况,开发人员自己捕获了异常,又手动抛出了异常:Exception
,事务同样不会回滚。
因为spring事务,默认情况下只会回滚RuntimeException
(运行时异常)和Error
(错误),对于普通的Exception
(非运行时异常),它不会回滚。比如常见的IOExeption
和SQLException