spring更新后 外层事务查不到_再深一点:面试工作两不误,源码级理解Spring事务...

c1fe981d6cc967384fd2c8cd4560f072.gif

原创:小姐姐味道(微信公众号ID:xjjdog),欢迎分享,转载请保留出处。

Spring有5种隔离级别,7种传播行为。这是面试常问的内容,也是代码中经常碰到的知识点。这些知识枯燥而且乏味,其中有些非常的绕。如果栽在这上面,就实在是太可惜了。

edca1aab67e7937b1c9953628e4efcbe.png

xjjdog在一些事务的基础上,再探讨几个容易淡忘的概念,从源码层面找原因,加深我们的理解,问题大概包括:

  1. Spring的事务和数据库的事务隔离是一个概念么?

  2. Spring是如何实现事务的?

  3. 事务隔离机制都有哪些?

  4. 事务传播机制都有哪些?

  5. 查询语句需要开事务么?

  6. private方法加事务注解有用么?

1、Spring的事务和数据库的事务隔离是一个概念么?

先来第一个问题,Spring的事务隔离级别和数据的事务隔离级别,是一回事么?

其实,数据库一般只有4种隔离机制,Spring抽象出一种default,根据数据设置来变动。

4928baeebedc50a92a29b0282961b563.png

  • read uncommitted(未提交读)

  • read committed(提交读、不可重复读)

  • repeatable read(可重复读)

  • serializable(可串行化)

  • default (PlatformTransactionManager默认的隔离级别,使用的就是数据库默认的)

这是因为,Spring只提供统一事务管理接口,具体实现都是由各数据库自己实现(如MySQL)。Spring会在事务开始时,根据当前环境中设置的隔离级别,调整数据库隔离级别,由此保持一致。

DataSourceUtils文件中,代码详细的输出了这个过程。

// Apply specific isolation level, if any.
Integer previousIsolationLevel = null;
if (definition != null && definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
if (logger.isDebugEnabled()) {
logger.debug("Changing isolation level of JDBC Connection [" + con + "] to " +
definition.getIsolationLevel());
}
int currentIsolation = con.getTransactionIsolation();
if (currentIsolation != definition.getIsolationLevel()) {
previousIsolationLevel = currentIsolation;
con.setTransactionIsolation(definition.getIsolationLevel());
}
}

结论:三种情况,如果Spring没有指定事务隔离级别,则会采用数据库默认的事务隔离级别;当Spring指定了事务隔离级别,则会在代码里将事务隔离级别修改为指定值;当数据库不支持这种隔离级别,效果则以数据库的为准(比如采用了MyISAM引擎)。

我们会使用如下的方式进行声明。如果不是有性能和需求问题,就不要瞎改。事务处理弄不好是会锁表的,而锁表在大并发的情况下是会死人的。

@Transactional(isolation = Isolation.READ_UNCOMMITTED)

2、Spring事务的7种传播机制

只要写代码,代码总会存在嵌套,或者循环,造成了事务的嵌套或者循环。那么事务在这些情况下,根据配置会有不同的反应。

aede33d29a9f9a071c36adfefe875147.png

  • REQUIRED 这是默认的。表示当前方法必须在一个具有事务的上下文中运行,如有客户端有事务在进行,那么被调用端将在该事务中运行,否则的话重新开启一个事务。(如果被调用端发生异常,那么调用端和被调用端事务都将回滚)

  • REQUIRE_NEW 表示当前方法必须运行在它自己的事务中。如果存在当前事务,在该方法执行期间,当前事务会被挂起

  • NESTED  如果当前方法正有一个事务在运行中,则该方法应该运行在一个嵌套事务中,被嵌套的事务可以独立于被封装的事务中进行提交或者回滚。如果封装事务存在,并且外层事务抛出异常回滚,那么内层事务必须回滚,反之,内层事务并不影响外层事务。如果封装事务不存在,则同required的一样

  • SUPPORTS 表示当前方法不必需要具有一个事务上下文,但是如果有一个事务的话,它也可以在这个事务中运行

  • NOT_SUPPORTED 表示该方法不应该在一个事务中运行。如果有一个事务正在运行,他将在运行期被挂起,直到这个事务提交或者回滚才恢复执行

  • MANDATORY 表示当前方法必须在一个事务中运行,如果没有事务,将抛出异常

  • NEVER 表示当方法务不应该在一个事务中运行,如果存在一个事务,则抛出异常

一般用得比较多的是REQUIREDREQUIRES_NEW,用到其他的,你就要小心了,搞懂再用。

最怕如果这俩字了,它会将事情搞的很复杂,尤其是代码量大的时候,你永远不知道你写的service会被谁用到。这就很尴尬了。

我们会采用下面的方式进行声明。鉴于Spring的事务传播非常的绕,如果功能满足需求,那么就用默认的就好,否则会引起不必要的麻烦。

@Transactional(propagation=Propagation.REQUIRED)

3、事务传播机制是怎么实现的?

事务传播机制看似神奇,实际上是使用简单的ThreadLocal的机制实现的。所以,如果调用的方法是在新线程调用的,事务传播实际上是会失效的。这不同于我们以前讲到的透传,Spring并没有做这样的处理。

参考:你的也是我的。3例ko多线程,局部变量透传

所以事务传播机制,只有翻一遍源代码,才能印象深刻。仅靠文字去传播,很多东西会变得不可描述。

f30549a4c91c172b41966c25d1a16552.png


如图,PlatformTransactionManager只有简单的三个抽象接口,定义了包含JDBC在内的Spring所有的事务操作。

28cf08fff0ca45411eda594b659cdc07.png

我们平常说的JDBC,只是占到其中一部分。

实现的方式,依然是使用AOP来实现的,具体的实现类是由TransactionAspectSupport来实现的。可以看到,代码定义了一个叫做transactionInfoHolder的ThreadLocal变量,当用到它的时候,就能够确保在同一个线程下,获取的变量是一致的。

/**
* Holder to support the {@code currentTransactionStatus()} method,
* and to support communication between different cooperating advices
* (e.g. before and after advice) if the aspect involves more than a
* single method (as will be the case for around advice).
*/
private static final ThreadLocal transactionInfoHolder =new NamedThreadLocal<>("Current aspect-driven transaction");

具体的业务逻辑,是在invokeWithinTransaction中实现的。如果你继续向下跟踪的话,会找到AbstractPlatformTransactionManager类中的getTransaction方法。

@Override
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)throws TransactionException {


// Use defaults if no transaction definition given.
TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());


Object transaction = doGetTransaction();
boolean debugEnabled = logger.isDebugEnabled();


if (isExistingTransaction(transaction)) {
// Existing transaction found -> check propagation behavior to find out how to behave.
return handleExistingTransaction(def, transaction, debugEnabled);
}


// Check definition settings for new transaction.
if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout());
}


// No existing transaction found -> check propagation behavior to find out how to proceed.
if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
throw new IllegalTransactionStateException(
"No existing transaction found for transaction marked with propagation 'mandatory'");
}
else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
SuspendedResourcesHolder suspendedResources = suspend(null);
if (debugEnabled) {
logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);
}
try {
return startTransaction(def, transaction, debugEnabled, suspendedResources);
}
catch (RuntimeException | Error ex) {
resume(null, suspendedResources);
throw ex;
}
}
else {
// Create "empty" transaction: no actual transaction, but potentially synchronization.
if (def.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
logger.warn("Custom isolation level specified but no actual transaction initiated; " +
"isolation level will effectively be ignored: " + def);
}
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);
}
}

不用我做过多解释了吧,一切明显的逻辑,都在代码里。事务就是在这里创建的。

4、查询方法可以不开启事务么?

事务有个readonly,控制了事务的只读属性,和事务是否开启没半毛钱关系。

在以前的一篇文章中,谈到通过设置readonly属性来控制语句的路由:”MySQL官方驱动“主从分离的神秘面纱(扫盲篇),这其中就用到了事务的其中一个属性readonly,它最终是体现在数据库连接层面的。

connection.setReadOnly(true);

在Spring中的使用方式如下:

@Transactional(readOnly=true)

值得注意的是,这个属性设置之后,并不是每个底层的数据库都支持。中间层的ORM或者驱动,也可能会拿这个属性做一些文章,所以与其说这个readonly是功能性的,不如说是一种暗示

拿MySQL来说,有两种提交模式:

  • SET AUTOCOMMIT=0 禁止自动提交

  • SET AUTOCOMMIT=1 开启自动提交

这都是实打实的SQL语句,所以如果开启了事务,AUTOCOMMIT要为false。我们可以看到Spring做了以下几个操作。

con.setAutoCommit(false);

如果是只读事务,还不忘手动设置一下。

if (isEnforceReadOnly() && definition.isReadOnly()) {    try (Statement stmt = con.createStatement()) {
stmt.executeUpdate("SET TRANSACTION READ ONLY");
}
}

这种操作是很昂贵的,如果不加Transaction注解,默认是不开启事务的。单条的查询语句也是没有必要开启事务的,数据库默认的配置就能满足需求。

但如果你一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询SQL必须保证整体的读一致性,否则,在前条SQL查询之后,后条SQL查询之前,数据被其他用户改变,就会造成数据的前后不一。

也仅有在这种情况下,要开启读事务。

5、private方法加事务注解有用么?

@Transaction注解加在private上,并没有什么卵用。

这倒不是事务处理的代码去写的特性。由于事务的这些功能,是通过AOP方式强加进去的,所以它收到动态代理的控制。

privatefinal修饰的方法,不会被代理。

但是,你却可以把private方法放在带有事务功能的public方法里。这样,它看起来也有了事务的一些功能特性,但它并没有。

End

互联网中,用到的事务并不多,很多都是非常小、速度非常快的接口,对于开发人员来说,事务是个累赘。

但在一些带有金融属性的业务中,或者一些企业级开发应用中,事务确实一个绕不过的坎。一旦深入其中,就会发现这个知识点,露着血盆大口,等君入瓮。

xjjdog从源码层次,聊到了几个面试常问的问题。不要觉得奇怪,有的人确实一直在拿着脏读、幻读这样的名词来面试。

而这些东西,都属于当时看了恍然大悟,第二天就继续懵逼的内容。

什么时候,才能务实一点呢?

18b7e407575a837e47163aaaf3020546.png

近期热门文章

《996的乐趣,你是无法想象的》魔幻现实主义,关爱神经衰弱

《一切荒诞的傲慢,皆来源于认知》不要被标题给骗了,画面感十足的消遣文章

《必看!java后端,亮剑诛仙》后端技术索引,中肯火爆。全网转载上百次。

《学完这100多技术,能当架构师么?(非广告)》精准点评100多框架,帮你选型

作者简介:小姐姐味道  (xjjdog),一个不允许程序员走弯路的公众号。聚焦基础架构和Linux。十年架构,日百亿流量,与你探讨高并发世界,给你不一样的味道。我的个人微信xjjdog0,欢迎添加好友,进一步交流

b0f5ed9f917af787df1b4db34e08efc0.gif

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/502202.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

php 字符串比较txt,PHP读到txt中文字符串比较失败

最近开始做PHP。今天从txt文件中读取一个中文字符串&#xff0c;跟一个字符串作比较&#xff0c;老是匹配失败&#xff0c;开始以为是编码问题&#xff0c;因为我的PHP文件编码是utf-8&#xff0c;遂将txt文件的编码也改成utf-8&#xff0c;好&#xff0c;噩梦来了...我将读到的…

箭头函数的this指向谁_高阶函数

NodeJS 系列文章&#xff0c;本篇是第一篇&#xff0c;首先&#xff0c;预计将后续高频使用逻辑串一遍&#xff0c;依次是高阶函数&#xff0c;promise以及事件机制。本篇主要是高阶函数。call、bind、applycall、apply 都是改变 this 指向&#xff0c;区别是接受参数的方式不一…

php怎么加编码,php怎么设置编码格式

php设置编码格式的方法&#xff1a;首先在php脚本中添加【header(“Content-Type: text/html; charsetutf-8")】&#xff1b;然后在静态页面设置编码&#xff1b;最后保证所有文件的编码相同即可。如果要使用gb2312编码&#xff0c;那么php要输出头&#xff1a;(推荐教程&…

java 中文 音序,java 中文字符串数组按照音序排列

java 中文字符串数组按照音序排列复制代码 代码如下:public class SortComparator implements Comparator{public int compare(Object o1,Object o2) {try{byte[] buf1 ((String) o1).getBytes("unicode");byte[] buf2 ((String) o2).getBytes("unicode"…

matlab1 3倍频程,瞬时声压时域数据怎么用matlab进行1/3倍频程声压级分析

%A计权声压级频谱分析clc;clear;close all;%时域分析ywavread(abc.wav);%频域分析fs51200;%采样频率p02e-5;%参考声压f[1.00 1.25 1.600 2.00 2.50 3.15 4.00 5.00 6.30 8.0]; %基准中心频率f1[20.00 25.0 31.5 40.0 50.0 63.0 80];fc[f1,100*f,1000*f,10000*f]; %%%%%%%%%中心…

python paramiko长连接_【Python】 SSH连接的paramiko

paramiko*paramiko需要PyCrypto模块的支持paramiko支持通过SSH协议进行一些操作&#xff0c;比如远程执行命令&#xff0c;上下传文件等等用法&#xff1a;①  远程命令ssh paramiko.SSHClient()ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())#指定当对方主机没…

php 共享内存列队,php中对共享内存,消息队列的操作

消息队列中的数据同样受到大小的约束&#xff0c;具体约束范围可通过msg_stat_queue的msg_qbytes看到。这段代码唯一有点小改动的地方就在接受消息时&#xff0c;指定了MSG_IPC_NOWAIT&#xff0c;不然如果目标队列没有数据&#xff0c;默认会一直等待。 一般会用到共享内存或消…

python文件路径拼接多出斜杠_浅谈python中拼接路径os.path.join斜杠的问题

浅谈python中拼接路径os.path.join斜杠的问题调试程序的过程中&#xff0c;发现通过os.path.join拼接的路径出现了反斜杠directory1/opt/apps/upgradePackagedirectory2icp_v1.8.0directory3os.path.join(directory1,directory2)print(directory3 : %s %directory3)执行结果dir…

matlab约当消去法,Gauss消去法解线性方程组(Matlab)

clear;clc;% Gauss消去法解线性方程组A[3 -5 6 4 -2 -3 8;1 1 -9 15 1 -9 2;2 -1 7 5 -1 6 11;-1 1 3 2 7 -1 -2;4 3 1 -7 2 1 1;2 9 -8 11 -1 -4 -1;7 2 -1 2 7 -1 9];%系数矩阵b[11 2 29 9 5 8 25];%n维向量yinv(A)*b %matlab的计算结果nlength(b);%方程个数nxzeros(n,1);%…

动感灯箱制作流程培训_广告立体灯箱的特点有哪些?

随着广告业的发展&#xff0c;灯箱广告行业也在突飞猛进。在灯箱广告屏的设计与制作中&#xff0c;为了追求真实感和艺术性&#xff0c;采用彩印或摄影喷绘;也有人用特种纸、塑料板(膜)、灯箱布等进行丝网印刷制作灯箱图片&#xff0c;并根据成本和耐候性要求选择制作方法和灯箱…

MATLAB仿真TSC在哪里找,-bash:tsc:找不到命令

我正在使用MAC并尝试安装TypeScript . 我使用安装打字稿sudo npm install -g typescript以下是结果Password:/Users//node/bin/tsc -> /Users//node/lib/node_modules/typescript/bin/tsc/Users//node/bin/tsserver -> /Users//node/lib/node_modules/typescript/bin/tss…

supervisor 重启_supervisor_twiddler的使用

点击上方蓝字关注【 北邮郭大宝 】Supervisor可以很好的实现Python的进程管理&#xff0c;但是新增进程时需要配置文件&#xff0c;对于需要动态创建进程的场景不是很友好&#xff0c;supervisor_twiddler插件可以帮助实现上述功能。本文就supervisor_twiddler的使用做简单的de…

matlab的词云,Word Cloud (词云) - JavaScript

在上一篇中已经分享了用 Python 创建词云了。接下来继续总结其他创建词云的方法。&带道术用量确示常构端析以要效开的用&#xff0c;近不gt;> Create Word Cloud via JavaScrip要圈器是天的年编功小还久概据含直这请框结业未商屏页屏随会维气大机域页效实一应控高标tJav…

python数据分析的步骤排序_Python数据分析

一.数据分析步骤1.提出问题2.理解数据3.数据清洗4.构建模型5.数据可视化二.朝阳医院2018年销售数据数据导入在文件路径前加r(转义符)文件可能有多个sheet&#xff0c;所以用sheet_name重命名参数dtypestr同意先按照字符串读入&#xff0c;之后再转换salesDf.head()&#xff0c;…

archlinux php mysql,arch linux上安装 httpd+php+mysql+ openssl(转)

arch linux上安装 httpdphpmysql openssl(转)[more]在arch linux安装则是非常的容易&#xff0c;只要执行&#xff1a;# pacman -Sy apache php mysql openssl根据提示选择输入Y即可安装&#xff01;1.修改apache配置文件默认的情况&#xff0c;apache是不支持PHP的&#xff0c…

ai人工智能python开发_Python才是人工智能AI的首选编程语言,你值得拥有……

在所有编程语言里&#xff0c;Python并不算萌新&#xff0c;从1991年发布第一个版本&#xff0c;至今已经快30年了。最近几年&#xff0c;随着人工智能概念的火爆&#xff0c;Python迅速升温&#xff0c;成为众多AI从业者的首选语言。根据数据平台 Kaggle发布的2017年机器学习及…

php bind_param,php – 在mysqli bind_param中绑定动态变量

当我尝试下面的代码时,它会给我一个警告mysqli_stmt::bind_param(): Number of elements in type definition string doesnt match number of bind variables$stmt $mysqli->prepare(SELECT * FROM users WHERE lname ? AND fname ?);$type "ss";$param ar…

unique函数_unique函数使用场景(一)

unique函数使用场景(一)前情提示&#xff1a;Microsoft 365(就是原来的office 365)已经推出有一段时间了&#xff0c;去年年审就在项目上使用365更新的一系列动态函数来提高审计效率(大胆一点&#xff0c;就是更好的偷懒)。讲解动态函数的文章不多&#xff0c;且365普及度低&am…

php属性未定义,PHP-警告-未定义的属性:stdClass-修复?

我在错误日志中收到此警告&#xff0c;并想知道如何在代码中更正此问题。警告&#xff1a;PHP注意&#xff1a;未定义属性&#xff1a;stdClass :: $ records在440行的script.php中一些代码&#xff1a;// Parse object to get account ids// The response doesnt have the rec…

python做数据和大数据区别_不懂Python,不懂大数据的人,和咸鱼有什么区别?

原标题&#xff1a;不懂Python&#xff0c;不懂大数据的人&#xff0c;和咸鱼有什么区别&#xff1f;在这个处处充斥着大数据影响的时代之下&#xff0c;不懂Python&#xff0c;不懂大数据&#xff0c;你就可能轻易地错过身边的黄金。我们生活在数据密布的环境中&#xff0c;就…