十一、SpringBoot事務(wù)管理@Transactional注解原理

1友存、依賴包

1.1祷膳、 SpringBoot中的依賴包

眾所周知,在SpringBoot中凡是需要跟數(shù)據(jù)庫打交道的屡立,基本上都要顯式或者隱式添加jdbc的依賴:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

1.2直晨、 Spring中的依賴包

在講SpringBoot中的事務(wù)管理之前,先來講下Spring的事務(wù)管理膨俐。在Spring項目中勇皇,加入的是spring-jdbc依賴:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
</dependency>

1.2.1、配置版事務(wù)

在使用配置文件的方式中焚刺,通常會在Spring的配置文件中配置事務(wù)管理器敛摘,并注入數(shù)據(jù)源:

<!-- 注冊數(shù)據(jù)源 -->
<bean id="dataSource" class="...">
    <property name="" value=""/>
</bean>

<!-- 注冊事務(wù)管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>

<!-- 開啟事務(wù)注解 -->
<tx:annotation-driven transaction-manager="txManager" />

接下來可以直接在業(yè)務(wù)層Service的方法上或者類上添加@Transactional

1.2.2乳愉、注解版事務(wù)

首先需要注冊兩個Bean兄淫,分別對應(yīng)上面Spring配置文件中的兩個Bean:

@Configuration
public class TxConfig {

    @Bean
    public DataSource dataSource() {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser("...");
        dataSource.setPassword("...");
        dataSource.setDriverClass("...");
        dataSource.setJdbcUrl("...");
        return dataSource;
    }
  
    @Bean
    public PlatformTransactionManager platformTransactionManager() {
        return new DataSourceTransactionManager(dataSource());//放入數(shù)據(jù)源
    }
}

PlatformTransactionManager這個Bean非常重要,要使用事務(wù)管理蔓姚,就必須要在IOC容器中注冊一個事務(wù)管理器捕虽,而在使用@Transactional注解的時候,默認會從IOC容器中查找對應(yīng)的事務(wù)管理器坡脐。

一般人都會認為到這已經(jīng)結(jié)束了泄私,可以正常使用@Transactional注解了,正常則commit备闲,異常則rollback晌端。各位可以簡單嘗試在Service層的方法中手動制造異常,看看事務(wù)是否會回滾恬砂?此處留給大家自行嘗試...

其實到這咧纠,事務(wù)在遇到異常后,還是沒有正承褐瑁回滾惧盹,為什么呢乳幸?缺少一個注解@EnableTransactionManagement,該注解的功能是開啟基于注解的事務(wù)管理功能钧椰,需要在配置類上添加該注解即可,這樣@Transactional的事務(wù)提交和回滾就會生效符欠。

@EnableTransactionManagement//重要
@Configuration
public class TxConfig {

    @Bean
    public DataSource dataSource() {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser("...");
        dataSource.setPassword("...");
        dataSource.setDriverClass("...");
        dataSource.setJdbcUrl("...");
        return dataSource;
    }
  
    //重要
    @Bean
    public PlatformTransactionManager platformTransactionManager() {
        return new DataSourceTransactionManager(dataSource());//放入數(shù)據(jù)源
    }
}

2嫡霞、@Transactional原理

這里咱們再回到SpringBoot的事務(wù)管理。在大多數(shù)SpringBoot項目中希柿,簡單地只要在配置類或者主類上添加@EnableTransactionManagement诊沪,并在業(yè)務(wù)層Service上添加@Transactional即可實現(xiàn)事務(wù)的提交和回滾。

因為在依賴jdbc或者jpa之后曾撤,會自動配置TransactionManager端姚。

  • 依賴jdbc會自動配置DataSourceTransactionManager
@Configuration
@ConditionalOnClass({ JdbcTemplate.class, PlatformTransactionManager.class })
@AutoConfigureOrder(Ordered.LOWEST_PRECEDENCE)
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceTransactionManagerAutoConfiguration {

    @Configuration
    @ConditionalOnSingleCandidate(DataSource.class)
    static class DataSourceTransactionManagerConfiguration {

        private final DataSource dataSource;

        private final TransactionManagerCustomizers transactionManagerCustomizers;

        DataSourceTransactionManagerConfiguration(DataSource dataSource,
                ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
            this.dataSource = dataSource;
            this.transactionManagerCustomizers = transactionManagerCustomizers
                    .getIfAvailable();
        }

        @Bean
        @ConditionalOnMissingBean(PlatformTransactionManager.class)
        public DataSourceTransactionManager transactionManager(
                DataSourceProperties properties) {
            DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(
                    this.dataSource);
            if (this.transactionManagerCustomizers != null) {
                this.transactionManagerCustomizers.customize(transactionManager);
            }
            return transactionManager;
        }

    }

}
  • 依賴jpa會自動配置JpaTransactionManager:
@EnableConfigurationProperties(JpaProperties.class)
@Import(DataSourceInitializedPublisher.Registrar.class)
public abstract class JpaBaseConfiguration implements BeanFactoryAware {
    
    //other code...
    
    @Bean
    @ConditionalOnMissingBean(PlatformTransactionManager.class)
    public PlatformTransactionManager transactionManager() {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        if (this.transactionManagerCustomizers != null) {
            this.transactionManagerCustomizers.customize(transactionManager);
        }
        return transactionManager;
    }
}

2.1、@EnableTransactionManagement

具體原理還要從注解@EnableTransactionManagement說起:

@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
    boolean proxyTargetClass() default false;
    
    AdviceMode mode() default AdviceMode.PROXY;
    
    int order() default Ordered.LOWEST_PRECEDENCE;
}

@EnableTransactionManagement簽名上可以看到挤悉,它導(dǎo)入了TransactionManagementConfigurationSelector類渐裸,其作用就是利用該類想容器中導(dǎo)入組件。而TransactionManagementConfigurationSelector主要向容器中導(dǎo)入了兩個組件装悲,分別是AutoProxyRegistrarProxyTransactionManagementConfiguration

public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> {

    /**
     * {@inheritDoc}
     * @return {@link ProxyTransactionManagementConfiguration} or
     * {@code AspectJTransactionManagementConfiguration} for {@code PROXY} and
     * {@code ASPECTJ} values of {@link EnableTransactionManagement#mode()}, respectively
     */
    @Override
    protected String[] selectImports(AdviceMode adviceMode) {
        switch (adviceMode) {
            case PROXY:
                return new String[] {AutoProxyRegistrar.class.getName(), ProxyTransactionManagementConfiguration.class.getName()};
            case ASPECTJ:
                return new String[] {TransactionManagementConfigUtils.TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME};
            default:
                return null;
        }
    }
}

2.2昏鹃、AutoProxyRegistrar

咱們先來看看AutoProxyRegistrar做了哪些事?

public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        boolean candidateFound = false;
        Set<String> annoTypes = importingClassMetadata.getAnnotationTypes();
        for (String annoType : annoTypes) {
            AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annoType);
            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) {
                    //注冊自動代理創(chuàng)建器
                    AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
                    if ((Boolean) proxyTargetClass) {
                        AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
                        return;
                    }
                }
            }
        }
        //other code...
    }

}

由于默認情況下mode為AdviceMode.PROXY诀诊,所以會通過AopConfigUtils.registerAutoProxyCreatorIfNecessary方法向容器中注冊自動代理創(chuàng)建器:

//AopConfigUtils工具類

public static final String AUTO_PROXY_CREATOR_BEAN_NAME =
            "org.springframework.aop.config.internalAutoProxyCreator";

public static BeanDefinition registerAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry) {
    return registerAutoProxyCreatorIfNecessary(registry, null);
}

public static BeanDefinition registerAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, Object source) {
    return registerOrEscalateApcAsRequired(InfrastructureAdvisorAutoProxyCreator.class, registry, source);
}

private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, BeanDefinitionRegistry registry, Object source) {
    Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
    if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
        BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
        if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
            int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
            int requiredPriority = findPriorityForClass(cls);
            if (currentPriority < requiredPriority) {
                apcDefinition.setBeanClassName(cls.getName());
            }
        }
        return null;
    }
    RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
    beanDefinition.setSource(source);
    beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
    beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
    registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
    return beanDefinition;
}

可以看到會向容器中注冊了一個名為internalAutoProxyCreator洞渤,類型為InfrastructureAdvisorAutoProxyCreator的組件Bean,而這個類其實同AnnotationAwareAspectJAutoProxyCreator類似属瓣,也是AbstractAdvisorAutoProxyCreator的實現(xiàn)類载迄,而它的頂層接口類其實就是BeanPostProcessor,所以它的邏輯跟AnnotationAwareAspectJAutoProxyCreator大體上一樣:利用后置處理器機制在對象創(chuàng)建之后抡蛙,并包裝成代理對象护昧,在代理對象執(zhí)行目標方法的時候利用攔截器鏈進行攔截,具體過程可以參考AOP原理溜畅。


2.3捏卓、ProxyTransactionManagementConfiguration

另一個組件為ProxyTransactionManagementConfiguration

@Configuration
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {

    @Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor() {
        BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
        advisor.setTransactionAttributeSource(transactionAttributeSource());
        advisor.setAdvice(transactionInterceptor());
        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() {
        TransactionInterceptor interceptor = new TransactionInterceptor();
        interceptor.setTransactionAttributeSource(transactionAttributeSource());
        if (this.txManager != null) {
            interceptor.setTransactionManager(this.txManager);
        }
        return interceptor;
    }

}

ProxyTransactionManagementConfiguration給容器中注入了多個組件Bean,其中:

  • transactionAdvisor這個Bean用于創(chuàng)建一個事務(wù)增強器慈格。
  • transactionAttributeSource這個Bean主要保存事務(wù)屬性怠晴,其中包括用于解析不同包下的@Transactional注解的解析器:
public AnnotationTransactionAttributeSource() {
    this(true);
}

public AnnotationTransactionAttributeSource(boolean publicMethodsOnly) {
    this.publicMethodsOnly = publicMethodsOnly;
    this.annotationParsers = new LinkedHashSet<TransactionAnnotationParser>(2);
    this.annotationParsers.add(new SpringTransactionAnnotationParser());
    if (jta12Present) {
        this.annotationParsers.add(new JtaTransactionAnnotationParser());
    }
    if (ejb3Present) {
        this.annotationParsers.add(new Ejb3TransactionAnnotationParser());
    }
}

其中包括Spring中的@Transactional注解,同時也包括了Jta和Ejb3中的@Transactional注解解析器浴捆。

  • transactionInterceptor這個Bean主要用于創(chuàng)建事務(wù)攔截器蒜田,其中設(shè)置上面所說到的事務(wù)屬性Bean,同時也保存了事務(wù)管理器选泻。TransactionInterceptor其實也是MethodInterceptor的子類冲粤。所以在代理對象執(zhí)行目標方法的時候美莫,方法攔截器就會進行攔截并工作。具體如何攔截工作梯捕,請繼續(xù)往下看invoke方法:
@Override
public Object invoke(final 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<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);

    // Adapt to TransactionAspectSupport's invokeWithinTransaction...
    return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() {
        @Override
        public Object proceedWithInvocation() throws Throwable {
            return invocation.proceed();
        }
    });
}

重點就在invokeWithinTransaction方法:

protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation)
            throws Throwable {

    // If the transaction attribute is null, the method is non-transactional.
    //獲取事務(wù)注解屬性
    final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
    //獲取事務(wù)管理器
    final PlatformTransactionManager tm = determineTransactionManager(txAttr);
    final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

    if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
        // Standard transaction demarcation with getTransaction and commit/rollback calls.
        //
        TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
        Object retVal = null;
        try {
            // This is an around advice: Invoke the next interceptor in the chain.
            // This will normally result in a target object being invoked.
            retVal = invocation.proceedWithInvocation();
        }
        catch (Throwable ex) {
            // target invocation exception
            completeTransactionAfterThrowing(txInfo, ex);
            throw ex;
        }
        finally {
            cleanupTransactionInfo(txInfo);
        }
        commitTransactionAfterReturning(txInfo);
        return retVal;
    }

    else {
        // It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
        try {
            Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, new TransactionCallback<Object>() {
      @Override                                                                            public Object doInTransaction(TransactionStatus status) {                               TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
          try {
                  return invocation.proceedWithInvocation();
          } catch (Throwable ex) {
              if (txAttr.rollbackOn(ex)) {
              // A RuntimeException: will lead to a rollback.
                if (ex instanceof RuntimeException) {
                   throw (RuntimeException) ex;
                } else {
                    throw new ThrowableHolderException(ex);
                }
              }else {
                  // A normal return value: will lead to a commit.
                  return new ThrowableHolder(ex);
              }
          } finally {
              cleanupTransactionInfo(txInfo); 
          }                                                                                }                                                                                });
            // Check result: It might indicate a Throwable to rethrow.
            if (result instanceof ThrowableHolder) {
                throw ((ThrowableHolder) result).getThrowable();
            }
            else {
                return result;
            }
        }
        catch (ThrowableHolderException ex) {
            throw ex.getCause();
        }
    }
}

其實大家看這段源碼就能看到:1厢呵、獲取事務(wù)相關(guān)屬性;2傀顾、獲取事務(wù)管理器襟铭;3、如果需要事務(wù)支持短曾,則創(chuàng)建一個事務(wù)寒砖;4、執(zhí)行目標方法嫉拐;5哩都、如果執(zhí)行正常,則進行事務(wù)提交婉徘;6漠嵌、如果執(zhí)行異常,則進行事務(wù)回滾判哥。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末献雅,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子塌计,更是在濱河造成了極大的恐慌挺身,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锌仅,死亡現(xiàn)場離奇詭異章钾,居然都是意外死亡,警方通過查閱死者的電腦和手機热芹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進店門贱傀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人伊脓,你說我怎么就攤上這事府寒。” “怎么了报腔?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵株搔,是天一觀的道長。 經(jīng)常有香客問我纯蛾,道長纤房,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任翻诉,我火速辦了婚禮炮姨,結(jié)果婚禮上捌刮,老公的妹妹穿的比我還像新娘。我一直安慰自己舒岸,他們只是感情好绅作,可當我...
    茶點故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蛾派,像睡著了一般棚蓄。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上碍脏,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天,我揣著相機與錄音稍算,去河邊找鬼典尾。 笑死,一個胖子當著我的面吹牛糊探,可吹牛的內(nèi)容都是我干的钾埂。 我是一名探鬼主播,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼科平,長吁一口氣:“原來是場噩夢啊……” “哼褥紫!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起瞪慧,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤髓考,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后弃酌,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體氨菇,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年妓湘,在試婚紗的時候發(fā)現(xiàn)自己被綠了查蓉。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡榜贴,死狀恐怖豌研,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情唬党,我是刑警寧澤鹃共,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站初嘹,受9級特大地震影響及汉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜屯烦,卻給世界環(huán)境...
    茶點故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一坷随、第九天 我趴在偏房一處隱蔽的房頂上張望房铭。 院中可真熱鬧,春花似錦温眉、人聲如沸缸匪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽凌蔬。三九已至,卻和暖如春闯冷,著一層夾襖步出監(jiān)牢的瞬間砂心,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工蛇耀, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留辩诞,地道東北人。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓纺涤,卻偏偏與公主長得像译暂,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子撩炊,可洞房花燭夜當晚...
    茶點故事閱讀 44,933評論 2 355