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

Spring/SpringBoot中的声明式事务和编程式事务源码、区别、优缺点、适用场景、实战

时间:2023-04-28 14:20:53  来源:微信公众号  作者:小王博客基地

一、前言

在现代软件开发中,事务处理是必不可少的一部分。当多个操作需要作为一个整体来执行时,事务可以确保数据的完整性和一致性,并避免出现异常和错误情况。在SpringBoot框架中,我们可以使用声明式事务和编程式事务来管理事务处理。其中事务的坑也是不少,比较常见的就是事务失效,大家可以看看!后面小编在出一篇事务失效场景哈,喜欢的可以关注,等待更新哈!

这篇博客将重点探讨这两种事务处理方式的源码实现、区别、优缺点、适用场景以及实战。我们来接着说事务,里面还涉及到三个知识点,大家可以自行百度好好了解!

  • 事务的特性
  • 事务的传播行为
  • 隔离级别

本篇文章主要讲的就是实现事务的两种方式的分析!

让我们开始探索声明式事务和编程式事务吧!

文章很长,耐心看完希望对你有帮助!

本文源码是使用:springboot2.7.1。

二、开启使用和大致源码实现

1、开启使用

我们在启动类上添加注解:@EnableTransactionManagement。

后续使用就可以添加注解@Transactional(rollbackFor = Exception.class)使用,或者是使用编程式事务使用了 !
后面我们在详细演示怎么使用哈!

2、声明式事务源码

 
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);
}

3、编程式事务源码

编程式事务主要下面的代码:

public class TransactionTemplate extends DefaultTransactionDefinition
    implements TransactionOperations, InitializingBean{}

TransactionTemplate UML图:

图片

TransactionTemplate类的execute()方法封装了事务的具体实现,通过调用TransactionCallback对象的doInTransaction()方法来执行业务逻辑并管理事务。在具体实现中,TransactionTemplate类会自动控制事务的提交和回滚,并将异常抛出给上层调用者进行处理。

 
@Override
@Nullable
public <T> T execute(TransactionCallback<T> action) throws TransactionException {
    Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");

    if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
        return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
    }
    else {
        TransactionStatus status = this.transactionManager.getTransaction(this);
        T result;
        try {
            result = action.doInTransaction(status);
        }
        catch (RuntimeException | Error ex) {
            // Transactional code threw Application exception -> rollback
            rollbackOnException(status, ex);
            throw ex;
        }
        catch (Throwable ex) {
            // Transactional code threw unexpected exception -> rollback
            rollbackOnException(status, ex);
            throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
        }
        this.transactionManager.commit(status);
        return result;
    }
}

三、两者区别

上面说了源码里的大体实现,下面我们来介绍一下两者区别:

  1. 技术实现方式:声明式事务是通过AOP技术来实现的,而编程式事务是通过编写具体的代码来实现的。
  2. 代码耦合度:声明式事务可以将事务处理逻辑从业务代码中分离出来,从而降低代码的耦合度。而编程式事务需要在业务代码中显式地调用事务管理代码,因此会增加代码的耦合度。
  3. 难易程度:声明式事务相对来说比较容易上手,开发人员只需要学习注解或XML配置即可。而编程式事务需要开发人员理解事务管理的底层机制,并编写具体的代码。
  4. 性能影响:由于声明式事务是由容器来处理的,所以在一些场景下可能会对性能产生影响,大事务会有很多问题(下面在说一下大事务出现的问题)。而编程式事务由于直接调用事务管理API,相对来说会有更好的性能表现。

总体而言,声明式事务和编程式事务都有各自的优缺点,开发人员需要根据具体需求选择适合的方式来控制事务。

补充:

大事务时间过长可能会导致以下问题:

数据库锁定:当事务涉及到大量的数据操作时,事务可能会占用数据库资源并长时间锁定相关数据。这可能会导致其他事务无法访问或修改这些数据,从而降低系统的并发性能和吞吐量。

资源耗尽:长时间运行的事务需要占用更多的系统资源,如内存和CPU等。如果系统资源不足,可能会导致系统出现延迟、死锁等问题,甚至导致系统崩溃。

事务失败概率增加:当事务时间过长时,事务执行期间可能会发生各种错误,如网络故障、硬件故障、操作系统问题等。此时,事务可能无法成功提交,导致数据丢失或数据不一致。

应用程序超时:应用程序通常会为每个事务设置一个超时时间,以避免事务持续时间过长。如果事务持续时间超过设定的超时时间,则应用程序可能会因为等待事务完成而阻塞,最终导致应用程序崩溃或超时。

回滚时间增加:如果事务失败需要回滚,长时间运行的事务将需要更长的时间来进行回滚操作。这可能会导致数据不一致或丢失,并增加数据库维护的工作量。因此,开发人员应该尽量避免事务时间过长,合理地设置事务范围、优化事务操作方式以及减少数据访问次数等措施,以提高系统的并发性能和吞吐量。

方案:

大事务可以拆分小的事务,一下查询方面的可以提取出来,操作数据库的抽离出来专门加上事务。也可以使用CompletableFuture组合式异步编排来解决大事务的问题!

四、优缺点

1、声明式事务

声明式事务通常通过AOP技术实现,在方法或类级别上声明事务属性。声明式事务的优点包括:

简化代码:开发人员只需要关注业务逻辑,而无需手动管理事务,可以减少代码复杂度和工作量。

可配置性强:事务属性可以通过XML文件、注解等方式进行配置,灵活方便。
易于扩展:可以通过AOP技术轻松地扩展使其支持新的事务策略。

声明式事务存在以下缺点:

限制较大:事务属性需要在方法或类级别进行声明,这可能会导致某些情况下难以满足特定的业务需求。

难以调试:由于事务是在AOP层面进行管理的,因此在调试时可能难以追踪事务管理的具体细节。

2、编程式事务

编程式事务通常通过API接口实现,开发人员可以在代码中显式地管理事务。

编程式事务的优点包括:

灵活性强:开发人员可以在代码中根据具体业务需要来控制事务的具体范围和属性。
易于调试:由于事务管理在代码层面上实现,因此开发人员可以很容易地追踪事务管理的细节。

编程式事务存在以下缺点:

代码复杂度高:需要在代码中手动处理事务,并处理各种异常情况,可能会增加代码的复杂度和工作量。
可配置性差:事务的范围和属性需要在代码中显式声明,这可能会导致一些特定的业务需求难以满足。

总之,声明式事务和编程式事务各有优缺点。开发人员需要根据具体业务需求和场景选择使用合适的事务管理方式。

五、使用场景

声明式事务通常适用于以下场景:

  • 大型企业级应用程序,需要管理多个事务。
  • 代码结构比较复杂,使用声明式事务可以更好地管理和维护代码(大事务参考上方的方案)。
  • 声明式事务可以将事务管理与业务逻辑分离,从而使得应用程序更加松耦合。

而编程式事务通常适用于以下场景:

  • 需要更精确地控制事务的范围和处理逻辑。
  • 编程式事务通常比声明式事务更加灵活,可以根据业务逻辑的需要来自定义事务的范围、隔离级别以及回滚机制等。
  • 在某些高并发场景下,可以使用编程式事务仅针对需要操作的数据进行锁定,而不是对整个业务逻辑加事务。

在实际场景中,可以根据需求综合考虑使用声明式事务和编程式事务的优势来进行选择。

根据不同的用户量来具体选择,在几乎没有并发量的系统设计一条异步编排反而大材小用,可能造成资源的浪费;但是有需要等待远程API的响应时,使用异步编排可以将等待时间最小化,并使得应用程序不必阻塞等待API响应,从而提高用户体验。

很多事情没有绝对化,只有相对化,只要能支持现有正常的使用,不管什么样的设计都是没问题的! 可能好的设计会使系统在经受并发量增大的过程中无感,还是要调研清楚,从而设计出更好的方案,防止资源浪费!

尽管小编还没有什么架构经验,但还是对架构充满兴趣,不想做架构师的开发不是好开发哈!!当然你也可以走管理!!

六、实战

1、声明式事务

这里就简单模拟一下,为了模拟报错,把OperIp设置为唯一!

@Transactional(rollbackFor = Exception.class)大家经常使用,就不多演示了!

 
@Transactional(rollbackFor = Exception.class)
@Override
public void template() {
    SysLog sysLog = new SysLog();
    sysLog.setOperIp("123");
    SysLog sysLog1 = new SysLog();
    sysLog1.setOperIp("hhh");
    log.info("插入第一条数据开始========");
    testMapper.insert(sysLog);
    log.info("插入第一条数据完成========");
    log.info("插入第二条数据开始========");
    testMapper.insert(sysLog);
    log.info("插入第二条数据完成========");

}

此时数据没有数据,全部回滚成功!

图片

2、编程式事务

首先注入TransactionTemplate:

@Autowired
private TransactionTemplate transactionTemplate;

后面直接使用即可:

 
@Override
public void template() {
    SysLog sysLog = new SysLog();
    sysLog.setOperIp("123");
    SysLog sysLog1 = new SysLog();
    sysLog1.setOperIp("hhh");
    log.info("插入第一条数据开始========");
    testMapper.insert(sysLog);
    log.info("插入第一条数据完成========");

    transactionTemplate.execute(status -> {
        log.info("编程式事务中:插入第一条数据开始========");
        testMapper.insert(sysLog1);
        log.info("编程式事务中:插入第一条数据完成========");
        log.info("编程式事务中:插入第二条数据开始========");
        int insert = testMapper.insert(sysLog);
        log.info("编程式事务中:插入第二条数据完成========");
        return insert;
    });
}

图片

查看数据库,第一条不在编程式事务内不会参与回滚!

图片

七、总结

本文介绍了SpringBoot框架中的声明式事务和编程式事务,并分析了它们的源码实现、区别、优缺点、适用场景以及实战。
无论是采用哪种方式来管理事务,都需要考虑到业务需求和开发团队的实际情况,选择合适的事务处理方式,以确保系统的可靠性和稳定性。

希望通过本文的介绍,你能够更好地理解声明式事务和编程式事务的概念和原理,在开发过程中选择合适的事务处理方式,提高项目的可维护性和稳定性。



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  点击:(16)  评论:(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)  加入收藏
▌简易百科推荐
对于微服务架构监控应该遵守的原则
随着软件交付方式的变革,微服务架构的兴起使得软件开发变得更加快速和灵活。在这种情况下,监控系统成为了微服务控制系统的核心组成部分。随着软件的复杂性不断增加,了解系统的...【详细内容】
2024-04-03  步步运维步步坑    Tags:架构   点击:(5)  评论:(0)  加入收藏
大模型应用的 10 种架构模式
作者 | 曹洪伟在塑造新领域的过程中,我们往往依赖于一些经过实践验证的策略、方法和模式。这种观念对于软件工程领域的专业人士来说,已经司空见惯,设计模式已成为程序员们的重...【详细内容】
2024-03-27    InfoQ  Tags:架构模式   点击:(13)  评论:(0)  加入收藏
哈啰云原生架构落地实践
一、弹性伸缩技术实践1.全网容器化后一线研发的使用问题全网容器化后一线研发会面临一系列使用问题,包括时机、容量、效率和成本问题,弹性伸缩是云原生容器化后的必然技术选择...【详细内容】
2024-03-27  哈啰技术  微信公众号  Tags:架构   点击:(10)  评论:(0)  加入收藏
DDD 与 CQRS 才是黄金组合
在日常工作中,你是否也遇到过下面几种情况: 使用一个已有接口进行业务开发,上线后出现严重的性能问题,被老板当众质疑:“你为什么不使用缓存接口,这个接口全部走数据库,这怎么能扛...【详细内容】
2024-03-27  dbaplus社群    Tags:DDD   点击:(12)  评论:(0)  加入收藏
高并发架构设计(三大利器:缓存、限流和降级)
软件系统有三个追求:高性能、高并发、高可用,俗称三高。本篇讨论高并发,从高并发是什么到高并发应对的策略、缓存、限流、降级等。引言1.高并发背景互联网行业迅速发展,用户量剧...【详细内容】
2024-03-13    阿里云开发者  Tags:高并发   点击:(6)  评论:(0)  加入收藏
如何判断架构设计的优劣?
架构设计的基本准则是非常重要的,它们指导着我们如何构建可靠、可维护、可测试的系统。下面是这些准则的转换表达方式:简单即美(KISS):KISS原则的核心思想是保持简单。在设计系统...【详细内容】
2024-02-20  二进制跳动  微信公众号  Tags:架构设计   点击:(36)  评论:(0)  加入收藏
详解基于SpringBoot的WebSocket应用开发
在现代Web应用中,实时交互和数据推送的需求日益增长。WebSocket协议作为一种全双工通信协议,允许服务端与客户端之间建立持久性的连接,实现实时、双向的数据传输,极大地提升了用...【详细内容】
2024-01-30  ijunfu  今日头条  Tags:SpringBoot   点击:(16)  评论:(0)  加入收藏
PHP+Go 开发仿简书,实战高并发高可用微服务架构
来百度APP畅享高清图片//下栽のke:chaoxingit.com/2105/PHP和Go语言结合,可以开发出高效且稳定的仿简书应用。在实现高并发和高可用微服务架构时,我们可以采用一些关键技术。首...【详细内容】
2024-01-14  547蓝色星球    Tags:架构   点击:(115)  评论:(0)  加入收藏
GraalVM与Spring Boot 3.0:加速应用性能的完美融合
在2023年,SpringBoot3.0的发布标志着Spring框架对GraalVM的全面支持,这一支持是对Spring技术栈的重要补充。GraalVM是一个高性能的多语言虚拟机,它提供了Ahead-of-Time(AOT)编...【详细内容】
2024-01-11    王建立  Tags:Spring Boot   点击:(124)  评论:(0)  加入收藏
Spring Boot虚拟线程的性能还不如Webflux?
早上看到一篇关于Spring Boot虚拟线程和Webflux性能对比的文章,觉得还不错。内容较长,抓重点给大家介绍一下这篇文章的核心内容,方便大家快速阅读。测试场景作者采用了一个尽可...【详细内容】
2024-01-10  互联网架构小马哥    Tags:Spring Boot   点击:(115)  评论:(0)  加入收藏
站内最新
站内热门
站内头条