文章目录
Spring的事务失效的11种场景
先介绍下Spring的事务:
Spring的事务主要通过@Transactional来管理事务,底层用到了aop,也就是动态代理:jdk,cglib动态代理。对标记的类或方法进行代理生产代理类,然后增强其方法执行。
其参数主要有三方面功能:
- 传播行为:@Transactional注解中有7中事务传播行为,对加该注解的方法调用了同样加该注解的方法的处理行为。
- 事务隔离级别:四种事务隔离级别。主要是根据数据库选择的,如果使用的数据库不支持事务或者不支持相应的事务的话,则Spring不能对其进行事务的管理。
- 异常类型处理:@Transactional注解的参数中可以设置针对于什么类型的异常而选择是否在异常时回滚事务。
抛出检查异常导致事务不能正确回滚
因为Spring默认只能对非检查异常进行回滚。
那么怎么处理呢?可以配置rollbackFor参数,把Exception异常类型设置进去。
aop切面顺序导致事务不能正确回滚
原因:事务切面优先级最低,但如果自定义的切面优先级和它一样,则还是自定义切面在内层,这时如果自定义的切面没有正确抛出异常,则事务的切面得不到异常信息,则无法正常回滚。
解决方案:
- 抛出异常
- 调整切面顺序
未被Spring管理
如果事务方法所在的类没有交给IOC管理的话,那么@Transactional注解就会失效。因为@Transactional注解的的原理就是用到了aop实现的,aop只能对IOC管理的bean进行代理,那么如果该类没有被IOC管理的话,那么也就没有办法使用aop,那么该类上的事务方法也就失效了。
访问权限问题
spring中只允许public权限的方法使用Transactional注解。在computeTransactionAttribute方法中会对反射的方法进行检查判断是否是public权限, 如果是则可以使用,否则则不能使用事务。
方法用final修饰
因为Spring的事务管理底层用到了aop,而aop用到了动态代理,动态代理分为:jdk和cglib两种。这两种分别是实现被代理类的接口生成的代理类和继承被代理类生成的代理类。final修饰的方法是不能被重写的,那么也就没有办法对它进行代理。那么在这种方法上使用@Transactional注解也就没有效果。
调用内部方法
其实本质还是代理问题。假设有a,b两个在同一个类的事务方法。那么在调用a方法时会生成一个代理类在该代理类中执行这个方法,那么如果a调用b方法的话,就没有办法生成代理类对b方法的管理,那么b方法的事务功能也就失效了。
解决方案:
- 自己依赖注入自己,注入的是代理对象
- AopContext.currentProxy()拿到当前上下文对象
方法的事务传播行为不支持事务
例如调用的方法的事务传播类型为NOT_SUPPORTED,NOT_SUPPORTED:如果当前上下文中存在事务的话,则挂起当前事务,然后该方法在没有事务的环境下执行。那么这种情况本方法中的事务对其不起作用。
异常在方法的catch中被处理
其实Spring事务的本质就是开启数据库事务,如果有异常对其进行回滚。如果在事务方法中已经把异常处理了,那么异常抛不出来,生成的代理类中接不到异常也就没有办法对其回滚。
解决:
- 抛出异常
- TransactionInterceptor.currentTransactionStatus.setRollbackOnly() 设置事务状态
数据库不支持事务
如果使用的数据库不支持事务的话,那么Spring也就没办法对其进行管理,加@Transactional注解也就没有效果。
未配置开启事务
@EnableTransactionManagement,没有使用这个注解的话事务就不会进行管理。
错误的传播特性
如果将propagation设置为Propagation.NEVER的话,则该事务方法被调用时,如果上下文中存在事务则报错,否则则在无事务的环境下执行。
多线程调用
当一个事务方法被调用时,会在当前线程的ThreadLocal中记录事务的状态,从而决定是否回滚。在不同的线程中运行事务方法每个线程中的事务状态也就可能不同,可能导致事务不一致的问题。
如果解决:可以用事务的传播行为Propagation.REQUIRES_NEW,它会将上下文中的事务挂起,建一个新的事务,运行事务方法,完成后,再恢复上下文中的事务。
- 不能通过加锁来处理,因为锁只对当前方法加锁,方法外的语句是没有锁的。但是可以扩大范围加锁。要加在代理方法上,而不是目标方法上。 —Java层面
- select … for update 查询加锁 —数据库层面