对于从事java开发工作的同学来说;spring的事务肯定再熟悉不过了。在某些业务场景下;如果同时有多张表的写入操作;为了保证操作的原子性;要么同时成功;要么同时失败;避免数据不一致的情况;我们一般都会使用spring事务。没错;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;即不支持事务。
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;
}
;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方法不会生成事务。
//;Service
public class UserService {
;Autowired
private UserMapper userMapper;
;Transactional
public void add(UserModel userModel) {
userMapper.insertUser(userModel);
}
}
我们可以看到UserService类没有定义;Service注解;即没有交给spring管理bean实例;所以它的add方法也不会生成事务。
;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和innerdb的。我以前也用过;对应查多写少的单表操作;可能会把表的数据库引擎定义成myslam;这样可以提升查询效率。但是;要千万记得一件事情;myslam只支持表锁;并且不支持事务。所以;对这类表的写入操作事务会失效。
;Slf4j
;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无法捕获异常;导致即使出现了异常;事务也不会回滚。
;Slf4j
;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。
;Slf4j
;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也回滚了。
why?
因为doOtherThing方法出现了异常;没有手动捕获;会继续往上抛;到外层add方法的代理方法中捕获了异常。所以;这种情况是直接回滚了整个事务;不只回滚单个保存点。
怎么样才能只回滚保存点呢?
;Slf4j
;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中;并且不继续往抛异常。
Spring-Boot-+-Flowable-快速实现工作流