Spring事務原始碼解讀
一、Spring事務使用
1.通過maven方式引入jar包
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.8</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.28</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.2.20.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.20.RELEASE</version> </dependency>
2.建立配置類,使用註解@EnableTransactionManagement開啟事務功能。程式碼如下:
package com.ybe.transaction.config; import com.alibaba.druid.pool.DruidDataSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; import javax.sql.DataSource; @Configuration @EnableTransactionManagement @ComponentScan("com.ybe.transaction") public class TransactionConfig { @Bean public DataSource dataSource() { DruidDataSource data = new DruidDataSource(); data.setDriverClassName("com.mysql.cj.jdbc.Driver"); data.setUrl("jdbc:mysql://localhost:3306/xxx?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8"); data.setUsername("xxx"); data.setPassword("xxx"); return data; } @Bean public JdbcTemplate jdbcTemplate(DataSource dataSource) { return new JdbcTemplate(dataSource); } @Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } }
3.使用@Transactional註解,程式碼如下:
package com.ybe.transaction.service; import com.ybe.transaction.dao.BookDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @Service public class BookService { @Autowired BookDao bookDao; public BookDao getBookDao() { return bookDao; } public void setBookDao(BookDao bookDao) { this.bookDao = bookDao; } /** * 結賬:傳入哪個使用者買了哪本書 * @param id */ @Transactional(propagation = Propagation.REQUIRED) public void checkout(int id){ bookDao.updateStock(id); } }
package com.ybe.transaction.dao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @Repository public class BookDao { @Autowired JdbcTemplate jdbcTemplate; public JdbcTemplate getJdbcTemplate() { return jdbcTemplate; } public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } /** * 減庫存,減去某本書的庫存 * @param id */ @Transactional(propagation = Propagation.REQUIRES_NEW) public void updateStock(int id){ String sql = "update book_stock set stock=stock-1 where id=?"; jdbcTemplate.update(sql,id); // for (int i = 1 ;i>=0 ;i--) // System.out.println(10/i); } }
5.Main的程式碼
AnnotationConfigApplicationContext configApplicationContext = new AnnotationConfigApplicationContext(TransactionConfig.class); BookService bean = configApplicationContext.getBean(BookService.class); bean.checkout(1); System.out.println("完成");
三、Spring事務的原始碼分析
Spring事務和SpringAOP一樣,都是利用了SpringIoc容器的外掛功能。在SpringIOC容器的生命週期過程中整合了Spring事務的功能。大概過程:通過 @Import註冊 TransactionManagementConfigurationSelector 類。該類中實現了 ImportSelector介面的selectImports方法 。在建立AnnoteationConfigApplicationContext的建構函式中會呼叫refresh()方法。在 invokeBeanFactoryPostProcessors()中會呼叫TransactionManagementConfigurationSelector 類的 selectImports 方法,會生成 AutoProxyRegistrar 的物件和ProxyTransactionManagementConfiguration物件。AutoProxyRegistrar會呼叫該類的registerBeanDefinitions方法註冊型別為InfrastructureAdvisorAutoProxyCreator的RootBeanDefinition。ProxyTransactionManagementConfiguration中注入了BeanFactoryTransactionAttributeSourceAdvisor型別的Bean,即事務通知類,該類繼承了 Advisor。 在finishBeanFactoryInitialization()過程中第一次呼叫 getAdvicesAndAdvisorsForBean 的時候,會先進行Advisors的建立,再獲取候選的Advisors。在建立完 Bean後會呼叫InfrastructureAdvisorAutoProxyCreator的 postProcessAfterInitialization方法,從 Advisors 中查詢是否匹配當前正在建立的Bean,如果能匹配,則建立相關的事務動態代理物件。
完整原始碼分析分三部分:Spring事務的初始化、事務的建立、事務的呼叫過程。
一、Spring事務的初始化
主要邏輯是找到所有標註了 @EnableTransactionManagement的類,並且解析標註中的事務通知方法和BeanPostProcessor的實現類BeanFactoryTransactionAttributeSourceAdvisor。
整體程式碼流程圖如下:
說明:
-
建立 AnnotationConfigApplicationContext() 容器。
-
在invokeBeanFactoryPostProcessors()中,會呼叫 ConfigurationClassPostProcessor 的 postProcessBeanDefinitionRegistry() 。在此方法中,會找到 @EnableTransactionManagement的 @Import 屬性傳入的 TransactionManagementConfigurationSelector.class 類。並且執行該類的selectImports() 方法,將型別為 InfrastructureAdvisorAutoProxyCreator、名稱為org.springframework.aop.
config.internalAutoProxyCreator和型別為 ProxyTransactionManagementConfiguration的 RootBeanDefinition註冊到BeanDefinitionRegistry中。
-
在 registerBeanPostProcessors() 中會根據上面一步生成的 RootBeanDefinition物件建立 InfrastructureAdvisorAutoProxyCreator的Bean例項。
-
在 finishBeanFactoryInitialization() 中第一次執行到 、AbstractAutowireCapableBeanFactory.initializeBean() 時,會執行一段這樣的程式碼,如下
if (mbd == null || !mbd.isSynthetic()) { wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); }
@Override public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) throws BeansException { Object result = existingBean; for (BeanPostProcessor processor : getBeanPostProcessors()) { Object current = processor.postProcessAfterInitialization(result, beanName); if (current == null) { return result; } result = current; } return result; }
以上程式碼會執行 InfrastructureAdvisorAutoProxyCreator 的 postProcessAfterInitialization() 方法。程式碼如下:
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) { if (bean != null) { Object cacheKey = getCacheKey(bean.getClass(), beanName); if (this.earlyProxyReferences.remove(cacheKey) != bean) { return wrapIfNecessary(bean, beanName, cacheKey); } } return bean; }
在 wrapIfNecessary 的getAdvicesAndAdvisorsForBean會呼叫findEligibleAdvisors,程式碼如下:
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) { List<Advisor> candidateAdvisors = findCandidateAdvisors(); List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName); extendAdvisors(eligibleAdvisors); if (!eligibleAdvisors.isEmpty()) { eligibleAdvisors = sortAdvisors(eligibleAdvisors); } return eligibleAdvisors; }
在findCandidateAdvisors方法中會生成所有的 Advisors,其中事務的Advisor 是 BeanFactoryTransactionAttributeSourceAdvisor 類。
protected List<Advisor> findCandidateAdvisors() { Assert.state(this.advisorRetrievalHelper != null, "No BeanFactoryAdvisorRetrievalHelper available"); return this.advisorRetrievalHelper.findAdvisorBeans(); }
advisorRetrievalHelper.findAdvisorBeans()是關鍵。方法裡面的邏輯如下:
1.先從cachedAdvisorBeanNames中獲取beanNames 2.如果cachedAdvisorBeanNames為null, 2.1 則獲取所有型別為 Advisor的 beanNames,並且新增到cachedAdvisorBeanNames中 3. 遍歷 beanNames ,根據beanName獲取具體的Bean物件。新增到advisors中 4.最後返回 advisors。
advisorRetrievalHelper.findAdvisorBeans()方法會快取所有型別為Advisor的BeanName,方法的最後返回 advisors。至此整個 Spring事務的初始化完成。
二、事務的建立
在建立Bean的生命週期的 initializeBean 方法中,會執行 InfrastructureAdvisorAutoProxyCreator的 postProcessAfterInitialization方法。該方法裡會拿快取中BeanFactoryTransactionAttributeSourceAdvisor的pointCut去匹配正在建立的例項Bean的所有方法。如果 BeanFactoryTransactionAttributeSourceAdvisor 和 Bean 的某一個方法能匹配上,則把該BeanFactoryTransactionAttributeSourceAdvisor 新增到 advisor的候選集合中。直到找出匹配Bean的所有Adsivors。最後根據Adsivor的候選集合和Bean型別建立動態代理物件ProxyFactory。
整體程式碼流程圖如下:
說明:
1.動態代理的建立
建立動態代理有兩種方法,一種是 JDK ,一種是 CGLib 。
1.如果目標類有實現介面的話,則是使用JDK的方式生成代理物件。
2.配置了使用Cglib進行動態代理或者目標類沒有實現介面,那麼使用Cglib的方式建立代理物件。
區別: 建立:CGLIB由於建立多個.class 檔案所以 效率肯定要慢於JDK動態代理 呼叫:CGLIB是要優於JDK的。因為JDK使用的反射,而CGLIB直接呼叫。
三、事務的呼叫過程
以上面的Main方法為示例,只分析兩層事務,並且只分析事務提交成功的情況。在呼叫bean.checkout(1)方法的時候會直接呼叫 CglibAopProxy.intercept()方法,裡面的大概邏輯如下:
-
獲取被代理的實現類;
-
找出匹配該checkout 的 BeanFactoryTransactionAttributeSourceAdvisor,並且把 BeanFactoryTransactionAttributeSourceAdvisor 轉成Interceptor型別,返回攔截器鏈。
-
建立 CglibMethodInvocation (該物件中包括了 代理物件、被代理物件、執行的方法、方法引數、被代理物件的型別、事務攔截器)它繼承了 ReflectiveMethodInvocation 物件。執行CglibMethodInvocation 物件的proceed()方法,裡面會呼叫 ReflectiveMethodInvocation 的 proceed方法 ,該方法中會進行事務攔截器的遞迴呼叫,具體呼叫流程如下圖。ReflectiveMethodInvocation 物件在通知攔截器鏈呼叫中作用很關鍵,有銜接各個攔截器的作用。
程式碼流程如下圖:
-
在ReflectiveMethodInvocation 的proceed方法中,會先判斷當前攔截器鏈的索引,如果索引等於最後一個那麼則執行被代理類的方法。
-
如果不是,那麼先從攔截器鏈中獲取攔截器(TransactionInterceptor)並且執行該攔截器的 proceed 方法(方法接受 ReflectiveMethodInvocation 物件例項)
-
TransactionInterceptor會呼叫 invokeWithinTransaction()方法,主要邏輯為
1. 是否需要建立事務。 2. 執行具體的方法。 3. 回滾或者提交事務。
主要程式碼如下:
//建立必要的事務 TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification); Object retVal; 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);
-
createTransactionIfNecessary 中會呼叫 getTransaction 方法。getTransaction 方法執行的關鍵程式碼有
7.1 doGetTransaction(),建立資料來源事務物件,並設定 conHolder物件。此時conHolder為null。
// 建立一個數據源事務物件 DataSourceTransactionObject txObject = new DataSourceTransactionObject(); // 是否允許當前事務設定保持點 txObject.setSavepointAllowed(isNestedTransactionAllowed()); /** * TransactionSynchronizationManager 事務同步管理器物件(該類中都是區域性執行緒變數) * 用來儲存當前事務的資訊,我們第一次從這裡去執行緒變數中獲取 事務連線持有器物件 通過資料來源為key去獲取 * 由於第一次進來開始事務 我們的事務同步管理器中沒有被存放.所以此時獲取出來的conHolder為null */ ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource()); // 非新建立連線則寫false txObject.setConnectionHolder(conHolder, false); // 返回事務物件 return txObject;
7.2 isExistingTransaction(transaction),判斷依據為當前執行緒記錄的連線不為空且連線中的transactionActive屬性不為空。此時為不存在連線,所以該方法返回false。
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction; // 若第一次進來開始事務,txObject.hasConnectionHolder()返回的null那麼表示不存在事務 return (txObject.hasConnectionHolder() && txObject.getConnectionHolder().isTransactionActive());
7.3 因為checkout()方法 的 事務屬性為Propagation.REQUIRED,所以這裡進入如下程式碼
else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED || def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW || def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) { SuspendedResourcesHolder suspendedResources = suspend(null); if (debugEnabled) { logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def); } try { return startTransaction(def, transaction, debugEnabled, suspendedResources); } catch (RuntimeException | Error ex) { resume(null, suspendedResources); throw ex; } }
其中 suspend(null),表示沒有當前事務。因為這個時候當前的執行緒變數中有沒有啟用的事物並且transaction 為null 所以這個方法返回 null,程式碼如下:
else { // Neither transaction nor synchronization active. return null; }
7.4 startTransaction(),開啟事務並返回事務狀態。
// 是否需要新同步 boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); // 建立新的事務 DefaultTransactionStatus status = newTransactionStatus( definition, transaction, true, newSynchronization, debugEnabled, suspendedResources); // 開啟事務和連線 doBegin(transaction, definition); // 同步事務的設定,如果執行緒狀態是:NewSynchronization,則設定執行緒同步管理器的相關資訊 prepareSynchronization(status, definition); return status;
其中 doBegin(),建立連線,關閉自動提交,儲存連線持有器到當前執行緒管理器中(確保巢狀中的事務屬性物件可以訪問到當前的事務聯結器,即事務連線可傳播,內層的事務可以訪問外層的事務)。其中 doBegin方法中關鍵程式碼如下:
// 通過資料來源獲取一個數據庫連線物件 Connection newCon = obtainDataSource().getConnection(); // 把我們的資料庫連線包裝成一個ConnectionHolder物件 然後設定到我們的txObject物件中去 txObject.setConnectionHolder(new ConnectionHolder(newCon), true); // 關閉自動提交 con.setAutoCommit(false); //如果是新事務連線持有器則儲存連線持有器到執行緒管理器中 if (txObject.isNewConnectionHolder()) { TransactionSynchronizationManager.bindResource(this.obtainDataSource(), txObject.getConnectionHolder()); }
其中 prepareSynchronization,同步事務的設定,如果執行緒狀態是:NewSynchronization,則設定執行緒同步管理器的相關資訊。此時執行緒狀態是NewSynchronization。程式碼如下:
if (status.isNewSynchronization()) { // 繫結事務啟用 TransactionSynchronizationManager.setActualTransactionActive(status.hasTransaction()); // 當前事務的隔離級別 TransactionSynchronizationManager.setCurrentTransactionIsolationLevel( definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT ? definition.getIsolationLevel() : null); // 是否為只讀事務 TransactionSynchronizationManager.setCurrentTransactionReadOnly(definition.isReadOnly()); // 事務的名稱 TransactionSynchronizationManager.setCurrentTransactionName(definition.getName()); TransactionSynchronizationManager.initSynchronization(); }
- createTransactionIfNecessary 中的prepareTransactionInfo(),建立一個TransactionInfo,然後設定該事務資訊的新事務狀態。程式碼如下:
//建立事務資訊物件 TransactionInfo txInfo = new TransactionInfo(tm, txAttr, joinpointIdentification); if (txAttr != null) { // We need a transaction for this method... if (logger.isTraceEnabled()) { logger.trace("Getting transaction for [" + txInfo.getJoinpointIdentification() + "]"); } // The transaction manager will flag an error if an incompatible tx already exists. //設定事務狀態 txInfo.newTransactionStatus(status); }
- invocation.proceedWithInvocation()執行具體的方法體,這裡指checkout()的方法。當執行 bookDao.updateStock(id)的時候,因為bookDao也是代理類,這裡會進入第內層代理類的呼叫,即呼叫CglibAopProxy.intercept()方法。
以下程式碼是 updateStock(id)方法的事務處理過程:
- 前面步驟和 checkout() 都差不多,直到createTransactionIfNecessary 的中getTransaction的程式碼:
1.1 doGetTransaction(),建立資料來源事務物件,並設定conHolder物件。由於是第二次進來建立事務方法, 我們的事務同步管理器中有第一次事務儲存的conHolder。所以此時獲取出來的conHolder不為null。
// 建立一個數據源事務物件 DataSourceTransactionObject txObject = new DataSourceTransactionObject(); // 是否允許當前事務設定保持點 txObject.setSavepointAllowed(isNestedTransactionAllowed()); /** * TransactionSynchronizationManager 事務同步管理器物件(該類中都是區域性執行緒變數) * 用來儲存當前事務的資訊,我們第一次從這裡去執行緒變數中獲取 事務連線持有器物件 通過資料來源為key去獲取 * 由於第二次進來開始事務 我們的事務同步管理器中有第一次事務儲存的conHolder.所以此時獲取出來的conHolder不為null */ ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource()); // 非新建立連線則寫false txObject.setConnectionHolder(conHolder, false);
1.2 isExistingTransaction(transaction),判斷依據為當前執行緒記錄的連線不為空且連線中的transactionActive屬性不為空,此時為存在,會執行if程式碼塊裡面的程式碼,執行handleExistingTransaction()方法。在此方法中執行的關鍵程式碼如下:
1.2.1 由於updateStock的事務屬性為Propagation.REQUIRES_NEW。所以執行下面的程式碼:
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; } }
1.2.2 suspend方法中主要邏輯為:呼叫doSuspend 掛起連線持有器。然後先從事務管理器中獲取當前事務(外層事務)的名稱、只讀屬性、事務隔離級別、當前事務啟用狀態。然後清空事務狀態。再把之前獲取到的屬性封裝為一個掛起的事務屬性SuspendedResourcesHolder返回出去,該事務屬性用來記錄當前事務的相關屬性,以便內層事務後面用來恢復外層事務。程式碼如下:
// 判斷當前的執行緒同步管理器變數中有沒有啟用的事物,有需要清空執行緒變數 if (TransactionSynchronizationManager.isSynchronizationActive()) { List<TransactionSynchronization> suspendedSynchronizations = doSuspendSynchronization(); try { Object suspendedResources = null; if (transaction != null) { //掛起的資源,連線持有器,這裡指掛起當前連線,後面備用,會傳入掛起的資源管理器 suspendedResources = doSuspend(transaction); } // 獲取當前事務名稱 String name = TransactionSynchronizationManager.getCurrentTransactionName(); // 清空執行緒變數 TransactionSynchronizationManager.setCurrentTransactionName(null); // 獲取出只讀事務的名稱 boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly(); // 清空執行緒變數 TransactionSynchronizationManager.setCurrentTransactionReadOnly(false); // 獲取已存在事務的隔離級別 Integer isolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel(); // 清空隔離級別 TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(null); // 判斷當前事務啟用狀態 boolean wasActive = TransactionSynchronizationManager.isActualTransactionActive(); // 清空標記 TransactionSynchronizationManager.setActualTransactionActive(false); // 把上訴從執行緒變數中獲取出來的存在事務屬性封裝為掛起的事務屬性返回出去 return new SuspendedResourcesHolder( suspendedResources, suspendedSynchronizations, name, readOnly, isolationLevel, wasActive); } catch (RuntimeException | Error ex) { // doSuspend failed - original transaction is still active... doResumeSynchronization(suspendedSynchronizations); throw ex; } }
其中 doSuspend ,主要是掛起外層的事務連線持有器並返回掛起的物件,程式碼邏輯如下
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction; // 清空連線持有器 txObject.setConnectionHolder(null); // 解綁執行緒私有的資源,返回給上一層 return TransactionSynchronizationManager.unbindResource(obtainDataSource());
1.2.3 startTransaction 開啟事務並返回事務狀態。這裡會存入上面返回的掛起的資源聯結器,裡面儲存了外層事務狀態的相關資訊以及連線持有器。
// 是否需要新同步 boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); // 建立新的事務狀態 DefaultTransactionStatus status = newTransactionStatus( definition, transaction, true, newSynchronization, debugEnabled, suspendedResources); // 開啟事務和連線 doBegin(transaction, definition); // 同步事務的設定,如果執行緒狀態是:新執行緒,則設定執行緒同步管理器的相關資訊 prepareSynchronization(status, definition); return status;
1.2.3.1 doBegin(),建立連線,關閉自動提交,儲存連線持有器到當前執行緒管理器中(確保巢狀中的事務屬性物件可以訪問到當前的事務聯結器,即事務連線可傳播,內層的事務可以訪問外層的事務)。和上面的doBegin作用一樣。
1.2.3.2 prepareSynchronization(),同步事務的設定,如果執行緒狀態是:NewSynchronization,則設定執行緒同步管理器的相關資訊。此時執行緒狀態是NewSynchronization。
- createTransactionIfNecessary 中 prepareTransactionInfo(),建立一個TransactionInfo,然後設定該事務資訊的新事務狀態。
- retVal = invocation.proceedWithInvocation();執行updateStock(id)方法體裡面的內容。
- commitTransactionAfterReturning 提交具體的事務。由於此時TransactionInfo的TransactionStatus為true。會執行如下程式碼:
//當前狀態是新事務 else if (status.isNewTransaction()) { if (status.isDebug()) { logger.debug("Initiating transaction commit"); } unexpectedRollback = status.isGlobalRollbackOnly(); // 如果是獨立的事務則直接提交 doCommit(status); }
4.1 其中doCommitf方法中會獲取連線物件,並且完成資料庫事務的提交,程式碼如下:
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction(); Connection con = txObject.getConnectionHolder().getConnection(); if (status.isDebug()) { logger.debug("Committing JDBC transaction on Connection [" + con + "]"); } try { // JDBC連線提交 con.commit(); } catch (SQLException ex) { throw new TransactionSystemException("Could not commit JDBC transaction", ex); }
- cleanupAfterCompletion,回滾後的處理工作,其中resume方法中會恢復之前掛起的事務。程式碼如下:
// 有掛起的事務要恢復 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()); }
- resume 中的 doResume 會拿到外層的事務即(checkout方法事務)的連線持有器放入事務同步管理器中。這樣就確保了,在執行到 checkout方法體的時候拿到的連線物件是checkout當時生成的連線物件,以便checkout方法類其他巢狀事務呼叫。
protected void doResume(@Nullable Object transaction, Object suspendedResources) { TransactionSynchronizationManager.bindResource(this.obtainDataSource(), suspendedResources); }
至此,updateStock() 方法執行基本完成,程式碼執行回到checkout()方法體中。
- checkout執行完畢之後,回到checkout過程中的invocation.proceedWithInvocation()。
- 如果成功,則commitTransactionAfterReturning 提交事務。由於此時TransactionInfo的TransactionStatus為true。會執行如下程式碼:
//當前狀態是新事務 else if (status.isNewTransaction()) { if (status.isDebug()) { logger.debug("Initiating transaction commit"); } unexpectedRollback = status.isGlobalRollbackOnly(); // 如果是獨立的事務則直接提交 doCommit(status); }
2.1 其中doCommitf方法中會獲取連線物件,並且完成資料庫事務的提交,程式碼如下:
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction(); Connection con = txObject.getConnectionHolder().getConnection(); if (status.isDebug()) { logger.debug("Committing JDBC transaction on Connection [" + con + "]"); } try { // JDBC連線提交 con.commit(); } catch (SQLException ex) { throw new TransactionSystemException("Could not commit JDBC transaction", ex); }
至此,checkout() 方法執行大概完成。整個程式碼呼叫事務也基本完成了。
四、事務的傳播行為總結
事務的傳播行為 | 外部不存在事務 | 外部存在事務 |
---|---|---|
REQUIRED(預設) | 開啟新事務 | 融合到外部事務中 |
SUPPORTS | 不開啟新事務 | 融合到外部事務中 |
MANDATORY | 丟擲異常 | 融合到外部事務中 |
REQUIRES_NEW | 開啟新事務 | 掛起外部事務,建立新事務 |
NOT_SUPPORTED | 不開啟新事務 | 掛起外部事務 |
NEVER | 不開啟新事務 | 丟擲異常 |
NESTED | 開啟新的事務 | 融合到外部事務中,SavePoint機制,外層影響內層,內層不會影響外層 |
- 分享自己平時使用的socket多客戶端通訊的程式碼技術點和軟體使用
- iNeuOS工業網際網路作業系統,增加2154個檢視建模(WEB組態)行業向量圖元、大屏背景及相關圖元
- 多臺雲伺服器的 Kubernetes 叢集搭建
- Elasticsearch學習系列四(聚合搜尋)
- 關於swiper外掛在vue2的使用
- 使用 Abp.Zero 搭建第三方登入模組(一):原理篇
- LVGL庫入門教程 - 顏色和影象
- 物聯網?快來看 Arduino 上雲啦
- SpringBoot JWT Redis 開源知識社群系統
- CVPR2022 | 可精簡域適應
- Spring框架系列(3) - 深入淺出Spring核心之控制反轉(IOC)
- 面試突擊59:一個表中可以有多個自增列嗎?
- CVPR2022 | 弱監督多標籤分類中的損失問題
- JDBC、ORM、JPA、Spring Data JPA,傻傻分不清楚?一文帶你釐清箇中曲直,給你個選擇SpringDataJPA的...
- Spring Security:使用者和Spring應用之間的安全屏障
- Mybatisi和Spring整合原始碼分析
- 前端學習 linux —— 第一篇
- call apply bind的作用及區別? 應用場景?
- Bika LIMS 開源LIMS集——實驗室檢驗流程概述及主頁、面板
- 軟體專案管理 7.5.專案進度模型(SPSP)