Spring/SpringBoot中的声明式事务和编程式事务源码、区别、优缺点、适用场景、实战
一、前言
在现代软件开发中,事务处理是必不可少的一部分。当多个操作需要作为一个整体来执行时,事务可以确保数据的完整性和一致性,并避免出现异常和错误情况。在SpringBoot框架中,我们可以使用声明式事务和编程式事务来管理事务处理。其中事务的坑也是不少,比较常见的就是事务失效,大家可以看看!后面小编在出一篇事务失效场景哈,喜欢的可以关注,等待更新哈!
这篇博客将重点探讨这两种事务处理方式的源码实现、区别、优缺点、适用场景以及实战。
我们来接着说事务,里面还涉及到三个知识点,大家可以自行百度好好了解!
- 事务的特性
- 事务的传播行为
- 隔离级别
本篇文章主要讲的就是实现事务的两种方式的分析!
让我们开始探索声明式事务和编程式事务吧!
文章很长,耐心看完希望对你有帮助!
本文源码是使用:springboot2.7.1
二、开启使用和大致源码实现
1. 开启使用
我们在启动类上添加注解:@EnableTransactionManagement
后续使用就可以添加注解@Transactional(rollbackFor = Exception.class)使用,或者是使用编程式事务使用了 !
后面我们在详细演示怎么使用哈!
2. 声明式事务源码
public class TransactionInterceptor extends TransactionAspectSupport
implements MethodInterceptor, Serializable{}
TransactionInterceptor UML图:

声明式事务主要是通过AOP实现,主要包括以下几个节点:
启动时扫描
@Transactional注解:在启动时,Spring Boot会扫描所有使用了@Transactional注解的方法,并将其封装成TransactionAnnotationParser对象。AOP 来实现事务管理的核心类依然是
TransactionInterceptor。TransactionInterceptor 是一个拦截器,用于拦截使用了 @Transactional 注解的方法将TransactionInterceptor织入到目标方法中:在AOP编程中,使用
AspectJ编写切面类,通过@Around注解将TransactionInterceptor织入到目标方法中。在目标方法执行前创建事务:在目标方法执行前,TransactionInterceptor会调用
PlatformTransactionManager创建一个新的事务,并将其纳入到当前线程的事务上下文中。执行目标方法:在目标方法执行时,如果发生异常,则将事务状态标记为
ROLLBACK_ONLY;否则,将事务状态标记为COMMIT。提交或回滚事务:在目标方法执行完成后,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 = determineTransactionManager(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;
}
}
三、两者区别
上面说了源码里的大体实现,下面我们来介绍一下两者区别:
- 技术实现方式:声明式事务是通过AOP技术来实现的,而编程式事务是通过编写具体的代码来实现的。
- 代码耦合度:声明式事务可以将事务处理逻辑从业务代码中分离出来,从而降低代码的耦合度。而编程式事务需要在业务代码中显式地调用事务管理代码,因此会增加代码的耦合度。
- 难易程度:声明式事务相对来说比较容易上手,开发人员只需要学习注解或XML配置即可。而编程式事务需要开发人员理解事务管理的底层机制,并编写具体的代码。
- 性能影响:由于声明式事务是由容器来处理的,所以在一些场景下可能会对性能产生影响,大事务会有很多问题(下面在说一下大事务出现的问题)。而编程式事务由于直接调用事务管理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框架中的声明式事务和编程式事务,并分析了它们的源码实现、区别、优缺点、适用场景以及实战。
无论是采用哪种方式来管理事务,都需要考虑到业务需求和开发团队的实际情况,选择合适的事务处理方式,以确保系统的可靠性和稳定性。
希望通过本文的介绍,你能够更好地理解声明式事务和编程式事务的概念和原理,在开发过程中选择合适的事务处理方式,提高项目的可维护性和稳定性。
如果对你有帮助,还请动一下您的发财小手,关注一下公众号哈!!谢谢您的关注!!文章首发看!!!
建了一个IT交流群,欢迎大家加入,过期加我拉你们进哈!

Spring/SpringBoot中的声明式事务和编程式事务源码、区别、优缺点、适用场景、实战的更多相关文章
- spring boot中的声明式事务管理及编程式事务管理
这几天在做一个功能,具体的情况是这样的: 项目中原有的几个功能模块中有数据上报的功能,现在需要在这几个功能模块的上报之后生成一条消息记录,然后入库,在写个接口供前台来拉取消息记录. 看到这个需求,首先 ...
- vue中声明式导航和编程式导航
官方文档:https://router.vuejs.org/zh-cn/essentials/navigation.html 声明式导航和编程式导航 共同点: 都能进行导航,都可以触发路由,实现组件切 ...
- [vue]声明式导航和编程式导航
声明式导航和编程式导航 共同点: 都能进行导航,都可以触发路由,实现组件切换 区别: 写法不一样,声明式导航是写在组件的template中,通过router-link来触发,编程式导航写在js函数中, ...
- spring事务管理——编程式事务、声明式事务
本教程将深入讲解 Spring 简单而强大的事务管理功能,包括编程式事务和声明式事务.通过对本教程的学习,您将能够理解 Spring 事务管理的本质,并灵活运用之. 先决条件 本教程假定您已经掌握了 ...
- spring的声明式事务和编程式事务
事务管理对于企业应用来说是至关重要的,当出现异常情况时,它可以保证数据的一致性. Spring事务管理的两种方式 1.编程式事务 使用Transaction Ttempleate或者直接使用底层的Pl ...
- Spring 声明式事务与编程式事务详解
本文转载自IBM开发者论坛:https://developer.ibm.com/zh/articles/os-cn-spring-trans 根据自己的学习理解有所调整,用于学习备查. 事务管理对于企 ...
- Spring中声明式事务处理和编程式事务处理的区别
编程式事务:所谓编程式事务指的是通过编码方式实现事务,即类似于JDBC编程实现事务管理.管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManag ...
- 用Java+xml配置方式实现Spring数据事务(编程式事务)
一.用Java配置的方式 1.实体类: Role public class Role { private int id; private String roleName; private String ...
- Spring事务管理实现方式之编程式事务与声明式事务详解(转)
原文:https://blog.csdn.net/liaohaojian/article/details/70139151 编程式事务 编码方式实现事务管理(代码演示为JDBC事务管理) Spring ...
- Spring笔记(4) - Spring的编程式事务和声明式事务详解
一.背景 事务管理对于企业应用而言至关重要.它保证了用户的每一次操作都是可靠的,即便出现了异常的访问情况,也不至于破坏后台数据的完整性.就像银行的自助取款机,通常都能正常为客户服务,但是也难免遇到操作 ...
随机推荐
- 2023年icpc大学生程序设计竞赛-crf
第一次在除郑轻以外的校外的地方比赛,也是第一次出市比赛,赛程也比较长.20号出发的时候遇到一些意外,不过无伤大雅,第一天热身赛平平无奇,晚上的时候补了一下前年icpc的题,一个多小时做了五题,很是自信 ...
- netstat 某连接的 Recv-Q(接收队列)达到500多万字节的内核参数排查
思路: cat proc文件系统下的 sys/net 目录下所有文件,根据结果降序排序(如果打印前xx,可能会漏掉关键信息,在定位问题时需要注意,慎用过滤),根据结果使用 grep -rn xxx 找 ...
- 你不知道的 HTTP Referer
前言 上周突然发现自己的自己站点的图片全都403了,之前还是好好的,图片咋就全都访问不了呢?由于我每次发文章都是先发了掘金,然后再从掘金拷贝到我自己的站点,这样我就不用在自己的站点去上传图片了,非常方 ...
- 【go语言】2.2.1 数组和切片
数组和切片是 Go 语言中常用的数据结构,它们都可以存储多个同类型的元素. 数组 数组是具有固定长度的数据类型,它的长度在定义时就已经确定,不能随意改变. 你可以使用以下方式定义一个数组: var a ...
- 磁盘问题和解决: fsck,gdisk,fdisk等
错误: Resize inode not valid 对于gpt分区的硬盘一般fsck只能检查分区, 不能用于检查整个硬盘, 但是如果对硬盘设备运行时遇到这样的错误 $ sudo fsck -n /d ...
- pyinstaller 安装报错,环境是python3.7
在pycharm中安装,和直接输入pip install pyinstaller 均报错, 最后,输入pip install -i https://pypi.douban.com/simple/ py ...
- MQ消息队列篇:三大MQ产品的必备面试种子题
MQ有什么用? MQ(消息队列)是一种FIFO(先进先出)的数据结构,主要用于实现异步通信.削峰平谷和解耦等功能.它通过将生产者生成的消息发送到队列中,然后由消费者进行消费.这样,生产者和消费者之间就 ...
- 用 Python 自动创建 Markdown 表格 - 每天5分钟玩转 GPT 编程系列(4)
目录 1. 他们居然问我要 Prompts 2. 让 GPT-4 来写代码 2.1 我对 DevChat 说 2.2 DevChat 回答 2.3 我又对 DevChat 说 2.4 DevChat ...
- nginx搭建静态文件下载服务器
配置文件大致内容 server { # 监听8001端口 listen 8001; server_name 192.168.0.2; # 指定使用utf8的编码 charset utf-8; # 内容 ...
- 一文详述流媒体传输网络MediaUni
一张「多元融合」的网络. 黄海宇|演讲者 大家好,我是阿里云视频云的黄海宇,今天分享主题是MediaUni--面向未来的流媒体传输网络设计与实践. 下面我将会从应用对流媒体传输网络的要求.MediaU ...