您当前的位置:首页 > 电脑百科 > 程序开发 > 框架

Spring事务失效常见的五种方式及其解决方案

时间:2023-05-26 13:46:21  来源:微信公众号  作者:小王博客基地
本文总结了Spring 声明式事务的源码实现、五种常见的事务失效情况,并提供了相应的解决方案。

一、前言

在Web 开发中,Spring 框架已经成为了众多开发者的首选。Spring 的声明式事务管理是其中最重要的特性之一,它可以帮助我们简化业务逻辑的复杂度,并且确保在出现异常情况时数据的一致性。

事务失效情况很常见,但我们只要注意,就可以避免事情发生!在本文中,我将详细地介绍 Spring 声明式事务的源码实现和事务失效常见的五种情况,并给出有效的解决方案。

其实我们常说的事务失效是声明式事务(@Transactional)的失效,本文也是从声明式事务来进行演示的!

通过本文的学习,你将掌握如何正确地使用 Spring 的事务管理,减少生产事故。

「一定要保持数据一致性」。

二、@Transactional注解参数解读

我们拿出几个经常使用的参数来简单介绍一下:

  • propagation:指定事务的传播行为。其取值包括 REQUIRED、SUPPORTS、MANDATORY、REQUIRES_NEW、NOT_SUPPORTED、NEVER 和 NESTED 等。默认为 REQUIRED。 其中,REQUIRED 表示如果当前已经存在一个事务,则加入该事务,否则新建一个事务;而 REQUIRES_NEW 表示新建一个独立的事务,如果当前已经存在事务,则挂起当前事务。后面就不一一说了,大家可以自行百度哈!
  • isolation:指定事务的隔离级别。其取值包括 DEFAULT、READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ 和 SERIALIZABLE 等。默认为 DEFAULT。 其中,DEFAULT 表示采用数据库的默认隔离级别.
  • timeout:指定事务的超时时间,单位为秒。默认为 -1,表示不设置超时时间。如果在规定时间内事务还未完成,则抛出 TransactionTimedOutException 异常。
  • readOnly:指定事务是否只读,即是否允许修改数据。默认为 false,表示可以进行数据修改操作。如果将其设置为 true,则表示该事务仅能进行数据查询操作,不能进行数据修改操作,这样可以提高并发性能。
  • rollbackFor:指定哪些异常需要回滚事务。其取值为一个 Class 数组,其中每个元素表示一个异常类型。默认为空,表示只有抛出 RuntimeException 或 Error 类型的异常时才回滚事务。
  • noRollbackFor:指定哪些异常不需要回滚事务。其取值为一个 Class 数组,其中每个元素表示一个异常类型。默认为空,表示抛出任何异常都回滚事务。

三、声明式事务源码实现

声明式事务实现类为:TransactionInterceptor ,下面我们来一起看看这个类!

源码版本为Springboot2.7.1。

public class TransactionInterceptor extends TransactionAspectSupport 
 implements MethodInterceptor, Serializable{}

TransactionInterceptor UML图:

图片

声明式事务主要是通过AOP实现,主要包括以下几个节点:

  1. 启动时扫描@Transactional注解:在启动时,Spring Boot会扫描所有使用了@Transactional注解的方法,并将其封装成TransactionAnnotationParser对象。
  2. AOP 来实现事务管理的核心类依然是 TransactionInterceptor。TransactionInterceptor 是一个拦截器,用于拦截使用了 @Transactional 注解的方法
  3. 将TransactionInterceptor织入到目标方法中:在AOP编程中,使用AspectJ编写切面类,通过@Around注解将TransactionInterceptor织入到目标方法中。
  4. 在目标方法执行前创建事务:在目标方法执行前,TransactionInterceptor会调用PlatformTransactionManager创建一个新的事务,并将其纳入到当前线程的事务上下文中。
  5. 执行目标方法:在目标方法执行时,如果发生异常,则将事务状态标记为ROLLBACK_ONLY;否则,将事务状态标记为COMMIT。
  6. 提交或回滚事务:在目标方法执行完成后,TransactionInterceptor会根据事务状态(COMMIT或ROLLBACK_ONLY)来决定是否提交或回滚事务。

源码:

@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {
 // Work out the target class: may be {@code null}.
 // The TransactionAttributeSource should be passed the target class
 // as well as the method, which may be from an interface.
 Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);

 // Adapt to TransactionAspectSupport's invokeWithinTransaction...
 return invokeWithinTransaction(invocation.getMethod(), targetClass, new CoroutinesInvocationCallback() {
  @Override
  @Nullable
  public Object proceedWithInvocation() throws Throwable {
   return invocation.proceed();
  }
  @Override
  public Object getTarget() {
   return invocation.getThis();
  }
  @Override
  public Object[] getArguments() {
   return invocation.getArguments();
  }
 });
}

下面是核心处理方法,把不太重要的代码忽略了,留下每一步的节点。

@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
 final InvocationCallback invocation) throws Throwable {
 // 获取事务属性
 final TransactionManager tm = determ.NETransactionManager(txAttr);
 // 准备事务
 TransactionInfo txInfo = prepareTransactionInfo(ptm, txAttr, joinpointIdentification, status);
 // 执行目标方法
 Object retVal = invocation.proceedWithInvocation();
  // 回滚事务
 completeTransactionAfterThrowing(txInfo, ex);
 // 提交事务
 commitTransactionAfterReturning(txInfo);
}

四、五种失效和解决方案

图片

下面我们从几个情况来给大家展示失效场景并给出解决方案。

1、类没有被 Spring 管理

public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addUser(User user) {
        userDao.addUser(user);
    }
}

如上代码所示,UserServiceImpl 类没有被声明为 Spring Bean,因此其中的 addUser() 方法无法受到 Spring 事务管理的保护。 我们使用Spring,要把类交给Spring进行管理,不然是无法生效!

「解决方案:」 交给spring进行管理bean,在类上添加:@Service!

2、方法不是public修饰

@Service
public class UserService {

    @Autowired
    private UserDao userDao;

    @Transactional(rollbackFor = Exception.class)
    protected void addUser(User user) {
        userDao.addUser(user);
    }
}

我们上面说了声明式事务是基于AOP实现的,AOP是通过代理模式实现的,即为目标对象生成一个代理对象,当调用代理对象的方法时,会自动添加事务的控制代码。 在这种情况下,如果事务注释所在的方法不是public的,则无法生成代理对象,因此事务代码将无法添加到方法执行前后,导致事务失效。

其实这种情况还是不经常这么使用,我们基本都是使用接口和实现大部分都是public修饰的!

「解决方案:」 使用public来修饰方法。

3、异常被捕获并处理了

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addUser(User user) {
        try {
            userDao.addUser(user);
        } catch (Exception e) {
            // 处理异常,但没有抛出或重新抛出异常
            log.error("add user error", e);
        }
    }
}

如上代码所示,如果 userDao.addUser() 方法抛出异常,但是在 UserServiceImpl.addUser() 中被捕获并处理了,事务检测不到有异常抛出,那么事务不会回滚。

「解决方案:」 catch 处理完成后,在重新把异常在抛出去:throw e。

4、同一个类中,方法内部调用

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;

    @Override
    public void addUser(User user) {
        doAddUser(user);
    }

    @Transactional(rollbackFor = Exception.class)
    public void doAddUser(User user) {
        userDao.addUser(user);
    }
}

Spring使用代理来实现事务控制,但是这种方法直接调用了this对象的方法,则无法通过代理来拦截该方法调用,从而使得事务失效。

「解决方案:」

推荐使用有两种:

  • 使用ApplicationContext来获取当前bean对象来调用doAddUser方法。
  • 在addUser方法加上@Transactional(rollbackFor = Exception.class)。

网上还有一些使用AopContext.currentProxy()拿到代理对象的、自己注入自己的、抽到单独的bean里的 这里小编不是很推荐!

方法一完整展示:

如果觉得Service里注入ApplicationContext 不优雅,可以抽到单独的工具bean里!

@Service
public class UserServiceImpl implements UserService {

 @Autowired
    private UserDao userDao;
    @Autowired
 private ApplicationContext applicationContext;

    @Override
    public void addUser(User user) {
     UserServiceImpl userService = applicationContext.getBean(UserServiceImpl.class);
        userService.doAddUser(user);
    }

    @Transactional(rollbackFor = Exception.class)
    public void doAddUser(User user) {
        userDao.addUser(user);
    }
}

5、MySQL存储引警不支持事务

MyISAM 存储引擎是 MySQL 的一种存储引擎,它是 MySQL 5.1 版本之前的默认存储引擎,它是不支持事务的。从 MySQL 5.5 版本开始,InnoDB 成为了 MySQL 的默认存储引擎。我们想使用也可以切换到MyISAM引擎。

「解决方案:」 把mysql换到5.5以上使用InnoDB 存储引擎。

「补充使用MyISAM 方式:」

  • 表从 InnoDB 引擎转换为 MyISAM 引擎:使用 ALTER TABLE 命令来更改表的引擎类型。
ALTER TABLE table_name ENGINE = MyISAM;
  • 默认的存储引擎设置为 MyISAM, 可以在 MySQL 配置文件中设置 default-storage-engine 参数。
 
default-storage-engine=MyISAM
  • 创建表时指定MyISAM 引擎 要将表的引擎类型设置为 MyISAM,请在 CREATE TABLE 语句中包含 ENGINE = MyISAM 子句
 
CREATE TABLE table_name (
    column1 datatype,
    column2 datatype,
    ...
) ENGINE = MyISAM;

五、总结

本文总结了Spring 声明式事务的源码实现、五种常见的事务失效情况,并提供了相应的解决方案。

当然还有很多情况:被final修饰、多线程调用、传播行为使用不当、抛的异常不对应等等

理解 Spring 事务机制的,深入了解 Spring 事务的内部原理。同时,在使用声明式事务的过程中,我们也可以针对自己的业务场景进行定制化的配置,比如指定特定的事务传播机制、设置超时时间等,这些都有助于更好地应对复杂的业务场景和代码需求。这样才能真正地提高系统的可维护性、可扩展性和稳定性。



Tags:Spring   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
Spring Security:保障应用安全的利器
SpringSecurity作为一个功能强大的安全框架,为Java应用程序提供了全面的安全保障,包括认证、授权、防护和集成等方面。本文将介绍SpringSecurity在这些方面的特性和优势,以及它...【详细内容】
2024-02-27  Search: Spring  点击:(54)  评论:(0)  加入收藏
Spring Security权限控制框架使用指南
在常用的后台管理系统中,通常都会有访问权限控制的需求,用于限制不同人员对于接口的访问能力,如果用户不具备指定的权限,则不能访问某些接口。本文将用 waynboot-mall 项目举例...【详细内容】
2024-02-19  Search: Spring  点击:(39)  评论:(0)  加入收藏
详解基于SpringBoot的WebSocket应用开发
在现代Web应用中,实时交互和数据推送的需求日益增长。WebSocket协议作为一种全双工通信协议,允许服务端与客户端之间建立持久性的连接,实现实时、双向的数据传输,极大地提升了用...【详细内容】
2024-01-30  Search: Spring  点击:(15)  评论:(0)  加入收藏
Spring实现Kafka重试Topic,真的太香了
概述Kafka的强大功能之一是每个分区都有一个Consumer的偏移值。该偏移值是消费者将读取的下一条消息的值。可以自动或手动增加该值。如果我们由于错误而无法处理消息并想重...【详细内容】
2024-01-26  Search: Spring  点击:(86)  评论:(0)  加入收藏
SpringBoot如何实现缓存预热?
缓存预热是指在 Spring Boot 项目启动时,预先将数据加载到缓存系统(如 Redis)中的一种机制。那么问题来了,在 Spring Boot 项目启动之后,在什么时候?在哪里可以将数据加载到缓存系...【详细内容】
2024-01-19  Search: Spring  点击:(86)  评论:(0)  加入收藏
Spring Boot2.0深度实践 核心原理拆解+源码分析
Spring Boot2.0深度实践:核心原理拆解与源码分析一、引言Spring Boot是一个基于Java的轻量级框架,它简化了Spring应用程序的创建过程,使得开发者能够快速搭建一个可运行的应用...【详细内容】
2024-01-15  Search: Spring  点击:(95)  评论:(0)  加入收藏
SpringBoot3+Vue3 开发高并发秒杀抢购系统
开发高并发秒杀抢购系统:使用SpringBoot3+Vue3的实践之旅随着互联网技术的发展,电商行业对秒杀抢购系统的需求越来越高。为了满足这种高并发、高流量的场景,我们决定使用Spring...【详细内容】
2024-01-14  Search: Spring  点击:(91)  评论:(0)  加入收藏
Spring Boot 3.0是什么?
Spring Boot 3.0是一款基于Java的开源框架,用于简化Spring应用程序的构建和开发过程。与之前的版本相比,Spring Boot 3.0在多个方面进行了改进和增强,使其更加易用、高效和灵活...【详细内容】
2024-01-11  Search: Spring  点击:(133)  评论:(0)  加入收藏
GraalVM与Spring Boot 3.0:加速应用性能的完美融合
在2023年,SpringBoot3.0的发布标志着Spring框架对GraalVM的全面支持,这一支持是对Spring技术栈的重要补充。GraalVM是一个高性能的多语言虚拟机,它提供了Ahead-of-Time(AOT)编...【详细内容】
2024-01-11  Search: Spring  点击:(124)  评论:(0)  加入收藏
Spring Boot虚拟线程的性能还不如Webflux?
早上看到一篇关于Spring Boot虚拟线程和Webflux性能对比的文章,觉得还不错。内容较长,抓重点给大家介绍一下这篇文章的核心内容,方便大家快速阅读。测试场景作者采用了一个尽可...【详细内容】
2024-01-10  Search: Spring  点击:(115)  评论:(0)  加入收藏
▌简易百科推荐
Web Components实践:如何搭建一个框架无关的AI组件库
一、让人又爱又恨的Web ComponentsWeb Components是一种用于构建可重用的Web元素的技术。它允许开发者创建自定义的HTML元素,这些元素可以在不同的Web应用程序中重复使用,并且...【详细内容】
2024-04-03  京东云开发者    Tags:Web Components   点击:(8)  评论:(0)  加入收藏
Kubernetes 集群 CPU 使用率只有 13% :这下大家该知道如何省钱了
作者 | THE STACK译者 | 刘雅梦策划 | Tina根据 CAST AI 对 4000 个 Kubernetes 集群的分析,Kubernetes 集群通常只使用 13% 的 CPU 和平均 20% 的内存,这表明存在严重的过度...【详细内容】
2024-03-08  InfoQ    Tags:Kubernetes   点击:(12)  评论:(0)  加入收藏
Spring Security:保障应用安全的利器
SpringSecurity作为一个功能强大的安全框架,为Java应用程序提供了全面的安全保障,包括认证、授权、防护和集成等方面。本文将介绍SpringSecurity在这些方面的特性和优势,以及它...【详细内容】
2024-02-27  风舞凋零叶    Tags:Spring Security   点击:(54)  评论:(0)  加入收藏
五大跨平台桌面应用开发框架:Electron、Tauri、Flutter等
一、什么是跨平台桌面应用开发框架跨平台桌面应用开发框架是一种工具或框架,它允许开发者使用一种统一的代码库或语言来创建能够在多个操作系统上运行的桌面应用程序。传统上...【详细内容】
2024-02-26  贝格前端工场    Tags:框架   点击:(47)  评论:(0)  加入收藏
Spring Security权限控制框架使用指南
在常用的后台管理系统中,通常都会有访问权限控制的需求,用于限制不同人员对于接口的访问能力,如果用户不具备指定的权限,则不能访问某些接口。本文将用 waynboot-mall 项目举例...【详细内容】
2024-02-19  程序员wayn  微信公众号  Tags:Spring   点击:(39)  评论:(0)  加入收藏
开发者的Kubernetes懒人指南
你可以将本文作为开发者快速了解 Kubernetes 的指南。从基础知识到更高级的主题,如 Helm Chart,以及所有这些如何影响你作为开发者。译自Kubernetes for Lazy Developers。作...【详细内容】
2024-02-01  云云众生s  微信公众号  Tags:Kubernetes   点击:(50)  评论:(0)  加入收藏
链世界:一种简单而有效的人类行为Agent模型强化学习框架
强化学习是一种机器学习的方法,它通过让智能体(Agent)与环境交互,从而学习如何选择最优的行动来最大化累积的奖励。强化学习在许多领域都有广泛的应用,例如游戏、机器人、自动驾...【详细内容】
2024-01-30  大噬元兽  微信公众号  Tags:框架   点击:(68)  评论:(0)  加入收藏
Spring实现Kafka重试Topic,真的太香了
概述Kafka的强大功能之一是每个分区都有一个Consumer的偏移值。该偏移值是消费者将读取的下一条消息的值。可以自动或手动增加该值。如果我们由于错误而无法处理消息并想重...【详细内容】
2024-01-26  HELLO程序员  微信公众号  Tags:Spring   点击:(86)  评论:(0)  加入收藏
SpringBoot如何实现缓存预热?
缓存预热是指在 Spring Boot 项目启动时,预先将数据加载到缓存系统(如 Redis)中的一种机制。那么问题来了,在 Spring Boot 项目启动之后,在什么时候?在哪里可以将数据加载到缓存系...【详细内容】
2024-01-19   Java中文社群  微信公众号  Tags:SpringBoot   点击:(86)  评论:(0)  加入收藏
花 15 分钟把 Express.js 搞明白,全栈没有那么难
Express 是老牌的 Node.js 框架,以简单和轻量著称,几行代码就可以启动一个 HTTP 服务器。市面上主流的 Node.js 框架,如 Egg.js、Nest.js 等都与 Express 息息相关。Express 框...【详细内容】
2024-01-16  程序员成功  微信公众号  Tags:Express.js   点击:(88)  评论:(0)  加入收藏
站内最新
站内热门
站内头条