1. 方法访问权限问题
java 访问权限有四种:private、default、protected、public, 它们的权限从左到右,依次变大。
例如:我们把方法的访问权限定义成非public,这样会导致事务失效
@Transactional
protected void update(WxGzhBean wxGzhBean) {
wxGzhMapper.update(wxGzhBean);
throw new RuntimeException("异常");
}
因为:在AbstractFallbackTransactionAttributeSource 类的 computeTransactionAttribute 方法中有个判断,如果目标方法不是public,则TransactionAttribute返回null,即不支持事务,所以想用事务就必须把它定义成public。
2. 方法用final、static修饰
有时候,某个方法不想被子类重写,这时可以将方法定义成final,普通方法这样定义是没有问题的,但如果是将事务方法定义成final,那么会导致事务失效,例如
@Transactional
public final void update(WxGzhBean wxGzhBean) {
wxGzhMapper.update(wxGzhBean);
throw new RuntimeException("异常");
}
// idea 也会提示你:@Transactional方法必须要可以被重写
因为:spring事务底层使用了aop,也就是通过jdk动态代理或者cglib,帮我们生成了代理类,在代理类中实现事务功能。如果某个方法用final修饰了,那么在它的代理类中就无法重写该方法实现事务功能。
同样,如果用static修饰,也是不能被动态代理的,static是随着类的加载而加载,是属于类, 所以静态方法是不能被重写的
3. 自调失效问题
在同一个类中,没有事务的A 去调用有事务的B,当B出现异常时事务不会回滚,出现自调失效问题
@Service
public class GzhService {
public void update(WxGzhBean wxGzhBean) {
updateState(wxGzhBean);
}
@Transactional
public void updateState(WxGzhBean wxGzhBean) {
wxGzhMapper.update(wxGzhBean);
throw new RuntimeException("异常");
}
}
原因:因为这种方式是直接调用了this对象的方法,并没有走代理对象,所以事务失效了。
解决:把自己注入进来,通过代理对象去调用
@Service
public class GzhService {
@Autowired
private GzhService gzhService;
public void update(WxGzhBean wxGzhBean) {
gzhService.updateState(wxGzhBean);
}
@Transactional
public void updateState(WxGzhBean wxGzhBean) {
wxGzhMapper.update(wxGzhBean);
throw new RuntimeException("异常");
}
}
4. 未被spring管理
使用spring事务的前提是:对象要被spring管理,需要创建bean实例。
通常情况下,我们通过@Controller、@Service、@Component等注解,可以自动实现bean实例化和依赖注入的功能。
例如,忘记给Service类加@Service注解:
// @Service
public class GzhService {
@Transactional
public void updateState(WxGzhBean wxGzhBean) {
wxGzhMapper.update(wxGzhBean);
throw new RuntimeException("异常");
}
}
5. 多线程调用
事务方法A中调用了事务方法B,但是事务方法B是在另外一个线程中调用的。这样会导致两个方法不在同一个线程中,获取到的数据库连接不一样,从而是两个不同的事务。如果B方法中抛出了异常,A方法也跟着回滚是不可能的。例如:
@Service
public class GzhService {
@Transactional
public void updateState(WxGzhBean wxGzhBean) {
Thread t = new Thread(() -> {
subService.updateState(wxGzhBean);
throw new NullPointerException("异常");
});
t.start();
return wxGzhMapper.update(wxGzhBean);
}
}
@Service
public class SubService {
@Transactional
public void updateState(WxGzhBean wxGzhBean) {
wxGzhMapper.update(wxGzhBean);
throw new RuntimeException("异常");
}
}
6. 选择错误的传播行为
spring有7种事务传播行为
- PROPAGATION_REQUIRED 支持当前事务,假设当前没有事务。就新建一个事务
- PROPAGATION_SUPPORTS 支持当前事务,假设当前没有事务,就以非事务方式运行
- PROPAGATION_MANDATORY 支持当前事务,假设当前没有事务,就抛出异常
- PROPAGATION_REQUIRES_NEW 新建事务,假设当前存在事务。把当前事务挂起
- PROPAGATION_NOT_SUPPORTED 以非事务方式运行操作。假设当前存在事务,就把当前事务挂起
- PROPAGATION_NEVER 以非事务方式运行,假设当前存在事务,则抛出异常
- PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则新建事务
2,5,6需要谨慎使用,会造成事务失效
目前只有三种传播属性会创建新事务:NESTED, REQUIRES_NEW, REQUIRED
NEVER 例子:
@Service
public class GzhService {
@Transactional
public Object update(WxGzhBean wxGzhBean) throws Exception {
return subService.update(wxGzhBean);
}
}
@Service
public class SubService {
@Transactional(propagation = Propagation.NEVER)
public Object update(WxGzhBean wxGzhBean) throws Exception {
wxGzhMapper.update(wxGzhBean);
throw new RuntimeException("异常");
}
}
NEVER 以非事务方式执行,如果当前存在事务,则抛出异常:Existing transaction found for transaction marked with propagation ‘never’
7. 异常未抛出
开发者在代码中手动try catch了异常,比如:
@Transactional
public void update(WxGzhBean wxGzhBean) {
try {
wxGzhMapper.update(wxGzhBean);
throw new RuntimeException("异常");
} catch (Exception e) {
Console.log(e.getStackTrace());
}
}
事务是通过有没有出现异常来判断是否要回滚的,因为异常被捕获住了,又没有手动抛出,spring认为程序是正常的,当然不会回滚。
8. 抛出的异常与rollbackFor的异常不符时
rollbackFor属性: 只有当出现指定异常才会回滚, 当抛出的异常与rollbackFor的异常不符时,会出现事务失效
@Transactional(rollbackFor = RuntimeException.class)
public void update(WxGzhBean wxGzhBean) throws Exception {
wxGzhMapper.update(wxGzhBean);
throw new Exception("异常");
}
spring事务默认情况下,只会回滚 RuntimeException(运行时异常)和 Error(错误),对于普通的 Exception (非运行时异常),它不会回滚
9. rollback-only 事务回滚
当事务嵌套时,比如事务A调用事务B,当B出现异常,事务会被标记为全局回滚,最外层事务A在commit时发现事务已打上全局回滚标签,于是先回滚事务,再抛出rollback-only 异常( transaction rolled back because it has been marked as rollback-only 事务回滚,因为它被标记为仅回滚)
@Transactional
public void a(WxGzhBean wxGzhBean) {
try {
gzhService.b(wxGzhBean);
} catch (Exception e) { }
}
@Transactional
public void b(WxGzhBean wxGzhBean) {
wxGzhMapper.update(wxGzhBean);
throw new RuntimeException("异常");
}
如果想要子事务回滚不影响父事务,修改传播行为requires_new 挂起当前事务,新建一个新事务,这样两个事务互不影响