关注“苏三说技术”,回复:开发手册、工夫治理 有惊喜。
对于从事java开发工作的同学来说,spring的事务必定再相熟不过了。在某些业务场景下,如果同时有多张表的写入操作,为了保障操作的原子性(要么同时胜利,要么同时失败)防止数据不统一的状况,咱们个别都会应用spring事务。
没错,spring事务大多数状况下,能够满足咱们的业务需要。然而明天我要通知大家的是,它有很多坑,稍不留神事务就会生效。
不信,咱们一起看看。
1.谬误的拜访权限
@Servicepublic class UserService { @Autowired private UserMapper userMapper; @Transactional private void add(UserModel userModel) { userMapper.insertUser(userModel); }}
咱们能够看到add办法的拜访权限被定义成了private,这样会导致事务生效,spring要求被代理办法必须是public的。
AbstractFallbackTransactionAttributeSource类的computeTransactionAttribute办法中有个判断,如果指标办法不是public,则TransactionAttribute返回null,即不反对事务。
protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) { // Don't allow no-public methods as required. if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) { return null; } // The method may be on an interface, but we need attributes from the target class. // If the target class is null, the method will be unchanged. Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass); // First try is the method in the target class. TransactionAttribute txAttr = findTransactionAttribute(specificMethod); if (txAttr != null) { return txAttr; } // Second try is the transaction attribute on the target class. txAttr = findTransactionAttribute(specificMethod.getDeclaringClass()); if (txAttr != null && ClassUtils.isUserLevelMethod(method)) { return txAttr; } if (specificMethod != method) { // Fallback is to look at the original method. txAttr = findTransactionAttribute(method); if (txAttr != null) { return txAttr; } // Last fallback is the class of the original method. txAttr = findTransactionAttribute(method.getDeclaringClass()); if (txAttr != null && ClassUtils.isUserLevelMethod(method)) { return txAttr; } } return null; }
2.办法被定义成final的
@Servicepublic class UserService { @Autowired private UserMapper userMapper; @Transactional public final void add(UserModel userModel) { userMapper.insertUser(userModel); }}
咱们能够看到add办法被定义成了final的,这样会导致spring aop生成的代理对象不能复写该办法,而让事务生效。
3.办法外部调用
@Servicepublic 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办法不会生成事务。
4.以后实体没有被spring治理
//@Servicepublic class UserService { @Autowired private UserMapper userMapper; @Transactional public void add(UserModel userModel) { userMapper.insertUser(userModel); } }
咱们能够看到UserService类没有定义@Service注解,即没有交给spring治理bean实例,所以它的add办法也不会生成事务。
5.谬误的spring事务流传个性
@Servicepublic 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。
6.数据库不反对事务
msql8以前的版本数据库引擎是反对myslam和innerdb的。我以前也用过,对应查多写少的单表操作,可能会把表的数据库引擎定义成myslam,这样能够晋升查问效率。然而,要千万记得一件事件,myslam只反对表锁,并且不反对事务。所以,对这类表的写入操作事务会生效。
7.本人吞掉了异样
@Slf4j@Servicepublic 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无奈捕捉异样,导致即便呈现了异样,事务也不会回滚。
8.抛出的异样不正确
@Slf4j@Servicepublic 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。
9.多线程调用
@Slf4j@Servicepublic 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(); }}@Servicepublic 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");
咱们说的同一个事务,其实是指同一个数据库连贯,只有领有同一个数据库连贯能力同时提交和回滚。如果在不同的线程,拿到的数据库连贯必定是不一样的,所以是不同的事务。
10.嵌套事务多回滚了**
public class UserService { @Autowired private UserMapper userMapper; @Autowired private RoleService roleService; @Transactional public void add(UserModel userModel) throws Exception { userMapper.insertUser(userModel); roleService.doOtherThing(); }}@Servicepublic class RoleService { @Transactional(propagation = Propagation.NESTED) public void doOtherThing() { System.out.println("保留role表数据"); }}
这种状况应用了嵌套的内部事务,本来是心愿调用roleService.doOtherThing办法时,如果呈现了异样,只回滚doOtherThing办法里的内容,不回滚 userMapper.insertUser里的内容,即回滚保留点。。但事实是,insertUser也回滚了。
why?
因为doOtherThing办法呈现了异样,没有手动捕捉,会持续往上抛,到外层add办法的代理办法中捕捉了异样。所以,这种状况是间接回滚了整个事务,不只回滚单个保留点。
怎么样能力只回滚保留点呢?
@Slf4j@Servicepublic 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中,并且不持续往抛异样。
介绍到这里,你会发现spring事务的坑还是挺多的。如果对这一块比拟感兴趣的敌人,请关注一下我的公众账号:苏三说技术,下次我将给大家解说spring事务的源码。