先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7
深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
如果你需要这些资料,可以添加V获取:vip1024b (备注Java)
正文
示例二:会回滚的情况(事务生效)
再看这个例子,同样是 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
}
}
@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);
}
本例中,两个方法的事务都没有设置propagation
属性,默认都是PROPAGATION_REQUIRED
。即前者开启事务,后者加入前面开启的事务,二者同属于一个物理事务。insertWithTxThrowException()
方法抛出异常,将事务标记为回滚。既然大家是在一条船上,那么后者打翻了船,前者肯定也不能幸免。
所以tryCatchRollBackSuccess()
所执行的SQL也必将回滚,执行此用例可以查看结果
访问 http://localhost:8080/h2-console/ ,连接信息如下:
点击Connect
进入控制台即可查看表中数据:
USER 表确实没有插入数据,证明了我们的结论,并且可以看到日志报错:
Transaction rolled back because it has been marked as rollback-only
事务已经回滚,因为它被标记为必须回滚。
也就是后面方法触发的事务回滚,让前面方法的插入也回滚了。
看到这里,你应该能把默认的传播类型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_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
可以在一个已存在的物理事务上设置多个供回滚使用的保存点。这种部分回滚可以让内部事务在其自己的作用域内回滚,与此同时,外部事务可以在某些操作回滚后继续执行。其底层实现就是数据库的savepoint
。
这种传播机制比前面两种都要灵活,看下面的代码:
@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
标记为嵌套事务,内部事务的回滚通过保存点完成,不会影响外部事务。而外部方法的回滚,则会连带内部方法一块回滚。
小结:本小节介绍了 3 种常见的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();
}
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;
// 省略代码…
结合课代表增加的这四步注释,相信你很容易就能看明白。
搞懂了事务的主要流程,它的传播机制又是怎么实现的呢?这就要看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());
}
这里是把已有事务的connection
解除,并返回被挂起的资源。在接下来开启事务时,会将该挂起资源一并传入,这样当内层事务执行完成后,可以继续执行外层被挂起的事务。
那么,什么时候来继续执行被挂起的事务呢?
事务的流程,虽然是由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、常见失效场景
很多人(包括课代表本人)一开始使用声明式事务,都会觉得这玩意儿真坑,使用起来那么多条条框框,一不小心就不生效了。为什么会有这种感觉呢?
爬了多次坑之后,课代表总结了两条经验:
-
没看官方文档
-
不会读源码
最后总结我的面试经验
2021年的金三银四一眨眼就到了,对于很多人来说是跳槽的好机会,大厂面试远没有我们想的那么困难,摆好心态,做好准备,你也可以的。
另外,面试中遇到不会的问题不妨尝试讲讲自己的思路,因为有些问题不是考察我们的编程能力,而是逻辑思维表达能力;最后平时要进行自我分析与评价,做好职业规划,不断摸索,提高自己的编程能力和抽象思维能力。
BAT面试经验
实战系列:Spring全家桶+Redis等
其他相关的电子书:源码+调优
面试真题:
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
路,因为有些问题不是考察我们的编程能力,而是逻辑思维表达能力;最后平时要进行自我分析与评价,做好职业规划,不断摸索,提高自己的编程能力和抽象思维能力。
[外链图片转存中…(img-MFIEHIU4-1713571920471)]
BAT面试经验
实战系列:Spring全家桶+Redis等
[外链图片转存中…(img-2WFmBe5w-1713571920472)]
其他相关的电子书:源码+调优
[外链图片转存中…(img-5oJzXpwU-1713571920472)]
面试真题:
[外链图片转存中…(img-Pi30qY8O-1713571920473)]
[外链图片转存中…(img-Fl1sthbH-1713571920474)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-QgjWpXZs-1713571920474)]
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!