解决方案:数字化营销人员必备工具–SEMRush SEO综合分析工具
优采云 发布时间: 2022-11-27 20:49解决方案:数字化营销人员必备工具–SEMRush SEO综合分析工具
在外贸业务中,无论是从事B2B传统外贸,还是B2C跨境电商,营销都是不可或缺的一环。当然,相关的营销手段也是必不可少的。本文介绍目前最全面的SEO工具:SEMRush是一站式数字营销工具。他还拥有关键词排名跟踪、关键词研究、长尾词研究、外部链接挖掘、PPC广告研究、流量分析、品牌监测、竞争对手分析、社交媒体分析、行业热门文章搜索、Content营销和许多其他功能。
市面上有很多SEO工具,但是如果你不想每一个产品都买,SEMRush是你最好的选择,因为这是一个综合分析工具,因为它不仅功能齐全,而且每一个功能都是先进的三者的存在是一个非常强大的工具。同时,还有中文界面。对于我们中国人来说,界面已经很友好了。不用担心菜单不知道是什么意思。
SEMRush是一家提供搜索引擎优化数据的老牌公司。成立于2008年,其原理是SEMRush的服务器搜索互联网上所有有价值的网站,分析归类,然后链接各个网站,网站在Google,关键词等排名,是一个不错的选择网站分析工具。和谷歌类似,只是谷歌使用它的爬虫把网站索引到它的数据库中,而SEMRush的爬虫将获取的数据存储到它的数据库中供我们查询。
如何选择SEM(搜索引擎营销)工具?
在过去的探索中,我使用了很多相关的工具。作为数字营销人员,我的工作之一就是了解市场上有哪些工具可以帮助我更轻松、更快速、更高效地完成工作。在我们选择工具之前,首先要了解选择和使用工具的标准。下面从几个方面来分析:
实用性
这个是第一位的,因为如果一个工具对我来说不是真的有用(不管是便宜的还是昂贵的),不要浪费时间和金钱。一个工具的数据必须给我们提供有用的数据和指标,否则数据不准确或者指标不明确,或者需要过多的人工干预,就会失去原有的价值。
价格
对于我们老百姓来说,价格其实是比较敏感的。当然,这部分是很多人拒绝购买工具的重要原因,但对于真正做数字营销的人来说,这笔费用是应该花的。省什么省,把钱花在有用的工具上,没必要在这上面省钱。
特征
这个跟实用性有关,但是在具体使用上跟每个功能都不一样,要考虑综合性能,而不仅仅是某一个功能。例如,使用 SEMRush,您可以在同一个工具中使用 SEO、关键字研究、排名跟踪、社交媒体、链接构建和 PPC 相关任务等。
便于使用
得到一个新事物,我们不需要花太多时间去研究如何使用它,而是通过相关数据轻松直观地做出良好的判断,帮助我们节省时间,高效工作。我认为数字营销人员应该拥有这样一个易于使用且直观的判断工具。
可靠性
最后但并非最不重要的一点是,我们需要一种能够为我们提供可靠和最新数据的工具,以帮助我们就我们的活动做出明智的决策。
SEMRush 如何帮助我们成为更好的数字营销人员?
基于以上标准,我选择 SEMRush 而不是其他工具,因为:
他真的帮助了我并且很实用并且收录
很*敏*感*词*
SEMRush 不仅仅是一个工具,而是一个用于执行许多任务的完整平台,包括:关键词研究、关键词跟踪、网站审计、反向链接审计和分析、PPC 活动的广告研究(包括购物广告、视频和展示广告)、域到-域比较、社交媒体分析、关键词难度分析等。
下图是 SEMRush 功能的概述。
便于使用
SEMRush 团队在工具界面上做得非常好。所有菜单内容设置一目了然,使用过程中不会卡在某个菜单或选项中。一切都非常清楚。
可靠性
所有数据都是最新的,包括所有类型网站的信息,而不仅仅是排名靠前的网站。
此外,他们还提供出色的文档(包括网络研讨会和学院)和内容丰富的数字营销博客。还有电子书下载,
价格
有年付和月付两种方式:
专业版为 99.95 美元(每月),您可以使用以上功能。
如果按照年付方式,月付会降到每月$83.28,这样就更好了。
当您考虑到其他工具每月仅针对关键字跟踪或仅针对关键字研究和反向链接分析收取此金额而没有所有其他有用功能时,这是一个合理的价格。
点此查看SEMrush的详细价格
" />
SEMRush功能介绍
为了更好地了解 SEMRush 如何为您提供帮助,这里有一个示例,说明它是如何在您的网站或客户项目中实际使用的。
如果您想效仿,请先注册一个 7 天免费试用帐户。
这将帮助您快速了解该工具的所有功能。
注册试用时,无需先添加*敏*感*词*等支付方式。当我们购买很多其他工具进行试用时,我们需要添加支付方式。如果你觉得 SEMRush 不错,你可以升级为专业用户、专家或企业帐户,或者如果你有其他需求,你可以联系他们的团队。
关键字研究 - 如何轻松找到长尾 关键词
我以市场上比较火的一个NICHE小众为例,减肥小众,找一个容易定位的文章话题,也就是长尾关键词。
跟随脚步,继续前行:
登录 SEMRush 并在顶部搜索栏中输入 关键词“如何减肥”,选择美国并单击“搜索”按钮。
您在仪表板中看到的几乎是您需要了解的有关“如何减肥”关键字的摘要。
您可以查看此关键字的总体价值、相关关键字、CPC 信息、关键字的站点排名、当前运行的 PPC 广告(包括实际广告文案)等。
由于我们的目标是找到长尾关键词,因此请单击“词组匹配关键词”下的“查看完整报告”按钮。
如果你只能看到 10 个结果,因为你是试用版,但你已经可以找到第一个候选长尾 关键词'how many calories shold i eat to lose weight'。
如果是专业版,可以深挖,找其他长尾关键词,多挖一些词,比如“减肥应该喝多少水”。
单击关键字将为您提供更多相关关键字。
关键词 如果您有经验,“多少水可以快速减肥”是一个更好的选择,让我们仔细检查一下。
从左边的菜单(工具栏)中选择“关键词难度”,在下面的框中添加如下所示的三个关键词,然后点击“显示难度”:
SEMRush为每个关键词提供了一个百分比难度,可以看到关键词“喝多少水能快速减肥”确实比“怎么减肥”或者“喝多少水能减肥”目标更好做单词。
反向链接分析 - 识别反向链接的机会
我相信大多数人都会同意,SEO 最耗时的任务之一就是进行反向链接分析。
你需要多少反向链接来提高你的谷歌排名和你的链接配置文件以对抗竞争对手?
您可以在下方的搜索框中输入关键词或域名链接试一试。
SEMRush 可以给你答案。
在左侧菜单域分析下的“反向链接”下,您可以在该菜单下的许多工具中找到反向链接机会。
让我们以一个随机网站为例来寻找反向链接机会,您找到了一个相似但更强的博客,并且想要分析他们的链接配置文件。
当您输入 SEMRush 反向链接检查器时,您会看到很多关于您的 hubspot 链接配置文件的信息,包括:
谁链接到他的域
哪些链接是“不关注”的,
每个链接的锚文本
页面上的链接数
何时添加链接和更多信息。
" />
为了使分析更容易,您可以使用过滤器并排除收录
其域名的锚文本,即 hubspot,以及像和(很难找到)这样的知名博客。
因此,如果您联系他们或(在社交媒体上与他们联系)并让他们知道您的存在,您最终会得到极少数网站链接到您的博客(请记住您正在寻找反向链接)。
关键字排名跟踪 - 跟踪进度
尝试在 Google 中对您的网站进行排名时,最重要的指标之一是您对不同关键字的排名。
做SEO的都知道,你的关键词排名不可能马上奇迹般出现在Google首页,而是要不断优化,逐步提升排名。
SEMRush 有一个功能,叫做“位置跟踪”——您可以在左侧菜单的工具栏中找到它。
SEMRush 的专业版允许添加多达 500 个关键字并监控它们在 Google 中的排名或在 Adwords 中的位置。
这个功能很不错,大多数其他工具可能允许少量的关键词有这个跟踪功能,但是要额外付费才能使用。SEMRush不跟踪Adwords的位置,同样的价格是无法匹配的。
使用此功能很简单,只需输入您要为您的域跟踪的关键字,然后让 SEMRush 完成剩下的工作。
您甚至可以将您的域与其他域的特定关键字进行比较,看看您的排名。
开始跟踪关键字的一个好方法是查看来自 Google Search Console(Google 网站管理员工具)的性能报告。
从报告中导出关键字并将它们添加到跟踪位置以跟踪您的进度。
网站审核 - 正确优化您的页面搜索引擎优化
页面搜索引擎优化现在比以往任何时候都更加重要。标题和描述、alt 标签、h1 标题、网站速度、重复内容、断开的链接等等,如果您想增加网站在搜索中的曝光率,这些都是您需要设置的。
SEMRush 有一个名为“站点审核”的工具(位于左侧菜单中的“工具”下),可以帮助您修复页面上的 SEO 缺陷。
使用站点审核功能分析网站后,您将收到一份报告,其中收录
必须修复的错误和警告。
下图为分析报告图片:
进行任何必要的修复后,您可以重新抓取您的站点,比较不同的运行并继续工作,直到所有错误和警告都消失。
PPC - 监控你的竞争对手
在开始新的 PPC 活动之前,您需要做的一件事是检查您要在活动中定位的关键字,并尽可能详细地了解您的竞争对手。
谁在为这些关键字出价,他们每天花费多少,以及他们的广告是什么样子,如果您想为您的企业或客户设置有利可图的 PPC 广告,您需要知道几个问题的答案。
SEMRush 可以帮助您回答所有问题。
假设您正在准备一个新的 PPC 广告,使用一个竞争激烈的 关键词 但有利可图的词:订婚戒指。
您可以使用“广告研究”工具获取您需要的所有详细信息,包括:实时数据、谁在为某个关键字做广告、平均每次点击费用、广告文案(包括谷歌购物广告系列)、相关关键字等等。
如果您使用 PPC 广告,那么您就会知道这些信息对您有多重要,尤其是因为您可以更深入地挖掘、查看历史统计数据,甚至可以将数据导出到电子表格以供进一步分析。
总结:
我们在选择工具的时候,一定要充分考虑自己的需求。单一分析工具具有单一分析工具的优点。当然,你支付的费用并不低于SEMRush支付的费用。如果每个工具的总成本肯定比 SEMRush Pay 多。所以,如果选择综合工具,SEMRush一站式数字营销工具是最佳选择,没有之一,强烈推荐。
该工具具有更*敏*感*词*,并且会不断丰富更*敏*感*词*,而且无需额外付费即可使用。
SEMRush 正在努力保持在 SEO 工具领域的领先地位,并保持其作为市场上最好的搜索引擎营销工具之一的地位。
解决方案:Spring 声明式事务应该怎么学?
1 简介
Spring的声明式事务大大方便了日常事务相关代码的编写。它的设计非常巧妙,在使用中几乎感觉不到。你只需要优雅地加上一个@Transactional注解,一切就顺理成章了!
毫不夸张地说,Spring 的声明式事务非常容易使用,以至于大多数人都忘记了如何编写程序化事务。
但是,您越是想当然,一旦出现问题,就越难排除故障。不知道你和你的小伙伴有没有遇到@Transactional失败的场景。这不仅是日常开发中经常踩到的坑,也是面试中的高频问题。
其实这些故障场景不需要死记硬背。如果了解了它的工作原理,并结合源码,需要调试的时候就可以自己分析了。毕竟,源代码是最好的手册。
还是那句话,授人以鱼不如授人以渔。即使班代表总结了100个失败场景,也未必能把你可能踩到的坑都盖掉。因此,在本文中,课代表将结合几种常见的故障情况,从源码层面来说明故障原因。仔细阅读本文后,相信您会对声明式事务有更深入的了解。
本文所有代码均已上传至课代表的github。为了方便快速部署和运行,示例代码使用内存数据库H2,不需要额外部署数据库环境。
2.审查手写交易
数据库层面的事务具有ACID的四个特性,共同保证了数据库中数据的准确性。事务的原理不是本文的重点。我们只需要知道示例中使用的H2数据库已经完全实现了对事务的支持(read committed)。
在编写Java代码时,我们使用JDBC接口与数据库进行交互,完成与事务相关的指令。伪代码如下:
//获取用于和数据库交互的连接
Connection conn = DriverManager.getConnection();
try {
// 关闭自动提交:
conn.setAutoCommit(false);
// 执行多条SQL语句:
insert();
update();
delete();
// 提交事务:
conn.commit();
} catch (SQLException e) {
// 如果出现异常,回滚事务:
conn.rollback();
} finally {
//释放资源
conn.close();
}
这是一个典型的程序化事务代码流程:在开始之前先关闭autocommit,因为默认情况下,autocommit是开启的,每条语句都会开启一个新的事务,执行完后会自动提交。
关闭事务的自动提交是为了让多个SQL语句在同一个事务中。如果代码运行正常则提交事务,如果出现异常则整体回滚,从而保证多条SQL语句的完整性。
除了事务提交,数据库还支持保存点的概念。在一次物理交易中,可以设置多个保存点,方便回滚到指定的保存点(类似于玩单机游戏时的存档,角色挂掉后可以随时保存)回到上次archive)设置和回滚保存点的代码如下:
//设置保存点
Savepoint savepoint = connection.setSavepoint();
//回滚到指定的保存点
connection.rollback(savepoint);
//回滚到保存点后按需提交/回滚前面的事务
conn.commit();//conn.rollback();
Spring 的声明式事务所做的工作围绕着两对命令:commit/rollback transaction 和set/rollback to savepoint。为了让我们写尽可能少的代码,Spring定义了几个传播属性来进一步抽象事务。注意,Spring的事务传播(Propagation)只是Spring定义的一层抽象,与数据库无关。不要将它与数据库的事务隔离级别混淆。
3. Spring的事务传播(Transaction Propagation)
观察传统的事务代码:
conn.setAutoCommit(false);
// 执行多条SQL语句:
insert();
update();
delete();
// 提交事务:
conn.commit();
此代码在同一个事务中表达了三个 SQL 语句。
它们可能是同一个类中的不同方法,也可能是不同类中的不同方法。如何表达事务方法加入其他事务、创建自己的事务、嵌套事务等概念?这取决于Spring的事务传播机制。
事务传播(Transaction Propagation)字面意思是:事务的传播/传递方式。
在Spring源码的TransactionDefinition接口中,定义了7种传播属性。官网解释了其中的3个。只要理解了这3个,剩下的4个都只是推论。
1)传播需要
字面意思:传播 - 必须
PROPAGATION_REQUIRED 是它的默认传播属性,它强制打开事务。如果之前的方法已经开启了事务,则会加入之前的事务。两者在物理上属于同一个事务。
一图抵千言,下图说明他们物理上是在同一笔交易中:
上图翻译成伪代码是这样的:
try {
conn.setAutoCommit(false);
transactionalMethod1();
transactionalMethod2();
conn.commit();
} catch (SQLException e) {
conn.rollback();
} finally {
conn.close();
}
由于是在同一个物理事务中,如果transactionalMethod2()出现异常,需要回滚,那么transactionalMethod1()是否也应该回滚?
得益于上面的图和伪代码,我们很容易得到答案,transactionalMethod1()必须回滚。
这是一个问题:
事务方法中的异常被try catch吃掉了,事务还能回滚吗?
不急于下结论,看下面两个代码示例就知道了。
示例一:不会回滚的情况(事务失败)
观察下面的代码,methodThrowsException()什么都不做,只是抛出一个异常,调用者尝试捕获抛出的异常,这种场景下不会触发回滚
@Transactional(rollbackFor = Exception.class)
public void tryCatchRollBackFail(String name) {
jdbcTemplate.execute("INSERT INTO USER (NAME) VALUES ('" + name + "')");
try {
methodThrowsException();
} catch (RollBackException e) {
//do nothing
}
}
public void methodThrowsException() throws RollBackException {
throw new RollBackException(ROLL_BACK_MESSAGE);
}
示例2:回滚的情况(交易生效)
再看这个例子,异常也是try catch,结果却完全相反
@Transactional(rollbackFor = Throwable.class)
public void tryCatchRollBackSuccess(String name, String anotherName) {
jdbcTemplate.execute("INSERT INTO USER (NAME) VALUES ('" + name + "')");
try {
// 带事务,抛异常回滚
userService.insertWithTxThrowException(anotherName);
} catch (RollBackException e) {
// do nothing
}
}
<p>
" />
@Transactional(rollbackFor = Throwable.class)
public void insertWithTxThrowException(String name) throws RollBackException {
jdbcTemplate.execute("INSERT INTO USER (NAME) VALUES ('" + name + "')");
throw new RollBackException(ROLL_BACK_MESSAGE);
}</p>
本例中,两个方法的事务都没有设置propagation属性,默认为PROPAGATION_REQUIRED。即前者开启一个事务,后者加入之前开启的事务,两者属于同一个物理事务。insertWithTxThrowException() 方法抛出异常,将事务标记为回滚。既然大家都在同一条船上,如果后者翻船,前者也不能幸免。
所以tryCatchRollBackSuccess()执行的SQL也会回滚,执行本用例可以查看结果
访问:8080/h2-console/,连接信息如下:
点击连接进入控制台查看表中数据:
USER表没有插入数据,证明了我们的结论,在日志中可以看到报错:
事务回滚,因为它已被标记为只回滚
即回滚后一个方法触发的事务,从而回滚前一个方法的插入。
看到这里,你应该能彻底理解默认的传播类型PROPAGATION_REQUIRED了。在这个例子中,是因为两个方法相互影响,在同一个物理事务下回滚。
你可能会问,我想让启动事务的两个方法互不影响怎么办?
这将使用下面提到的通信类型。
2)、PROPAGATION_REQUIRES_NEW
从字面上看:传播必须新
PROPAGATION_REQUIRES_NEW 和 PROPAGATION_REQUIRED 的区别在于,它总是开启一个独立的事务,不会参与已有的事务,这样就保证了两个事务的状态相互独立,互不影响,不会被一个干扰一方回滚到另一方。
一图抵千言,下图说明他们物理上不在同一笔交易中:
上图翻译成伪代码是这样的:
//Transaction1
try {
conn.setAutoCommit(false);
transactionalMethod1();
conn.commit();
} catch (SQLException e) {
conn.rollback();
} finally {
conn.close();
}
//Transaction2
try {
conn.setAutoCommit(false);
transactionalMethod2();
conn.commit();
} catch (SQLException e) {
conn.rollback();
} finally {
conn.close();
}
TransactionalMethod1 打开一个新事务。当它调用同样需要事务的TransactionalMethod2时,由于后者的propagation属性被设置为PROPAGATION_REQUIRES_NEW,所以它暂停了之前的事务(至于怎么暂停,后面会从源码中看到),并开启了一个新的物理独立于前者的事务,使得两者的事务回滚不会相互干扰。
还是前面的例子,只需要将insertWithTxThrowException()方法的事务传播属性设置为Propagation.REQUIRES_NEW就可以互不影响了:
@Transactional(rollbackFor = Throwable.class)
public void tryCatchRollBackSuccess(String name, String anotherName) {
jdbcTemplate.execute("INSERT INTO USER (NAME) VALUES ('" + name + "')");
try {
// 带事务,抛异常回滚
userService.insertWithTxThrowException(anotherName);
} catch (RollBackException e) {
// do nothing
}
}
@Transactional(rollbackFor = Throwable.class, propagation = Propagation.REQUIRES_NEW)
public void insertWithTxThrowException(String name) throws RollBackException {
jdbcTemplate.execute("INSERT INTO USER (NAME) VALUES ('" + name + "')");
throw new RollBackException(ROLL_BACK_MESSAGE);
}
PROPAGATION_REQUIRED和Propagation.REQUIRES_NEW足以满足大部分应用场景,这也是开发中常用的事务传播类型。前者需要基于同一个物理事务一起回滚,后者则需要大家使用独立的事务,互不干扰。另一种场景是:外部方法和内部方法共用一个事务,但是内部事务的回滚不影响外部事务,外部事务的回滚可以影响内部事务。这是嵌套此传播类型的用例。
3)、PROPAGATION_NESTED
字面意思:传播嵌套
PROPAGATION_NESTED 可以设置多个保存点用于回滚现有的物理事务。这种部分回滚允许内部事务在它自己的范围内回滚,而外部事务在某些操作被回滚后继续执行。它的底层实现是数据库的保存点。
这种传播机制比前两种更灵活,见如下代码:
@Transactional(rollbackFor = Throwable.class)
public void invokeNestedTx(String name,String otherName) {
jdbcTemplate.execute("INSERT INTO USER (NAME) VALUES ('" + name + "')");
try {
userService.insertWithTxNested(otherName);
} catch (RollBackException e) {
// do nothing
}
// 如果这里抛出异常,将导致两个方法都回滚
// throw new RollBackException(ROLL_BACK_MESSAGE);
}
@Transactional(rollbackFor = Throwable.class,propagation = Propagation.NESTED)
public void insertWithTxNested(String name) throws RollBackException {
jdbcTemplate.execute("INSERT INTO USER (NAME) VALUES ('" + name + "')");
throw new RollBackException(ROLL_BACK_MESSAGE);
}
外部事务方法invokeNestedTx()启动事务,内部事务方法insertWithTxNested标记为嵌套事务。内部事务的回滚是通过保存点完成的,不会影响外部事务。外部方法的回滚会和内部方法一起回滚。
小结:本节介绍三种常见的Spring声明式事务传播属性。结合示例代码,相信你也看懂了。接下来我们从源码层面来看一下Spring是如何帮助我们简化事务模板的。代码,解放生产力。
4.源代码窥探
在阅读源码之前,先分析一个问题:我想给一个方法添加事务,需要做哪些工作?
即使我们手写,也至少需要四步:
这不就是典型的AOP吗~
没错,Spring就是通过AOP来增强我们的事务方法,从而完成事务的相关操作。以下是几个关键类及其关键方法的源码走查。
既然是AOP,就要为事务写一个切面来做这件事。这个类是 TransactionAspectSupport。从名字就可以看出这是“事务切面支持类”。它的主要工作是实现交易的执行过程。它的主要实现方法是invokeWithinTransaction:
protected Object invokeWithinTransaction(Method method, @Nullable Class targetClass,
final InvocationCallback invocation) throws Throwable {
// 省略代码...
// Standard transaction demarcation with getTransaction and commit/rollback calls.
// 1、开启事务
TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
try {
// This is an around advice: Invoke the next interceptor in the chain.
// This will normally result in a target object being invoked.
//2、执行方法
retVal = invocation.proceedWithInvocation();
}
<p>
" />
catch (Throwable ex) {
// target invocation exception
// 3、捕获异常时的处理
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {
// Set rollback-only in case of Vavr failure matching our rollback rules...
TransactionStatus status = txInfo.getTransactionStatus();
if (status != null && txAttr != null) {
retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
}
}
//4、执行成功,提交事务
commitTransactionAfterReturning(txInfo);
return retVal;
// 省略代码...</p>
结合课代表添加的四步笔记,相信大家也能轻松理解。
了解了交易的主要流程之后,它的传递机制是如何实现的呢?这取决于 AbstractPlatformTransactionManager 类。从名字就可以看出,它负责事务管理。handleExistingTransaction 方法实现事务传播逻辑。下面是 PROPAGATION_REQUIRES_NEW 的实现和下面的代码:
private TransactionStatus handleExistingTransaction(
TransactionDefinition definition, Object transaction, boolean debugEnabled)
throws TransactionException {
// 省略代码...
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
if (debugEnabled) {
logger.debug("Suspending current transaction, creating new transaction with name [" +
definition.getName() + "]");
}
// 事务挂起
SuspendedResourcesHolder suspendedResources = suspend(transaction);
try {
return startTransaction(definition, transaction, debugEnabled, suspendedResources);
}
catch (RuntimeException | Error beginEx) {
resumeAfterBeginException(transaction, suspendedResources, beginEx);
throw beginEx;
}
}
// 省略代码...
}
我们知道PROPAGATION_REQUIRES_NEW会挂起之前的事务,开始一个独立的新事务,但是数据库不支持挂起事务。Spring是如何实现这个特性的?
从源码可以看出,这里调用了返回值为SuspendedResourcesHolder的suspend(transaction)方法,其实际逻辑由其内部的doSuspend(transaction)抽象方法实现。这里我们使用JDBC连接数据库。自然要选择DataSourceTransactionManager的子类来查看其实现。代码如下:
protected Object doSuspend(Object transaction) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
txObject.setConnectionHolder(null);
return TransactionSynchronizationManager.unbindResource(obtainDataSource());
}
这里是释放已有事务的连接,返回挂起的资源。下次开启事务时,会将挂起的资源一起传入,这样内层事务执行完成后,外层挂起的事务可以继续执行。
那么,什么时候恢复暂停的交易呢?
事务过程虽然是由TransactionAspectSupport实现的,但真正的提交和回滚是由AbstractPlatformTransactionManager完成的。在其 processCommit(DefaultTransactionStatus status) 方法的 finally 块中,执行了 cleanupAfterCompletion(status):
private void cleanupAfterCompletion(DefaultTransactionStatus status) {
status.setCompleted();
if (status.isNewSynchronization()) {
TransactionSynchronizationManager.clear();
}
if (status.isNewTransaction()) {
doCleanupAfterCompletion(status.getTransaction());
}
// 有挂起事务则获取挂起的资源,继续执行
if (status.getSuspendedResources() != null) {
if (status.isDebug()) {
logger.debug("Resuming suspended transaction after completion of inner transaction");
}
Object transaction = (status.hasTransaction() ? status.getTransaction() : null);
resume(transaction, (SuspendedResourcesHolder) status.getSuspendedResources());
}
}
这里判断有挂起的资源,会恢复执行,从而完成挂起和恢复事务的逻辑。
其他事务传播属性的实现,有兴趣的同学使用该类代表的示例工程,不再自行追源码。限于篇幅,这里只给出大概的处理流程。源码中有很多细节,需要同学们自己去体会。有了上面介绍的主要逻辑框架,跟踪源码和查看其他实现应该不会太困难。
5. 常见故障场景
当很多人(包括课代表自己)开始使用声明式事务的时候,都会觉得这个东西真的很猫腻。使用的时候有那么多规矩,一不小心就起不到作用。你为什么会有这种感觉?
爬了很多坑后,课代表总结了两个经验:
不看官方文档就看不懂源码
以下是一些失败场景:
1)非公开方法不生效
官网有说明:
方法可见性和@Transactional
当您使用具有 Spring 标准配置的事务代理时,您应该仅将 @Transactional 注释应用于具有公共可见性的方法。
2)Spring不支持redis集群中的事务
redis事务启动命令是multi,但是Spring Data Redis在redis集群中不支持multi命令。如果使用声明式事务,会报错:MULTI is currently not supported in cluster mode。
3)在多数据源的情况下,需要为每个数据源配置TransactionManager,必须指定transactionManager参数
在源码窥探的第四部分,已经看到AbstractPlatformTransactionManager实际上是进行事务操作的,它是TransactionManager的实现类。每个事务的连接连接由它管理。如果没有配置,则无法完成事务操作。单数据源情况下的正常运行是因为SpringBoot的DataSourceTransactionManagerAutoConfiguration自动帮我们配置好了。
4) rollbackFor设置错误
默认情况下,只有未检查的异常(即 java.lang.RuntimeException 的子类)和 java.lang.Error 被回滚。如果知道抛出异常需要回滚,建议设置为@Transactional(rollbackFor = Throwable.class)
5)AOP不生效
其他如MyISAM不支持,es不支持等就不一一列举了。
如果您有兴趣,可以在源代码中找到上述所有问题的答案。
六,结论
关于Spring的声明式事务,要想用好,确实需要多调试几遍源码。由于Spring的源码细节过于丰富,实在不适合在文章中全部贴出来。建议自己跟着源码走。一旦熟悉了,再遇到失败就不怕了。
以下信息证明我没有说谎
1、文中测试用例代码:
2. Spring官网交易文档:#tx-propagation
3、Oracle官网JDBC文档:
4.Spring Data Redis源码:
往期原创干货推荐
使用 Spring Validation 优雅地验证参数
单例模式,关键字级别详解
深入浅出MySQL优先级队列(你肯定会踩到order by limit问题)