Spring的事务有坑请注意
错误的访问权限
@Service public class UserService { @Autowired private UserMapper userMapper; @Transactional private void add(UserModel userModel) { userMapper.insertUser(userModel); } }
我们可以看到add方法的访问权限被定义成了private,这样会导致事务失效,spring要求被代理方法必须是public的。
AbstractFallbackTransactionAttributeSource类的computeTransactionAttribute方法中有个判断,如果目标方法不是public,则TransactionAttribute返回null,即不支持事务。
方法被定义成final的
@Service public class UserService { @Autowired private UserMapper userMapper; @Transactional public final void add(UserModel userModel) { userMapper.insertUser(userModel); } }
我们可以看到add方法被定义成了final的,这样会导致spring aop生成的代理对象不能复写该方法,而让事务失效。
方法内部调用
@Service public class UserService { @Autowired private UserMapper userMapper; @Transactional public void add(UserModel userModel) { userMapper.insertUser(userModel); updateStatus(userModel); } @Transactional public void updateStatus(UserModel userModel) { // doSameThing(); } }
我们看到在事务方法add中,直接调用事务方法updateStatus。从前面介绍的内容可以知道,updateStatus方法拥有事务的能力是因为spring aop生成代理了对象,但是这种方法直接调用了this对象的方法,所以updateStatus方法不会生成事务。
当前实体没有被spring管理
//@Service public class UserService { @Autowired private UserMapper userMapper; @Transactional public void add(UserModel userModel) { userMapper.insertUser(userModel); } }
我们可以看到UserService类没有定义@Service注解,即没有交给spring管理bean实例,所以它的add方法也不会生成事务。
错误的spring事务传播特性
@Service public class UserService { @Autowired private UserMapper userMapper; @Transactional(propagation = Propagation.NEVER) public void add(UserModel userModel) { userMapper.insertUser(userModel); } }
我们可以看到add方法的事务传播特性定义成了Propagation.NEVER,这种类型的传播特性不支持事务,如果有事务则会抛异常。只有这三种传播特性才会创建新事务:PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW,PROPAGATION_NESTED。
数据库不支持事务
msql8以前的版本数据库引擎是支持myslam和innodb的。我以前也用过,对应查多写少的单表操作,可能会把表的数据库引擎定义成myslam,这样可以提升查询效率。但是,要千万记得一件事情,myslam只支持表锁,并且不支持事务。所以,对这类表的写入操作事务会失效。
自己吞掉了异常
@Service public class UserService { @Autowired private UserMapper userMapper; @Transactional public void add(UserModel userModel) { try { userMapper.insertUser(userModel); } catch (Exception e) { log.error(e.getMessage(), e); } } }
这种情况下事务不会回滚,因为开发者自己捕获了异常,又没有抛出。事务的AOP无法捕获异常,导致即使出现了异常,事务也不会回滚。
抛出的异常不正确
@Service public class UserService { @Autowired private UserMapper userMapper; @Transactional public void add(UserModel userModel) throws Exception { try { userMapper.insertUser(userModel); } catch (Exception e) { log.error(e.getMessage(), e); throw new Exception(e); } } }
这种情况下,开发人员自己捕获了异常,又抛出了异常:Exception,事务也不会回滚。因为spring事务,默认情况下只会回滚RuntimeException(运行时异常)和Error(错误),不会回滚Exception。
多线程调用
@Service public class UserService { @Autowired private UserMapper userMapper; @Autowired private RoleService roleService; @Transactional public void add(UserModel userModel) throws Exception { userMapper.insertUser(userModel); new Thread(() -> { roleService.doOtherThing(); }).start(); } } @Service public class RoleService { @Transactional public 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");
我们说的同一个事务,其实是指同一个数据库连接,只有拥有同一个数据库连接才能同时提交和回滚。如果在不同的线程,拿到的数据库连接肯定是不一样的,所以是不同的事务。
嵌套事务多回滚了
public class UserService { @Autowired private UserMapper userMapper; @Autowired private RoleService roleService; @Transactional public void add(UserModel userModel) throws Exception { userMapper.insertUser(userModel); roleService.doOtherThing(); } } @Service public class RoleService { @Transactional(propagation = Propagation.NESTED) public void doOtherThing() { System.out.println("保存role表数据"); } }
这种情况使用了嵌套的内部事务,原本是希望调用roleService.doOtherThing方法时,如果出现了异常,只回滚doOtherThing方法里的内容,不回滚 userMapper.insertUser里的内容,即回滚保存点。。但事实是,insertUser也回滚了。
为什么呢?
因为doOtherThing方法出现了异常,没有手动捕获,会继续往上抛,到外层add方法的代理方法中捕获了异常。所以,这种情况是直接回滚了整个事务,不只回滚单个保存点。
怎么样才能只回滚保存点呢?
@Service public class UserService { @Autowired private UserMapper userMapper; @Autowired private RoleService roleService; @Transactional public void add(UserModel userModel) throws Exception { userMapper.insertUser(userModel); try { roleService.doOtherThing(); } catch (Exception e) { log.error(e.getMessage(), e); } } }
在代码中手动把内部嵌套事务放在try/catch中,并且不继续往抛异常。