• Spring 事务原理总结七


    今天是二零二四年二月十八,农历正月初九。同时今天也是农历新年假期后的第一个工作日。我的内心既兴奋,又担忧,更急躁。兴奋是因为假期后的第一个工作日工作轻松;担忧是因为经过了这么长时间,我依旧没搞明白Spring事务,总觉得有些东西没搞懂,却又不知道是哪里,所以只能在这里徘徊不前;急躁是因为许多事情都要用钱去解决,自己那微薄的收入根本就是杯水车薪,而自己又不知道用哪种方式去筹集足够的资金来解决问题,所以只能整日急不可耐,而问题依旧。到底该怎么办呢?∙∙∙∙∙∙

    针对第二种心理,我觉得没有其他办法,只能继续梳理,但也不能因此而放弃后面知识点的梳理。今天就让我们来聊一下Spring事务的几个面试题吧!我想围绕下面这样几个问题展开:

    1. 《Spring事务原理总结四》这篇文章中提到的将TransactionInfo绑定到当前线程的意义是什么呢?
    2. Spring是如何解决事务嵌套的?

    首先回忆一下前面几篇文章的主要内容:《Spring事务原理总结一》主要梳理了事务的基本概念及特征,同时也梳理了Spring事务的基本用法;《Spring事务原理总结二》则主要梳理了Spring框架解析及注册事务代理的流程;《Spring事务原理总结三》则主要梳理了Spring事务的一些核心组件及其继承结构;《Spring事务原理总结四》梳理了Spring事务的执行流程;《Spring事务原理总结五》主要梳理并回答了前一篇文章中遗留的几个问题;《Spring事务原理总结六》主要梳理了Spring事务的异常回滚流程。接着让我们试着利用这些文章梳理的内容来回答这两个问题。

    首先看第一个问题,将TransactionInfo对象绑定到当前线程的操作,在《Spring事务原理总结四》这篇文章中有提到,如下图所示:

    当时只是说了这些代码位于TransactionAspectSupport类中,不过并没有深究其意义。现在让我们细化一下这个说法,然后在梳理完第二个问题后来回答这个问题。TransactionInfos是TransactionAspectSupport类中的最终静态内部类该类内部定义了bindToThread()方法和restoreThreadLocalStatus()方法。所以截图中说前者位于TransactionAspectSupport类的说法也是对的,因为其所在的类位于TransactionAspectSupport类中),其源码如下所示:

    1. protected static final class TransactionInfo {
    2. @Nullable
    3. private final PlatformTransactionManager transactionManager;
    4. @Nullable
    5. private final TransactionAttribute transactionAttribute;
    6. private final String joinpointIdentification;
    7. @Nullable
    8. private TransactionStatus transactionStatus;
    9. @Nullable
    10. private TransactionInfo oldTransactionInfo;
    11. public TransactionInfo(@Nullable PlatformTransactionManager transactionManager,
    12. @Nullable TransactionAttribute transactionAttribute, String joinpointIdentification) {
    13. this.transactionManager = transactionManager;
    14. this.transactionAttribute = transactionAttribute;
    15. this.joinpointIdentification = joinpointIdentification;
    16. }
    17. public PlatformTransactionManager getTransactionManager() {
    18. Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");
    19. return this.transactionManager;
    20. }
    21. @Nullable
    22. public TransactionAttribute getTransactionAttribute() {
    23. return this.transactionAttribute;
    24. }
    25. /**
    26. * Return a String representation of this joinpoint (usually a Method call)
    27. * for use in logging.
    28. */
    29. public String getJoinpointIdentification() {
    30. return this.joinpointIdentification;
    31. }
    32. public void newTransactionStatus(@Nullable TransactionStatus status) {
    33. this.transactionStatus = status;
    34. }
    35. @Nullable
    36. public TransactionStatus getTransactionStatus() {
    37. return this.transactionStatus;
    38. }
    39. /**
    40. * Return whether a transaction was created by this aspect,
    41. * or whether we just have a placeholder to keep ThreadLocal stack integrity.
    42. */
    43. public boolean hasTransaction() {
    44. return (this.transactionStatus != null);
    45. }
    46. private void bindToThread() {
    47. // Expose current TransactionStatus, preserving any existing TransactionStatus
    48. // for restoration after this transaction is complete.
    49. this.oldTransactionInfo = transactionInfoHolder.get();
    50. transactionInfoHolder.set(this);
    51. }
    52. private void restoreThreadLocalStatus() {
    53. // Use stack to restore old transaction TransactionInfo.
    54. // Will be null if none was set.
    55. transactionInfoHolder.set(this.oldTransactionInfo);
    56. }
    57. @Override
    58. public String toString() {
    59. return (this.transactionAttribute != null ? this.transactionAttribute.toString() : "No transaction");
    60. }
    61. }

    这个类上有bindToThread()方法和restoreThreadLocalStatus()两个方法,它们的主要作用就是更改当前线程持有的ThreadLocal上的TransactionInfo值。

    下面让我们来看一下第二个问题,要回答这个问题,需要从TransactionInterceptor的invoke(MethodInvocation)方法看起,这个方法的代码如下所示:

    1. @Nullable
    2. protected Object invokeWithinTransaction(Method method, @Nullable Class targetClass,
    3. final InvocationCallback invocation) throws Throwable {
    4. // If the transaction attribute is null, the method is non-transactional.
    5. TransactionAttributeSource tas = getTransactionAttributeSource();
    6. final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
    7. final TransactionManager tm = determineTransactionManager(txAttr);
    8. // ……
    9. // 这里删除了一些无用代码
    10. PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
    11. final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
    12. if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager cpptm)) {
    13. // Standard transaction demarcation with getTransaction and commit/rollback calls.
    14. TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
    15. Object retVal;
    16. try {
    17. // This is an around advice: Invoke the next interceptor in the chain.
    18. // This will normally result in a target object being invoked.
    19. retVal = invocation.proceedWithInvocation();
    20. }
    21. catch (Throwable ex) {
    22. // target invocation exception
    23. completeTransactionAfterThrowing(txInfo, ex);
    24. throw ex;
    25. }
    26. finally {
    27. cleanupTransactionInfo(txInfo);
    28. }
    29. if (retVal != null && txAttr != null) {
    30. TransactionStatus status = txInfo.getTransactionStatus();
    31. if (status != null) {
    32. if (retVal instanceof Future future && future.isDone()) {
    33. try {
    34. future.get();
    35. }
    36. catch (ExecutionException ex) {
    37. if (txAttr.rollbackOn(ex.getCause())) {
    38. status.setRollbackOnly();
    39. }
    40. }
    41. catch (InterruptedException ex) {
    42. Thread.currentThread().interrupt();
    43. }
    44. }
    45. else if (vavrPresent && VavrDelegate.isVavrTry(retVal)) {
    46. // Set rollback-only in case of Vavr failure matching our rollback rules...
    47. retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
    48. }
    49. }
    50. }
    51. commitTransactionAfterReturning(txInfo);
    52. return retVal;
    53. }
    54. else {
    55. Object result;
    56. final ThrowableHolder throwableHolder = new ThrowableHolder();
    57. // It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
    58. try {
    59. result = cpptm.execute(txAttr, status -> {
    60. TransactionInfo txInfo = prepareTransactionInfo(ptm, txAttr, joinpointIdentification, status);
    61. try {
    62. Object retVal = invocation.proceedWithInvocation();
    63. if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {
    64. // Set rollback-only in case of Vavr failure matching our rollback rules...
    65. retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
    66. }
    67. return retVal;
    68. }
    69. catch (Throwable ex) {
    70. if (txAttr.rollbackOn(ex)) {
    71. // A RuntimeException: will lead to a rollback.
    72. if (ex instanceof RuntimeException runtimeException) {
    73. throw runtimeException;
    74. }
    75. else {
    76. throw new ThrowableHolderException(ex);
    77. }
    78. }
    79. else {
    80. // A normal return value: will lead to a commit.
    81. throwableHolder.throwable = ex;
    82. return null;
    83. }
    84. }
    85. finally {
    86. cleanupTransactionInfo(txInfo);
    87. }
    88. });
    89. }
    90. catch (ThrowableHolderException ex) {
    91. throw ex.getCause();
    92. }
    93. catch (TransactionSystemException ex2) {
    94. if (throwableHolder.throwable != null) {
    95. logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
    96. ex2.initApplicationException(throwableHolder.throwable);
    97. }
    98. throw ex2;
    99. }
    100. catch (Throwable ex2) {
    101. if (throwableHolder.throwable != null) {
    102. logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
    103. }
    104. throw ex2;
    105. }
    106. // Check result state: It might indicate a Throwable to rethrow.
    107. if (throwableHolder.throwable != null) {
    108. throw throwableHolder.throwable;
    109. }
    110. return result;
    111. }
    112. }

    当调用事务代理对象时,程序会经过判断后,走进if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager cpptm))分支。首先让我们考虑一下非事务嵌套的操作场景,if分支中的retVal = invocation.proceedWithInvocation();就表示业务代码执行完成了,后面就是释放资源(即调用cleanupTransactionInfo(txInfo)方法)、提交事务(则是指调用commitTransactionAfterReturning(txInfo)方法)等一系列常规操作。这样整个事务和业务处理逻辑就执行完成了。接着再来考虑一下事务嵌套的处理场景(即需要事务的业务处理代码中调用了另外一个需要事务的业务代码),这个时候第一次进入if分支的程序执行完retVal = invocation.proceedWithInvocation();后,会再次进入到if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager cpptm))分支,这个时候我们需要关注if分支中的TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification)这句,先来看一下这个被调用的方法(createTransactionIfNecessary())及其关联方法(prepareTransactionInfo())的源码:

    1. protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,
    2. @Nullable TransactionAttribute txAttr, final String joinpointIdentification) {
    3. // If no name specified, apply method identification as transaction name.
    4. if (txAttr != null && txAttr.getName() == null) {
    5. txAttr = new DelegatingTransactionAttribute(txAttr) {
    6. @Override
    7. public String getName() {
    8. return joinpointIdentification;
    9. }
    10. };
    11. }
    12. TransactionStatus status = null;
    13. if (txAttr != null) {
    14. if (tm != null) {
    15. status = tm.getTransaction(txAttr);
    16. }
    17. else {
    18. if (logger.isDebugEnabled()) {
    19. logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +
    20. "] because no transaction manager has been configured");
    21. }
    22. }
    23. }
    24. return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
    25. }
    26. //
    27. protected TransactionInfo prepareTransactionInfo(@Nullable PlatformTransactionManager tm,
    28. @Nullable TransactionAttribute txAttr, String joinpointIdentification,
    29. @Nullable TransactionStatus status) {
    30. TransactionInfo txInfo = new TransactionInfo(tm, txAttr, joinpointIdentification);
    31. if (txAttr != null) {
    32. // We need a transaction for this method...
    33. if (logger.isTraceEnabled()) {
    34. logger.trace("Getting transaction for [" + txInfo.getJoinpointIdentification() + "]");
    35. }
    36. // The transaction manager will flag an error if an incompatible tx already exists.
    37. txInfo.newTransactionStatus(status);
    38. }
    39. else {
    40. // The TransactionInfo.hasTransaction() method will return false. We created it only
    41. // to preserve the integrity of the ThreadLocal stack maintained in this class.
    42. if (logger.isTraceEnabled()) {
    43. logger.trace("No need to create transaction for [" + joinpointIdentification +
    44. "]: This method is not transactional.");
    45. }
    46. }
    47. // We always bind the TransactionInfo to the thread, even if we didn't create
    48. // a new transaction here. This guarantees that the TransactionInfo stack
    49. // will be managed correctly even if no transaction was created by this aspect.
    50. txInfo.bindToThread();
    51. return txInfo;
    52. }

    这时我们主要关注prepareTransactionInfo()方法中的txInfo.bindToThread(),关于这个被调用的方法的详情可以看参看前面的源码。这里再贴一下图片:

    从图中不难看出,这个方法会先从当前线程的ThreadLocal中拿出一个TransactionInfo对象,并将其赋值给TransactionInfo的oldTransactionInfo属性,然后将新的TransactionInfo对象重新赋值到当前线程的ThreadLocal中。接着就是就是继续调用retVal = invocation.proceedWithInvocation(),然后调用cleanupTransactionInfo(txInfo)方法,最后再调用commitTransactionAfterReturning(txInfo)方法。这里我们再啰嗦一下cleanupTransactionInfo ()方法,其源码如下图所示:

    1. protected void cleanupTransactionInfo(@Nullable TransactionInfo txInfo) {
    2. if (txInfo != null) {
    3. txInfo.restoreThreadLocalStatus();
    4. }
    5. }

    该方法最终调用的是TransactionInfo类中的restoreThreadLocalStatus()方法,其源码如下图所示:

    说白了就是将当前TransactionInfo对象中的oldTransactionInfo对象重新赋值到当前线程的ThreadLocal对象中,这个对象就是嵌套事务的上一层事务。至此我们就可以用自己的语言来回答前面的两个问题了!

    总的来看,将TransactionInfo绑定到当前线程的主要目的就是解决嵌套事务的。Spring解决嵌套的主要思路就是先将当前的TransactionInfo对象绑定到当前线程,当当前TransactionInfo对应的业务处理代码调用其他事务代理时,会将当前线程中保存的TransactionInfo对象赋值给新的TransactionInfo对象的oldTransactionInfo属性,然后将新的TransactionInfo对象重新绑定到当前线程的ThreadLocal上,这样就实现了前后两个事务的关联,并完成对事务传播行为的处理。下面这幅图展示了出现事务嵌套情况时的处理逻辑:

  • 相关阅读:
    MySQL-InnoDB索引详解
    无服务器的无状态性
    视频号创作分成计划揭秘!300+收益轻松拿,幻术视频成爆款!
    arm 汇编基础指令
    04【NIO核心组件之Buffer】
    docker创建mysql,以及mysql无法连接问题
    吃鸡达人必备!超实用干货激爽分享!
    Maven插件mybatis-generator,如何让生成的PO类的field上有对应表字段的注释
    AIGC生成式人工智能的概念和孵化关键里程碑
    传统电网转型新趋势:边缘计算引入智能电网
  • 原文地址:https://blog.csdn.net/java_lover20111106/article/details/136160012