一文带你了解Spring中的事务管理
创始人
2024-02-21 11:07:57

文章目录

  • 前言
  • 一、事务的基础概念
  • 二、spring中事务的使用
    • 声明式事务
    • 编程式事务
    • 如何选择事务方式
  • 三、spring中事务管理实现原理


前言

本文将涉及以下知识点:

  • 事务的基础概念
  • spring当中事务的使用
  • spring当中事务管理的实现原理

一、事务的基础概念

事务(Transaction)是数据库软件中为了保证数据正确的一种手段。
事务必须满足4个特性:

  • 原子性:一个事务操作在软件系统中是不可拆分的,要么执行成功,要么执行失败。事务执行过程中有任何步骤失败,都应该回滚事务
  • 隔离性:事务与事务之间的执行是隔离开的,互相不影响。
  • 持久性:执行成功的事务,数据应该永久保存了,不会丢失。即使服务器系统崩溃或服务器宕机等故障。只要数据库重新启动,那么一定能够将其恢复到事务成功结束后的状态。
  • 一致性:事务的一致性是指事务在执行不能破坏数据库数据的完整性和一致性,一个事务在执行之前和执行之后,数据库都必须处以一致性状态。比如:张三给李四转钱,不可能张三被扣了钱,李四没有加钱。

二、spring中事务的使用

首先需要明确一点,spring只是帮助我们管理事务操作,省去我们自己去手动执行相应的事务操作sql。真正的事务实现是由数据库提供的。

spring提供了声明式和编程式两种管理事务的方式。两种方式的使用都非常简单。

声明式事务

spring在使用声明式事务前需要先开启事务管理器,否则事务不会生效。在springboot中开启事务配置是用@EnableTransactionManagement注解表示开启。
在这里插入图片描述

声明式事务是在你的逻辑调用入口添加一个 @Transactional注解,以AOP切面的形式帮助我们开启、提交、回滚事务。
在这里插入图片描述
声明式事务最大的优点就是使用简单、基本上对业务逻辑代码无侵入。开发者只需要关注业务逻辑,而不必关心事务的处理。缺点则是在使用@Transactional注解时,有一些会导致事务失效的点,需要特别注意
在这里插入图片描述
事务失效场景参考


编程式事务

编程式事务通常是由TransactionTemplate类来操作的,这个类是spring官方提供的操作事务的模板类,类路径为:

org.springframework.transaction.support.TransactionTemplate

TransactionTemplate通过执行execute(params)方法,接收一个TransactionCallback类型的参数。实现的事务管理。使用示例如下:
在这里插入图片描述
使用编程式事务需要注意,所有的sql操作需要写在doInTransaction()方法体内,否则你的sql执行不会被纳入到事务操作,将不具体事务性。

如何选择事务方式

首先,声明式事务、编程式事务二者在底层实现上是一样的。唯一不同的只有使用方面。具体两种方式如何选择的话。

建议业务逻辑复杂情况使用编程式事务,它可以更好的把控业务处理时事务的粗细粒度。如果业务不是很复杂的场景,可以使用声明式事务处理。使用的时候注意一下上面提到的会导致事务失效的点。还有一个点,spring中事务是跟线程绑定的,事务只能控制当前线程的操作。


三、spring中事务管理实现原理

从源码层次带大家看一下spring是怎么帮助我们管理事务的。

从编程式事务的角度出发来看原理的话,非常方便。直接阅读TransactionTemplate模板类的源码即可。

 @Nullablepublic  T execute(TransactionCallback 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);Object result;try {//执行业务逻辑,上面说到的sql语句都要放到doInTransaction()方法体内执行,就是在这里调用了result = action.doInTransaction(status);} catch (Error | RuntimeException var5) {//如果业务逻辑执行有异常,捕获到了直接回滚事务this.rollbackOnException(status, var5);throw var5;} catch (Throwable var6) {//同上this.rollbackOnException(status, var6);throw new UndeclaredThrowableException(var6, "TransactionCallback threw undeclared checked exception");}//业务执行完成,commit提交事务this.transactionManager.commit(status);return result;}}

上面TransactionTemplate.execute()方法中完整了罗列了事务管理的整个周期,开启、提交/回滚 。

下面从声明式事务角度来分析一下源码实现,由于声明式事务在使用上只有一个 @Transactional注解,所以需要找到事务aop的切面逻辑才能看到底层实现。由于声明式事务需要开启@EnableTransactionManagement,所以我们从@EnableTransactionManagement注解开始分析。
在这里插入图片描述
通过import导入配置类,进来找到这个类:ProxyTransactionManagementConfiguration
在这里插入图片描述

切入点在AnnotationTransactionAttributeSource类里
在这里插入图片描述
添加解析器,用来匹配查询@Transactional注解
在这里插入图片描述
AnnotationTransactionAttributeSource.isCandidateClass()方法会匹配查找@Transactional切入点。
在这里插入图片描述

具体的切入匹配是在SpringTransactionAnnotationParser.isCandidateClass()方法下面
在这里插入图片描述
成功匹配到@Transactional注解。接下来就是具体的切面逻辑执行。

切面逻辑在TransactionInterceptor类里(为什么切面逻辑在TransactionInterceptor类里,看下spring aop 内部实现)
在这里插入图片描述
进入TransactionInterceptor.invoke()方法。
在这里插入图片描述
为什么看invoke方法?实现了MethodInterceptor接口、看下TransactionInterceptor类的继承关系。
在这里插入图片描述
继续跟invoke方法,来到TransactionAspectSupport类下的invokeWithinTransaction方法

 @Nullableprotected Object invokeWithinTransaction(Method method, @Nullable Class targetClass, TransactionAspectSupport.InvocationCallback invocation) throws Throwable {TransactionAttributeSource tas = this.getTransactionAttributeSource();TransactionAttribute txAttr = tas != null ? tas.getTransactionAttribute(method, targetClass) : null;TransactionManager tm = this.determineTransactionManager(txAttr);if (this.reactiveAdapterRegistry != null && tm instanceof ReactiveTransactionManager) {TransactionAspectSupport.ReactiveTransactionSupport txSupport = (TransactionAspectSupport.ReactiveTransactionSupport)this.transactionSupportCache.computeIfAbsent(method, (key) -> {if (KotlinDetector.isKotlinType(method.getDeclaringClass()) && TransactionAspectSupport.KotlinDelegate.isSuspend(method)) {throw new TransactionUsageException("Unsupported annotated transaction on suspending function detected: " + method + ". Use TransactionalOperator.transactional extensions instead.");} else {ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(method.getReturnType());if (adapter == null) {throw new IllegalStateException("Cannot apply reactive transaction to non-reactive return type: " + method.getReturnType());} else {return new TransactionAspectSupport.ReactiveTransactionSupport(adapter);}}});return txSupport.invokeWithinTransaction(method, targetClass, invocation, txAttr, (ReactiveTransactionManager)tm);} else {PlatformTransactionManager ptm = this.asPlatformTransactionManager(tm);String joinpointIdentification = this.methodIdentification(method, targetClass, txAttr);if (txAttr != null && ptm instanceof CallbackPreferringPlatformTransactionManager) {TransactionAspectSupport.ThrowableHolder throwableHolder = new TransactionAspectSupport.ThrowableHolder();Object result;try {result = ((CallbackPreferringPlatformTransactionManager)ptm).execute(txAttr, (statusx) -> {TransactionAspectSupport.TransactionInfo txInfo = this.prepareTransactionInfo(ptm, txAttr, joinpointIdentification, statusx);Object var9;try {Object retVal = invocation.proceedWithInvocation();if (retVal != null && vavrPresent && TransactionAspectSupport.VavrDelegate.isVavrTry(retVal)) {retVal = TransactionAspectSupport.VavrDelegate.evaluateTryFailure(retVal, txAttr, statusx);}var9 = retVal;return var9;} catch (Throwable var13) {if (txAttr.rollbackOn(var13)) {if (var13 instanceof RuntimeException) {throw (RuntimeException)var13;}throw new TransactionAspectSupport.ThrowableHolderException(var13);}throwableHolder.throwable = var13;var9 = null;} finally {this.cleanupTransactionInfo(txInfo);}return var9;});} catch (TransactionAspectSupport.ThrowableHolderException var20) {throw var20.getCause();} catch (TransactionSystemException var21) {if (throwableHolder.throwable != null) {this.logger.error("Application exception overridden by commit exception", throwableHolder.throwable);var21.initApplicationException(throwableHolder.throwable);}throw var21;} catch (Throwable var22) {if (throwableHolder.throwable != null) {this.logger.error("Application exception overridden by commit exception", throwableHolder.throwable);}throw var22;}if (throwableHolder.throwable != null) {throw throwableHolder.throwable;} else {return result;}} else {//主要逻辑在这,默认走的是这段代码块,开启事务TransactionAspectSupport.TransactionInfo txInfo = this.createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);Object retVal;try {//执行业务逻辑retVal = invocation.proceedWithInvocation();} catch (Throwable var18) {//异常回滚事务this.completeTransactionAfterThrowing(txInfo, var18);throw var18;} finally {this.cleanupTransactionInfo(txInfo);}if (retVal != null && vavrPresent && TransactionAspectSupport.VavrDelegate.isVavrTry(retVal)) {TransactionStatus status = txInfo.getTransactionStatus();if (status != null && txAttr != null) {retVal = TransactionAspectSupport.VavrDelegate.evaluateTryFailure(retVal, txAttr, status);}}//业务执行完成,commit提交事务this.commitTransactionAfterReturning(txInfo);return retVal;}}}

到大致讲一下:spring在帮我们开启事务的时候是通过设置 自动提交autocommit=0 开启一条事务,提交/回滚的时候都是通过JDBC DataSource获取数据库连接,执行commit/rollback 命令完成的。大家可以按照上面代码标注的注释点一层一层点进去查看。

底层跟踪下去,声明式以及编程式事务都是由AbstractPlatformTransactionManager默认的事务管理器处理。

开启事务处理链路:AbstractPlatformTransactionManager.getTransaction() => AbstractPlatformTransactionManager.startTransaction() => AbstractPlatformTransactionManager.doBegin() => DataSourceTransactionManager.doBegin()
在这里插入图片描述

提交事务处理链路:AbstractPlatformTransactionManager.doCommit() => DataSourceTransactionManager.doCommit()

 protected void doCommit(DefaultTransactionStatus status) {DataSourceTransactionManager.DataSourceTransactionObject txObject = (DataSourceTransactionManager.DataSourceTransactionObject)status.getTransaction();//获取数据库连接Connection con = txObject.getConnectionHolder().getConnection();if (status.isDebug()) {this.logger.debug("Committing JDBC transaction on Connection [" + con + "]");}try {//根据具体数据库驱动实现,执行不同提交事务语法con.commit();} catch (SQLException var5) {throw new TransactionSystemException("Could not commit JDBC transaction", var5);}}

以mysql举例,底层会执行commit命令:con.commit() => com.mysql.cj.jdbc.ConnectionImpl.commit()
在这里插入图片描述
回滚事务同理:AbstractPlatformTransactionManager.doCommit() => DataSourceTransactionManager.rollback()
在这里插入图片描述

在mysql驱动下的实现:
con.rollback() => com.mysql.cj.jdbc.ConnectionImpl.rollback()
com.mysql.cj.jdbc.ConnectionImpl.rollback() => com.mysql.cj.jdbc.ConnectionImpl.rollbackNoChecks()
在这里插入图片描述
在这里插入图片描述

相关内容

热门资讯

【看表情包学Linux】进程地...   🤣 爆笑教程 👉 《看表情包学Linux》👈 猛...
吉字五行及吉凶 吉字五行中代表... 五行解析在文化中,五行是非常重要的概念之一,在这里解析一下五行对于人们生活的影响。首先,金属代表的是...
月老姻缘灵签内容详解大全 月老... 月老灵签 姻缘签44签 求解签君尔目下之人。本是可心满意足之人。焉知后来之人。一个比一个更美好。就此...
六爻排盘蛇 六爻排盘预测绝招 ... 六爻排盘结果怎么看纳甲六爻在线排盘姓名:出生年:1981性别:男占事:起卦方式:手动摇卦公历时间:2...
吉凶由情绪决定 每日吉凶 每月... 情绪的力量情绪是我们生活中一个重要的组成部分。我们每天都会通过不同的方式感受到情绪的存在,而情绪的质...
吉凶悔吝的解释是什么 风水形势... 吉凶悔吝的解释是什么从古至今,人们对于吉凶悔吝都有着不同的看法。所谓吉,是指好的运气,让人们沾沾自喜...
如何看每日生肖运势 每日生肖运... 背景说明每个人都希望自己的运势越来越好,而对于人来说,生肖运势是一个参考价值很高的判断标准。按照传统...
最准观音灵签21签解签 观音灵... 观音灵签21签解签-遵医嘱,健康长寿观音精神签证是中国民间宗教信仰的重要形式,也是一种广泛流传的祈祷...
梦见红裤子被水冲走 梦见河里洗... 梦见红裤子被水冲走红裤子是一种比较鲜艳的颜色,在梦中出现可能代表着某种情绪或状态。而被水冲走则更加具...
十二星座对象配对 12星座最佳... 12星座配偶标准白羊座:温柔善良的人乐观单纯的白羊座在恋爱时喜欢另一半无条件的宠爱自己,另一半对自己...
带水又带土的名字女孩名字有哪些... 含水和土的字有哪些含水的字:淦、澜、浸、泼、滴、没、汪、沸、鸿、沔、浩、渣、溢、潺江、注、漭、淬、澧...
六爻失物卦 在线占卜失物 六爻... 六爻占卜 寻找失物公历起卦时间:2012年12月24日9时44分(按公历时间起卦)农历:仁辰年十一月...
天网今日生肖运势 每日特吉生肖... 天网今日生肖运势天网今日,十二生肖依旧是重要的关键词之一。根据传统文化和民间信仰,每个人都属于一个生...
六爻代表书籍 六爻预测好的书籍... 学六爻的书籍那些比较经典,最好适合初学者的。从古至今六爻类的书流传于世的非常少,六爻类最经典的几本书...
十二星座女生专属花卉 小葩画1... 狮子座的女生喜欢什么样的花1.狮子座的女生喜欢鲜艳、华丽、高贵的花。2.狮子座的女生通常有着自信、热...
客厅风水禁忌及化解 客厅推拉门... 客厅风水禁忌及解决方案客厅是家庭中最重要的空间之一,也是最容易受到风水影响的空间之一。在客厅里,我们...
吉凶参半牛兔在含义 牛兔相冲到... 吉凶参半牛兔在啥意思吉凶参半牛兔在是指属牛的和属兔的结婚以后生活吉凶各一半。丑牛与子鼠六合,因此最宜...
各生肖属相的车牌号码吉凶对照表... 十二生肖与车牌号的佳搭配 十二生肖车牌号吉凶对照表通常每个人的黄道十二宫都会影响车牌号码的运行模式,...
客厅西部尖角的风水 客厅有棱角... 客厅西南角最好的风水是什么?客厅西南角最好的风水是什么?客厅西南角最好的风水是什么?房子的方形风水是...
带昶字的女孩名字 带滢的女孩名... 长字命名的寓意及意义长字命名的寓意和意义是正直、坚强、努力、阳光、前途似景、忠诚。长是一个通用词。长...
六壬怎么算命 六壬掐指神算金口... 什么是六仁?刘仁是中国古代的算命方法,起源于汉代,是中国道教学派的经典之一。刘仁包括六个神:天乙、天...
四月二十九生肖运势 十二生肖鸡... 女1993农历四月二十九早上十点生辰八字是什么如何出生时间:公历 1993年 6月 18日 10点本...
带心字的游戏女孩名字大全集 游... 2020男孩怎么起名有内涵 带心字的男孩名字大全心繁体:心起名五行:金姓名学笔画:4画简体笔画:4画...
八字鬼谷子算命 鬼谷子精髓50... 什么是八字鬼谷子算命?八字鬼谷子算命,又称李静算命,是中国传统的民间算命方式。八字鬼谷子算命起源于六...
十二星座下个月的运势女生 十二... 白羊座下个月的运势女生白羊座女生本身就充满了无限活力和热情,下个月的运势也不会让你失望。职业上可能会...
号令天下手机吉凶预测 号令天下... 手机号怎么算吉凶?用最后四个手机号码除以80,然后减去整数部分(只留小数),再乘以80,就会得到一个...
命理十二生肖今年运势 明天运势... 命理十二生肖今年运势今年每年都有不同的转瞬即逝的岁月。对于不同的黄道十二宫来说,它每年都有自己独特的...
八字长生好吗 八字中帝旺到长生... 八字日坐长生一定富吗丁火曰元生于未月,余气通根,年支丙火也能助身,但于上两透旺食,生财耗身过甚,故命...
带日的名字女孩名字大全 起名带... 日字旁边的女孩名字大全日字旁边的女孩名字推荐1、诗晗、慧曦、Xi、仲晴2、小芸、小娟、会晴、若昕、敏...
号令天下固话号码测吉凶 查电话... 周易81测手机号码吉凶,号令天下手机号码测吉凶提起周易81测手机号码吉凶,大家都知道,有人问天下手机...