几种常见事务失效原因

方法自调用导致的事务失效

  • 场景:在Spring中,声明式事务通常是通过AOP(面向切面编程)实现的,这意味着事务管理是通过代理对象对目标方法的调用进行增强的。然而,当同一个类中的方法A调用方法B,且方法B上使用了@Transactional注解时,如果这种调用是直接进行的(即非通过代理对象),那么事务的增强处理就不会被触发,从而导致事务失效。
  • 原因:AOP代理通常是通过Spring的容器在运行时动态生成的,它仅对外部调用进行拦截和处理。对于类内部的直接方法调用,由于绕过了代理对象,因此无法应用事务的增强。
  • 解决方案:既然调用的不是代理对象,那我们就想办法获取代理对象,通过AopContext.currentProxy()获取当前类的代理对象,并通过该代理对象调用方法B。但请注意,这种方式需要确保在Spring配置中启用了@EnableAspectJAutoProxy(exposeProxy = true)

异常处理不当导致的事务失效

  • 场景:如果@Transactional注解的方法内部捕获了异常,并且没有将异常重新抛出,或者没有将捕获的异常类型指定为需要回滚的异常类型,那么Spring默认不会触发事务的回滚。

  • 原因:Spring根据异常的抛出情况来决定是否回滚事务。默认情况下,只有运行时异常(RuntimeException及其子类)和错误(Error)会触发回滚。对于受检异常(Exception的子类,但排除RuntimeException及其子类),除非在@Transactional注解中明确指定,否则不会触发回滚。

  • 解决方案

    确保事务内部的方法被正确抛出

    使用@Transactional注解的rollbackFor属性来指定需要回滚的异常类型,包括受检异常。

Public方法上的事务失效

  • 场景:如果@Transactional注解被放置在非public方法上(如private、protected或默认访问权限的方法),那么事务将不会生效。
  • 原因:Spring在创建代理对象时,仅会扫描和处理public方法的注解。非public方法由于访问权限的限制,无法被代理对象拦截和处理。
  • 解决方案:将事务的方法改为public修饰

事务传播行为不对导致的事务失效

  • 场景:在一个事务方法中如果调用了其他事务方法,而事务的传播行为不当,例如使用了REQUIRES_NEW,就表示了进入该方法创建了一个新事务,这样会导致事务失效
  • 原因:在抛出异常时,该方法作为独立事务,不会随主方法进行回滚
  • 解决方案:慎用传播行为

没有被Spring管理导致的事务失效

  • 场景:若一个方法没有被Spring管理,则事务失效
  • 原因:若不被Spring管理,就没有人替你生成代理对象,事务自然失效
  • 解决方案:注意该类有没有被Spring所管理

额外知识补充(事务传播行为)

  1. PROPAGATION_REQUIRED(默认):如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
  2. PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行。
  3. PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  4. PROPAGATION_REQUIRES_NEW:创建一个新的事务,并暂停当前事务(如果存在)。
  5. PROPAGATION_NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,则把当前事务挂起。
  6. PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
  7. PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则行为等同于PROPAGATION_REQUIRED

高并发时的优化方案

变同步为异步

适用范围

变同步为异步主要用于那些业务逻辑复杂、处理时间较长、且对实时性要求不是非常高的场景。具体来说,当系统面临大量并发请求,且每个请求都需要进行多个数据库操作或调用多个远程服务时,采用同步处理方式会导致线程阻塞,影响系统整体的并发能力。此时,可以考虑将部分或全部业务逻辑异步化,以提高系统的响应速度和吞吐量。

优化方案

我们可以通过MQ(消息队列)将同步业务变为异步业务,从而提高效率,在业务处理流程中,当需要异步处理的任务产生时,将任务封装成消息发送到MQ的交换机中。交换机根据路由规则将消息发送到指定的队列。创建消费者监听队列中的消息。当队列中有消息时,消费者从队列中获取消息并进行处理。

优点

  • 无需等待复杂业务处理,大大减少响应时间
  • 利用MQ暂存消息,起到流量削峰整形作用
  • 降低写数据库频率,减轻数据库并发压力

缺点

  • 依赖于MQ的可靠性
  • 降低了些频率,但是没有减少数据库写次数

合并写请求

适用范围

合并写请求主要用于那些写操作频繁且数据关联度较高的场景。例如,在电商系统中,用户可能同时购买多个商品并生成多个订单记录,这些订单记录可以合并为一个写请求发送到数据库进行处理。此外,对于日志记录、数据同步等场景,也可以考虑将多个写请求合并为一个进行处理。

优化方案

合并写请求就是指当写数据库并发较高时,不再直接写到数据库。而是先将数据缓存到Redis,然后定期将缓存中的数据批量写入数据库。由于redis时内存操作,所以写的效率大大提高,同时减少了DB操作,对数据库的压力减小

优点

  • 写缓存速度快,响应时间大大减少
  • 降低数据库的写频率和写次数,大大减轻数据库压力
  • 降低网络开销,提高数据传输效率

缺点

  • 实现相对复杂
  • 依赖Redis可靠性
  • 不支持事务和复杂业务