Spring系列28:@Transactional事務源碼分析

来源:https://www.cnblogs.com/kongbubihai/archive/2022/03/31/16082281.html
-Advertisement-
Play Games

一、前言 眾所周知,Python 不是一種執行效率較高的語言。此外在任何語言中,迴圈都是一種非常消耗時間的操作。假如任意一種簡單的單步操作耗費的時間為 1 個單位,將此操作重覆執行上萬次,最終耗費的時間也將增長上萬倍。 while 和 for 是 Python 中常用的兩種實現迴圈的關鍵字,它們的運 ...


本文內容

  1. @Transactional事務使用

  2. @EnableTransactionManagement 詳解

  3. @Transactional事務屬性的解析

  4. TransactionInterceptor 事務控制

聲明式事務使用和原理

聲明式的主要步驟

  1. 使用@EnableTransactionManagement啟用Spring 事務管理支持
  2. 使用@Transactional標識需要事務的方法會自動開啟事務
  3. 註入數據源和事務管理器

下麵通過案例演示一下上面的效果。

案例

  1. 使用@EnableTransactionManagement啟用Spring 事務管理支持,配置類上需要有@Configuration註解

    @Configuration
    @ComponentScan
    @EnableTransactionManagement
    public class AppConfig {}
    
  2. 使用@Transactional標識需要事務的方法會自動開啟事務。addUser方法需要事務

    @Service
    public class UserService {
    
        @Autowired
        private JdbcTemplate jdbcTemplate;
    
        @Transactional
        public void addUser() {
            System.out.println("執行前記錄:" + jdbcTemplate.queryForList("SELECT * from t_user"));
            jdbcTemplate.update("insert into t_user (name) values (?)", "xx");
            jdbcTemplate.update("insert into t_user (name) values (?)", "oo");
            System.out.println("執行後記錄:" + jdbcTemplate.queryForList("SELECT * from t_user"));
        }
    }
    
  3. 註入數據源和事務管理器

    @Configuration
    @ComponentScan
    @EnableTransactionManagement
    public class AppConfig {
    
        /**
         * 定義一個數據源
         * @return
         */
        @Bean
        public DataSource dataSource() {
            DruidDataSource dataSource = new DruidDataSource();
            dataSource.setDriverClassName("");
            dataSource.setDriverClassName("com.mysql.jdbc.Driver");
            dataSource.setUrl("jdbc:mysql://192.168.25.24:3306/xxx?characterEncoding=UTF-8");
            dataSource.setUsername("root");
            dataSource.setPassword("xxx");
            return dataSource;
        }
    
        /**
         * 定義一個JdbcTemplate來執行sql
         * @param dataSource
         * @return
         */
        @Bean
        public JdbcTemplate jdbcTemplate(DataSource dataSource) {
            return new JdbcTemplate(dataSource);
        }
    
        /**
         * 定義一個管理器
         * @param dataSource
         * @return
         */
        @Bean
        public PlatformTransactionManager transactionManager(DataSource dataSource) {
            return new DataSourceTransactionManager(dataSource);
        }
    }
    
  4. 測試程式

    public class DeclarativeTest {
        public static void main(String[] args) {
            AnnotationConfigApplicationContext context =
                    new AnnotationConfigApplicationContext(AppConfig.class);
            UserService userService = context.getBean(UserService.class);
            userService.addUser();
            context.close();
        }
    }
    

    輸出結果如下:

    執行前記錄:[{id=1, name=test1-1}, {id=2, name=test1-2}, {id=3, name=xx}, {id=4, name=oo}, {id=5, name=xx}, {id=6, name=oo}]
    執行後記錄:[{id=1, name=test1-1}, {id=2, name=test1-2}, {id=3, name=xx}, {id=4, name=oo}, {id=5, name=xx}, {id=6, name=oo}, {id=7, name=xx}, {id=8, name=oo}]	
    

原理

@EnableTransactionManagement註解會開啟Spring自動管理事務的功能。開啟之後在Spring容器啟動的過程中,會攔截所有bean的創建過程,判斷bean 是否需要讓Spring來管理事務,如果需要那麼通過aop的方式創建代理對象。代理中會添加一個攔截器TransactionInterceptor,攔截@Trasaction標識方法的執行,在方法執行前後添加事務的功能。

下麵進行源碼分析,需要用到的前置只是是Spring Aop相關知識和編程式事務管理的知識,前面的文章有涉及,提前看一下。

@EnableTransactionManagement 詳解

@EnableTransactionManagement 會開啟Spring的事務管理功能,查看下源碼。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {

   /**
    * 指示是否創建基於子類(CGLIB)的代理(true),而不是標準的基於Java介面的代理(false)。預設為false。
    * 僅當mode()設置為AdviceMode.PROXY時適用。
    */
   boolean proxyTargetClass() default false;

   /**
    * 指示應該如何應用事務通知。 預設值是AdviceMode.PROXY。
    * 請註意,代理模式只允許通過代理攔截調用。同一類內的本地調用不會被攔截;
    * 在本地調用中,對這種方法的Transactional註釋將被忽略,因為Spring的攔截器甚至不會在這樣的運行時場景中起作用。
    */
   AdviceMode mode() default AdviceMode.PROXY;

   /**
    * 當在特定連接點上應用多個通知時,指示事務顧問程式的執行順序。 預設值是Ordered.LOWEST_PRECEDENCE
    */
   int order() default Ordered.LOWEST_PRECEDENCE;

}

3個參數屬性值

  • proxyTargetClass : 指示是否創建基於子類(CGLIB)的代理(true),而不是標準的基於Java介面的代理(false)
  • mode:指示應該如何應用事務通知。 預設值是AdviceMode.PROXY。
  • order: 當在特定連接點上應用多個通知時,指示事務顧問程式的執行順序。 預設值是Ordered.LOWEST_PRECEDENCE,最後處理事務攔截器。

TransactionManagementConfigurationSelector

重點是@Import(TransactionManagementConfigurationSelector.class),註入一些事務相關的bean到Spring容器中進行事務的管理控制。

根據EnableTransactionManagement的mode值選擇應該使用AbstractTransactionManagementConfiguration的哪個實現。

package org.springframework.transaction.annotation;

public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> {

   /**
    * 此處是AdviceMode的作用,預設是用代理,另外一個是ASPECTJ
    */
   @Override
   protected String[] selectImports(AdviceMode adviceMode) {
      switch (adviceMode) {
         case PROXY:
             // @1
            return new String[] {AutoProxyRegistrar.class.getName(),
                  ProxyTransactionManagementConfiguration.class.getName()};
         case ASPECTJ:
            return new String[] {determineTransactionAspectClass()};
         default:
            return null;
      }
   }

   private String determineTransactionAspectClass() {
      return (ClassUtils.isPresent("javax.transaction.Transactional", getClass().getClassLoader()) ?
            TransactionManagementConfigUtils.JTA_TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME :
            TransactionManagementConfigUtils.TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME);
   }

}

我們的mode是 AdviceMode.PROXY ,所以走@1位置,註入AutoProxyRegistrarProxyTransactionManagementConfiguration

AutoProxyRegistrar註入 InfrastructureAdvisorAutoProxyCreator

AutoProxyRegistrar的作用是註入一個InfrastructureAdvisorAutoProxyCreator,用於攔截bean的創建過程,為需要的事務控制的bean 創建代理對象,這個類非常關鍵,後面詳細講。

public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
   public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
      boolean candidateFound = false;
      Set<String> annTypes = importingClassMetadata.getAnnotationTypes();
      // 遍歷所有註解,找到有mode和proxyTargetClass的註解
      for (String annType : annTypes) {
         AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annType);
         if (candidate == null) {
            continue;
         }
         Object mode = candidate.get("mode");
         Object proxyTargetClass = candidate.get("proxyTargetClass");
         if (mode != null && proxyTargetClass != null && AdviceMode.class == mode.getClass() &&
               Boolean.class == proxyTargetClass.getClass()) {
            candidateFound = true;
            if (mode == AdviceMode.PROXY) {
               // 註冊aop InfrastructureAdvisorAutoProxyCreator 不展開
               AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
               // 強制設置proxyTargetClass=true後面使用cglib
               if ((Boolean) proxyTargetClass) {
                  AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
                  return;
               }
            }
         }
      }
   }

}

ProxyTransactionManagementConfiguration

@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {

   @Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
   @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
   public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(
         TransactionAttributeSource transactionAttributeSource, TransactionInterceptor transactionInterceptor) {

      BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
      advisor.setTransactionAttributeSource(transactionAttributeSource);
      advisor.setAdvice(transactionInterceptor);
      if (this.enableTx != null) {
         advisor.setOrder(this.enableTx.<Integer>getNumber("order"));
      }
      return advisor;
   }

   @Bean
   @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
   public TransactionAttributeSource transactionAttributeSource() {
      return new AnnotationTransactionAttributeSource();
   }

   @Bean
   @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
   public TransactionInterceptor transactionInterceptor(TransactionAttributeSource transactionAttributeSource) {
      TransactionInterceptor interceptor = new TransactionInterceptor();
      interceptor.setTransactionAttributeSource(transactionAttributeSource);
      if (this.txManager != null) {
         interceptor.setTransactionManager(this.txManager);
      }
      return interceptor;
   }

}

ProxyTransactionManagementConfiguration 代理事務配置,註冊事務需要用的一些類,而且Role=ROLE_INFRASTRUCTURE都是屬於內部級別的,如下:

  • BeanFactoryTransactionAttributeSourceAdvisor 事務屬性通知器,存放事務註解的方法相關的屬性
  • TransactionAttributeSource事務屬性源,就是事務註解的一些屬性,也用來解析事務註解屬性,實際是AnnotationTransactionAttributeSource
  • TransactionInterceptor事務攔截器,該類包含與Spring底層事務API的集成。TransactionInterceptor簡單地以正確的順序調用相關的超類方法,比如invokeWithinTransaction。這個類非常關鍵,負責事務相關的AOP增強的。

小結

EnableTransactionManagement註解的作用主要註入了InfrastructureAdvisorAutoProxyCreator負責攔截bean的創建過程為特定的bean創建代理對象,並通過TransactionInterceptor事務攔截器來實現方法的事務控制。

@Transactional 詳解

該註解用於描述單個方法或類上的事務屬性。在類級別,該註釋作為預設值應用於聲明類及其子類的所有方法。註意,類級別它並不適用於類層次結構上的父類,也就是父類方法需要在本地重新聲明,以便參與子類級別的註釋。

註解的屬性的語義的具體信息,由 TransactionDefinitionTransactionAttribute 提供。

package org.springframework.transaction.annotation;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {

   @AliasFor("transactionManager")
   String value() default "";

   /**
    * 用來確定目標事務管理器bean
    */
   @AliasFor("value")
   String transactionManager() default "";

   /**
    * 事務傳播類型
    */
   Propagation propagation() default Propagation.REQUIRED;

   /**
    * 事務隔離級別
    */
   Isolation isolation() default Isolation.DEFAULT;

   /**
    * 事務超時
    */
   int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;

   /**
    * 只讀事務
    */
   boolean readOnly() default false;

   /**
    * 指定哪些異常類型必須導致事務回滾,指定Throwable的子類型;預設只回滾RuntimeException和Error
    */
   Class<? extends Throwable>[] rollbackFor() default {};

   /**
    * 指示哪些異常類型必須導致事務回滾,這裡異常類名稱
    */
   String[] rollbackForClassName() default {};

   /**
    * 指定哪些異常不進行回滾
    */
   Class<? extends Throwable>[] noRollbackFor() default {};

   /**
    * 指定哪些異常類型不進行回滾,異常類型名稱
    */
   String[] noRollbackForClassName() default {};

}

@Transactional註解如何解析成事務屬性

AnnotationTransactionAttributeSource類

image-20220217100026551

從類圖看我們關註AnnotationTransactionAttributeSource通過SpringTransactionAnnotationParser@Transcation轉成事務屬性供Spring事務處理使用。

  1. 如果@Transcation註解配置了屬性,轉換成RuleBasedTransactionAttribute
  2. 如果@Transcation註解沒有配置屬性,轉換成DefaultTransactionAttribute,只有在拋出RuntimeExceptionError時候才回滾

按照Spring源碼設計設計的一般套路我們看下右側的TransactionAttributeSource 介面和抽象類AbstractFallbackTransactionAttributeSource

TransactionAttributeSource 介面

public interface TransactionAttributeSource {

   /**確定給定的類是否是TransactionAttributeSource元數據格式的事務屬性的候選類*/
   default boolean isCandidateClass(Class<?> targetClass) {
      return true;
   }

   /**解析給定方法的@Transaction事務屬性,如果方法是非事務性的,則返回null*/
   @Nullable
   TransactionAttribute getTransactionAttribute(Method method, @Nullable Class<?> targetClass);

}

AbstractFallbackTransactionAttributeSource類

先看getTransactionAttribute()方法

public TransactionAttribute getTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
   // 判斷method所在的class是不是Object類型
   if (method.getDeclaringClass() == Object.class) {
      return null;
   }

   // First, see if we have a cached value.
   // 構建緩存key
   Object cacheKey = getCacheKey(method, targetClass);
   // 從緩存中獲取 @1
   TransactionAttribute cached = this.attributeCache.get(cacheKey);
   // 有緩存,不會每次computeTransactionAttribute
   if (cached != null) {
      // Value will either be canonical value indicating there is no transaction attribute,
      // or an actual transaction attribute.
      // 判斷緩存中的對象是不是空事務屬性的對象
      if (cached == NULL_TRANSACTION_ATTRIBUTE) {
         return null;
      }
      else {
         // 存在就直接返回事務屬性
         return cached;
      }
   }
   else {
      // We need to work it out.
      // 查找我們的事務註解
      TransactionAttribute txAttr = computeTransactionAttribute(method, targetClass);
      // Put it in the cache.
      // 若解析出來的事務註解屬性為空
      if (txAttr == null) {
         // 往緩存中存放空事務註解屬性
         this.attributeCache.put(cacheKey, NULL_TRANSACTION_ATTRIBUTE);
      }
      else {
         // 我們執行方法的描述符:包名+類名+方法名
         String methodIdentification = ClassUtils.getQualifiedMethodName(method, targetClass);
         // 把方法描述設置到事務屬性上去
         if (txAttr instanceof DefaultTransactionAttribute) {
            ((DefaultTransactionAttribute) txAttr).setDescriptor(methodIdentification);
         }
         if (logger.isTraceEnabled()) {
            logger.trace("Adding transactional method '" + methodIdentification + "' with attribute: " + txAttr);
         }
         // 加入緩存
         this.attributeCache.put(cacheKey, txAttr);
      }
      return txAttr;
   }
}

緩存中有類對應方法的事務屬性就直接返回,沒有就先解析@1再緩存起來。

computeTransactionAttribute方法

protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
   // Don't allow no-public methods as required.
   // 首先判斷方法是否是public,預設是支持public的
   if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
      return null;
   }

   // The method may be on an interface, but we need attributes from the target class.
   // If the target class is null, the method will be unchanged.
   // method代表介面中的方法,specificMethod代表實現類中的方法
   Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);

   // First try is the method in the target class.
   // 優先方法上解析的事務註解的屬性,會去找父類或者介面的方法
   TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
   if (txAttr != null) {
      return txAttr;
   }

   // Second try is the transaction attribute on the target class.
   // 如果沒有,再嘗試聲明該方法的類上註解屬性,會去父類或者介面找
   txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
   if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
      return txAttr;
   }

   // 如果指定方法不等於方法
   if (specificMethod != method) {
      // Fallback is to look at the original method.
      // 查找介面方法
      txAttr = findTransactionAttribute(method);
      if (txAttr != null) {
         return txAttr;
      }
      // Last fallback is the class of the original method.
      // 到介面中的類中去尋找
      txAttr = findTransactionAttribute(method.getDeclaringClass());
      if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
         return txAttr;
      }
   }

   return null;
}

查找事務屬性的查找順序如下:

  1. 特定的目標方法,會去找父類或者介面的方法
  2. 目標類,會去找父類或者介面
  3. 聲明的方法
  4. 聲明方法所在的類

AnnotationTransactionAttributeSource#determineTransactionAttribute() 方法委托給SpringTransactionAnnotationParser解析給定類或是方法上的@Transactional註解的事務屬性

protected TransactionAttribute determineTransactionAttribute(AnnotatedElement element) {
   // 獲取我們的註解解析器
   for (TransactionAnnotationParser parser : this.annotationParsers) {
      // 通過註解解析器去解析我們的元素(方法或者類)上的註解
      TransactionAttribute attr = parser.parseTransactionAnnotation(element);
      if (attr != null) {
         return attr;
      }
   }
   return null;
}

接下來看下是如何解析和包裝的SpringTransactionAnnotationParser#parseTransactionAnnotation()方法。

public TransactionAttribute parseTransactionAnnotation(AnnotatedElement element) {
   // 從element對象中獲取@Transactional註解,然後把註解屬性封裝到了AnnotationAttributes
   AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(
         element, Transactional.class, false, false);
   if (attributes != null) {
      // 解析出真正的事務屬性對象
      return parseTransactionAnnotation(attributes);
   }
   else {
      return null;
   }
}

分析2個點:

  • AnnotatedElementUtils.findMergedAnnotationAttributes()負責解析目標類或目標方法上的@Transactional,會向上找父類或是介面的
  • parseTransactionAnnotation()方法包裝成TransactionAttribute

看下是如何包裝轉換的。

protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) {

   // 創建一個基礎規則的事務屬性對象
   RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();

   // 解析@Transactionl上的傳播行為
   Propagation propagation = attributes.getEnum("propagation");
   rbta.setPropagationBehavior(propagation.value());
   // 解析@Transactionl上的隔離級別
   Isolation isolation = attributes.getEnum("isolation");
   rbta.setIsolationLevel(isolation.value());
   // 解析@Transactionl上的事務超時事件
   rbta.setTimeout(attributes.getNumber("timeout").intValue());
   // 解析readOnly
   rbta.setReadOnly(attributes.getBoolean("readOnly"));
   // 解析@Transactionl上的事務管理器的名稱
   rbta.setQualifier(attributes.getString("value"));

   List<RollbackRuleAttribute> rollbackRules = new ArrayList<>();
   // 解析針對哪種異常回滾
   for (Class<?> rbRule : attributes.getClassArray("rollbackFor")) {
      rollbackRules.add(new RollbackRuleAttribute(rbRule));
   }
   // 對哪種異常進行回滾
   for (String rbRule : attributes.getStringArray("rollbackForClassName")) {
      rollbackRules.add(new RollbackRuleAttribute(rbRule));
   }
   // 對哪種異常不回滾
   for (Class<?> rbRule : attributes.getClassArray("noRollbackFor")) {
      rollbackRules.add(new NoRollbackRuleAttribute(rbRule));
   }
   // 對哪種類型不回滾
   for (String rbRule : attributes.getStringArray("noRollbackForClassName")) {
      rollbackRules.add(new NoRollbackRuleAttribute(rbRule));
   }
   rbta.setRollbackRules(rollbackRules);

   return rbta;
}

至此,@Transactional是如何變成RuleBasedTransactionAttribute已經很清晰了。

如何自動生成代理對象

這部分和之前的聲明式AOP的源碼分析是一樣的過程,通過類圖過一下。

image-20220217105749202

  1. 誰負責創建代理?

    InfrastructureAdvisorAutoProxyCreator繼承我們熟悉的AbstractAdvisorAutoProxyCreator類,是個BeanPostProcessor,在Spring容器啟動的過程中,會攔截bean的創建過程,為需要事務支持的bean生成代理對象。

  2. 誰負責判斷bean是否需要代理?

    BeanFactoryTransactionAttributeSourceAdvisor是個Advisor,組合了切點和通知。哪些bean需要代理滿足增強由切點TransactionAttributeSourcePointcut來通過TransactionAttributeSource來判定bean的類或是方法上是否有@Transactional註解。

  3. 誰負責實際的事務增強工作?

    TransactionInterceptor 繼承MethodInterceptor是個攔截器,負責攔截代理對象目標方法,在前後增加事務控制的邏輯。這個類下麵進行詳細分析。

TransactionInterceptor 如何進行事務控制

TransactionInterceptor類

Spring中聲明式事務時通過AOP的方式實現的,事務方法的執行最終都會由TransactionInterceptor invoke()攔截增強的。

package org.springframework.transaction.interceptor;
public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable {
   public Object invoke(MethodInvocation invocation) throws Throwable {
      // Work out the target class: may be {@code null}.
      // The TransactionAttributeSource should be passed the target class
      // as well as the method, which may be from an interface.
      // 獲取我們的代理對象的class屬性
      Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);

      // Adapt to TransactionAspectSupport's invokeWithinTransaction...
      /**
       * 以事務的方式調用目標方法
       * 在這埋了一個鉤子函數 用來回調目標方法的
       */
      return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
   }

事務的實現是委托給TransactionAspectSupport父類實現的。

TransactionAspectSupport類

invokeWithinTransaction() 方法

基於環繞通知的實現事務控制,委托給該類上的其他幾個模板方法,其實裡面主要內容就是編程式的事務控制了。這是個模板方法,主要功能點如下:

  • 如何獲取事務管理器對象
  • 通過事務管理器開啟事務
  • 執行目標方法
  • 方法異常如何完成事務
  • 正常返回如何完成事務提交
@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
      final InvocationCallback invocation) throws Throwable {
   // 獲取我們的事務屬性源對象
   TransactionAttributeSource tas = getTransactionAttributeSource();
   // 通過事務屬性源對象獲取到當前方法的事務屬性信息
   final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
   // @1獲取我們配置的事務管理器對象
   final TransactionManager tm = determineTransactionManager(txAttr);
   PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
   // 獲取連接點的唯一標識  類名+方法名
   final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

   // 聲明式事務處理
   if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
      // @2創建TransactionInfo
      TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);

      Object retVal;
      try {
         // 執行被增強方法,調用具體的處理邏輯
         retVal = invocation.proceedWithInvocation();
      }
      catch (Throwable ex) {
         //@3 異常回滾 如何走?可能只需提交,也可能只需回滾,這個取決於事務的配置
         completeTransactionAfterThrowing(txInfo, ex);
         throw ex;
      }
      finally {
         //清除事務信息,恢複線程私有的老的事務信息
         cleanupTransactionInfo(txInfo);
      }

      //成功後提交,會進行資源儲量,連接釋放,恢復掛起事務等操作
      commitTransactionAfterReturning(txInfo);
      return retVal;
   }
}
determineTransactionManager() 事務管理器獲取

查找和獲取的順序是:

  1. 先看@Transactional中是否通過value或者transactionManager指定了事務管理器
  2. TransactionInterceptor.transactionManagerBeanName是否有值,如果有,將通過這個值查找事務管理器
  3. TransactionInterceptor.transactionManager是否有值,如果有則返回,這個是通過容器TransactionManagementConfigurer介面設置到TransactionInterceptor中的
  4. 如果上面3種都沒有,將從Spring容器中查找TransactionManager類型的作為預設事務管理器
protected TransactionManager determineTransactionManager(@Nullable TransactionAttribute txAttr) {
   // Do not attempt to lookup tx manager if no tx attributes are set
   // txAttr == null || this.beanFactory == null ,返回攔截器中配置的事務管理器
   if (txAttr == null || this.beanFactory == null) {
      return getTransactionManager();
   }

   //qualifier就是@Transactional註解中通過value或者transactionManager來指定事務管理器的bean名稱
   String qualifier = txAttr.getQualifier();
   if (StringUtils.hasText(qualifier)) {
      //從spring容器中查找[beanName:qualifier,type:TransactionManager]的bean
      return determineQualifiedTransactionManager(this.beanFactory, qualifier);
   }
   else if (StringUtils.hasText(this.transactionManagerBeanName)) {
      //從spring容器中查找[beanName:this.transactionManagerBeanName,type:TransactionManager]的bean
      return determineQualifiedTransactionManager(this.beanFactory, this.transactionManagerBeanName);
   }
   else {
      //最後通過類型TransactionManager在spring容器中找事務管理器
      TransactionManager defaultTransactionManager = getTransactionManager();
      if (defaultTransactionManager == null) {
         defaultTransactionManager = this.transactionManagerCache.get(DEFAULT_TRANSACTION_MANAGER_KEY);
         if (defaultTransactionManager == null) {
            defaultTransactionManager = this.beanFactory.getBean(TransactionManager.class);
            this.transactionManagerCache.putIfAbsent(
                  DEFAULT_TRANSACTION_MANAGER_KEY, defaultTransactionManager);
         }
      }
      return defaultTransactionManager;
   }
}
createTransactionIfNecessary() 創建並開啟事務
protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,
      @Nullable TransactionAttribute txAttr, final String joinpointIdentification) {
   // 如果沒有名稱指定則使用方法唯一標識,並使用DelegatingTransactionAttribute封裝txAttr
   if (txAttr != null && txAttr.getName() == null) {
      txAttr = new DelegatingTransactionAttribute(txAttr) {
         @Override
         public String getName() {
            return joinpointIdentification;
         }
      };
   }

   TransactionStatus status = null;
   if (txAttr != null) {
      if (tm != null) {
         // @1獲取TransactionStatus事務狀態信息
         status = tm.getTransaction(txAttr);
      }
   }
   // @2根據指定的屬性與status準備一個TransactionInfo,
   return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}

分析如下:

  • @1獲取TransactionStatus事務狀態信息,也就是是編程式事務的創建和開啟。
  • @2TransactionInfo生成

prepareTransactionInfo() 方法創建事務信息並綁定到當前線程

protected TransactionInfo prepareTransactionInfo(@Nullable PlatformTransactionManager tm,
      @Nullable TransactionAttribute txAttr, String joinpointIdentification,
      @Nullable TransactionStatus status) {

   // 創建事務信息
   TransactionInfo txInfo = new TransactionInfo(tm, txAttr, joinpointIdentification);
   if (txAttr != null) {
      // The transaction manager will flag an error if an incompatible tx already exists.
      // 設置新事務狀態
      txInfo.newTransactionStatus(status);
   }

   // 事務信息綁定到當前線程
   txInfo.bindToThread();
   return txInfo;
}

事務信息是有哪些內容?簡單過一下內部類TransactionInfo

/**
 * 用於保存事務信息的不透明對象。子類必須將其傳遞迴該類的方法,但不能看到其內部
 */
protected static final class TransactionInfo {
       /** 事務管理器 */
   @Nullable
   private final PlatformTransactionManager transactionManager;
    /** 事務屬性 */
   @Nullable
   private final TransactionAttribute transactionAttribute;
    /** 切點標識名 */
   private final String joinpointIdentification;
    /** 事務狀態 */
   @Nullable
   private TransactionStatus transactionStatus;
       /** 舊的事務信息 */
   @Nullable
   private TransactionInfo oldTransactionInfo;
    
    /**
		 * 綁定新事務到當前線程,舊的會被保存
		 */
    private void bindToThread() {
        this.oldTransactionInfo = transactionInfoHolder.get();
        transactionInfoHolder.set(this);
    }

    /**
		 * 恢複線程中舊事務信息
		 */
    private void restoreThreadLocalStatus() {
        transactionInfoHolder.set(this.oldTransactionInfo);
    }    

}
completeTransactionAfterThrowing() 異常後完成事務
/**
 * 如果支持回滾的話就進行回滾,否則就處理提交,提交裡面如果TransactionStatus.isRollbackOnly()=true的話也會進行回滾處理
 */
protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
   if (txInfo != null && txInfo.getTransactionStatus() != null) {
      if (logger.isTraceEnabled()) {
         logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
               "] after exception: " + ex);
      }
      // @1判斷事務是否需要回滾
      if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
         try {
            // 進行回滾
            txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
         }
      }
      else {
         // We don't roll back on this exception.
         // Will still roll back if TransactionStatus.isRollbackOnly() is true.
         try {
            // @2通過事務管理器提交事務
            txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
         }
      }
   }
}

異常後如果匹配上我們@Transaction指定的異常類型,在調用事務管理器進行事務回滾,否則通過事務管理器進行提交事務。

commitTransactionAfterReturning 正常完成事務

通過事務管理器進行事務提交。

protected void commitTransactionAfterReturning(@Nullable TransactionInfo txInfo) {
   if (txInfo != null && txInfo.getTransactionStatus() != null) {
      txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
   }
}
小結

TransactionInterceptor 對事務控制包括開啟、提交、回滾等操作,其實都是通過事務管理器進行的,這和編程式事務管理是一樣的。

總結

本文進行了Spring中@Transactional聲明事務的源碼解析,結合了聲明式AOP的源碼分析和編程式事務管理的源碼分析。總結下過程是就是通過BeanPostProcessor攔截bean創建過程自動創建代理對象,通過TransactionInterceptor 環繞通知增強目標方法,在目標方法執行前後增加事務的控制邏輯。

知識分享,轉載請註明出處。學無先後,達者為先!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 觀察者模式又叫做發佈-訂閱模式,屬於行為型模式;觀察者模式通過定義一種一對多得依賴關係,讓多個觀察者對象同時監聽某一個主題對象,這個主題對象在狀態上發生變化時,會通知所有觀察者對象,使他們能夠自動更新自己。 觀察者模式的UML類圖如下: 如上圖所示,觀察者模式主要涉及到抽象主題角色、具體主題角色、抽 ...
  • 網路開發兩大架構 早期數據交互的格式是沒有網路的兩個文件之間的數據交互需要通過第三個文件a,b文件同時和c文件交互,a文件把數據先存放c文件中b文件從c文件取,反之亦然socket (套接字) 是一個收發數據的工具 有了網路之後a文件中的數據,通過網路協議,轉化101010...二進位進行發送a文件 ...
  • **Spring淪陷了!**這樣的標題這幾天是不是看膩了?然而,仔細看看都是拿著之前的幾個毫不相干的CVE來大吹特吹。所以,昨天發了一篇關於最近網傳的Spring大漏洞的文章,聊了聊這些讓人迷惑的營銷文、以及提醒大家不要去下載一些利用漏洞提供補丁的釣魚內容。而對於這個網傳的漏洞,依然保持關註狀態,因 ...
  • 博客推行版本更新,成果積累制度,已經寫過的博客還會再次更新,不斷地琢磨,高質量高數量都是要追求的,工匠精神是學習必不可少的精神。因此,大家有何建議歡迎在評論區踴躍發言,你們的支持是我最大的動力,你們敢投,我就敢肝 ...
  • 函數的嵌套有兩種方式: 交叉嵌套 迴環嵌套 交叉嵌套 交叉嵌套的方式是在本函數中調用同一級或上一級函數的嵌套方法: def func(foo): print(1) foo() print(3) def a(): print(1) b = func(a) print(b) 輸出的結果為: 1 1 3 ...
  • redis的基本命令學習 1.簡單理解redis 基於記憶體的key-value資料庫基於c語言編寫的,可以支持多種語言的api //set每秒11萬次,取get 81000次支持數據持久化value可以是string,hash, list, set, sorted set 使用場景: 去最新n個數據 ...
  • 前言 首先描述下業務場景,有一個介面,其中存在耗時操作,耗時操作的執行結果將寫入數據表中,不需要通過該介面直接返回。 因此理論上該介面可以在耗時操作執行結束前返回。本文中使用多線程後臺運行耗時操作,主線程提前返回,實現介面的提前返回。 此外,還嘗試使用協程實現,經驗證,協程適用於多任務併發處理,遇到 ...
  • 簡介 Apollo(阿波羅)是攜程框架部門研發的分散式配置中心,能夠集中化管理應用不同環境、不同集群的配置,配置修改後能夠實時推送到應用端,並且具備規範的許可權、流程治理等特性,適用於微服務配置管理場景。 服務端基於Spring Boot和Spring Cloud開發,打包後可以直接運行,不需要額外安 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...