SpringBoot + MyBatis 事務管理全解析:從 @Transactional 到 JDBC Connection 的旅程
- 一、JDBC Connection:事務操作的真正執行者
- 1.1 數據庫事務的本質
- 1.2 Spring 與 Connection 的協作流程
- 二、從 @Transactional 到 JDBC Connection 的完整鏈路
- 2.1 Spring 中 TransactionInterceptor 的核心邏輯
- 2.2 TransactionInterceptor 到 DataSourceTransactionManager 的調用鏈路
- 2.3 DataSourceTransactionManager 的核心實現
- 2.3.1 doBegin 方法:開啟事務并綁定資源
- 2.3.2 doCommit 方法:提交事務
- 2.3.3 doRollback 方法:回滾事務
- 2.4 TransactionSynchronizationManager:線程級事務上下文管理
- 三、MyBatis 與 Spring 事務的協作機制
- 3.1 SqlSessionTemplate:Spring 環境下的 MyBatis 會話
- 3.2 獲取 Spring 管理的 Connection
- 四、完整鏈路總結:從注解到數據庫的七步旅程
開篇:當我們使用 @Transactional
時,背后發生了什么?
在 SpringBoot + MyBatis
的項目中,只需在 Service
方法上添加@Transactional
注解,就能輕松實現事務管理。但這個過程中,Spring
如何與 MyBatis
協作?事務的提交 / 回滾究竟由誰執行?本文將基于spring-tx 5.3.23
和spring-boot-starter 2.2.2
版本,深入剖析從注解到數據庫的完整鏈路。
一、JDBC Connection:事務操作的真正執行者
1.1 數據庫事務的本質
在 JDBC
規范中,所有事務操作都由Connection
接口定義:
// java.sql.Connection接口核心方法
void setAutoCommit(boolean autoCommit) throws SQLException; // 開啟/關閉自動提交
void commit() throws SQLException; // 提交事務
void rollback() throws SQLException; // 回滾事務
無論上層框架如何封裝,最終執行事務提交 / 回滾的永遠是 JDBC
的 Connection
對象。Spring
的事務管理,本質是對這些底層操作的封裝與流程控制。
1.2 Spring 與 Connection 的協作流程
Spring
通過DataSourceTransactionManager
管理 Connection
的生命周期,關鍵流程如下:
- 獲取連接:從數據源 (
DataSource
) 獲取Connection
- 開啟事務:調用
connection.setAutoCommit(false)
- 執行業務邏輯:
MyBatis
使用該Connection
執行SQL
- 提交 / 回滾:根據執行結果調用
connection.commit()
或connection.rollback()
- 釋放連接:將
Connection
返回給連接池
偽代碼展示Spring
管理Connection
的核心邏輯:
// 偽代碼展示Spring管理Connection的核心邏輯
try {// 1. 從數據源獲取ConnectionConnection conn = dataSource.getConnection();// 2. 關閉自動提交,開啟事務conn.setAutoCommit(false);try {// 3. 執行SQL操作(MyBatis使用此Connection)userMapper.insert(user);orderMapper.createOrder(order);// 4. 提交事務conn.commit();} catch (Exception e) {// 5. 異常時回滾事務conn.rollback();} finally {// 6. 釋放連接conn.close(); // 實際由連接池管理}
} catch (SQLException ex) {throw new RuntimeException("數據庫操作失敗", ex);
}
二、從 @Transactional 到 JDBC Connection 的完整鏈路
2.1 Spring 中 TransactionInterceptor 的核心邏輯
TransactionInterceptor
是 Spring
框架中專門用于攔截帶有 @Transactional
注解方法的 AOP 攔截器
TransactionInterceptor
類繼承自 TransactionAspectSupport
,并實現了 MethodInterceptor
接口。在 Spring
的事務自動代理機制中,@Transactional
注解會被 TransactionAttributeSource
解析,最終觸發 TransactionInterceptor
的攔截邏輯
在 Spring 5.3.23
版本中,TransactionInterceptor
的核心邏輯如下:
/*** AOP 方法攔截器的核心實現,用于在事務環境中執行目標方法*/
@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {// TransactionAttributeSource 需要同時傳入目標類和方法(方法可能來自接口)Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);// 委托給 TransactionAspectSupport 的核心事務處理方法。傳入目標方法、目標類和自定義的調用回調return invokeWithinTransaction(invocation.getMethod(), targetClass, new CoroutinesInvocationCallback() {/*** 繼續執行攔截鏈,最終會調用目標方法*/@Override@Nullablepublic Object proceedWithInvocation() throws Throwable {return invocation.proceed();}@Overridepublic Object getTarget() {return invocation.getThis();}@Overridepublic Object[] getArguments() {return invocation.getArguments();}});
}
invoke
方法:
- 作為
AOP
攔截器的入口,負責攔截方法調用 - 解析目標類和方法信息
- 創建回調接口,連接事務管理器和目標方法
invokeWithinTransaction
方法:
- 事務管理的核心實現
- 根據事務屬性配置創建事務
- 執行目標方法并處理返回值
- 根據執行結果決定提交或回滾事務
2.2 TransactionInterceptor 到 DataSourceTransactionManager 的調用鏈路
整個調用鏈路可分為以下關鍵步驟:
// 關鍵調用鏈路偽代碼
TransactionInterceptor.invoke()→ TransactionAspectSupport.invokeWithinTransaction()→ createTransactionIfNecessary() // 創建事務→ AbstractPlatformTransactionManager.getTransaction()→ DataSourceTransactionManager.doBegin() // 開啟事務→ invocation.proceedWithInvocation(); // 執行目標方法(包含MyBatis SQL)→ commitTransactionAfterReturning() // 正常返回后提交→ AbstractPlatformTransactionManager.commit()→ DataSourceTransactionManager.doCommit()→ completeTransactionAfterThrowing() // 異常時回滾→ AbstractPlatformTransactionManager.rollback()→ DataSourceTransactionManager.doRollback()
2.3 DataSourceTransactionManager 的核心實現
2.3.1 doBegin 方法:開啟事務并綁定資源
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;Connection con = null;try {// 1. 獲取或創建新的Connectionif (!txObject.hasConnectionHolder() ||txObject.getConnectionHolder().isSynchronizedWithTransaction()) {Connection newCon = obtainDataSource().getConnection();if (logger.isDebugEnabled()) {logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");}txObject.setConnectionHolder(new ConnectionHolder(newCon), true);}// 2. 準備Connection用于事務txObject.getConnectionHolder().setSynchronizedWithTransaction(true);con = txObject.getConnectionHolder().getConnection();// 3. 設置隔離級別Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);txObject.setPreviousIsolationLevel(previousIsolationLevel);txObject.setReadOnly(definition.isReadOnly());// 4. 【關鍵】:關閉自動提交,開啟事務if (con.getAutoCommit()) {txObject.setMustRestoreAutoCommit(true);if (logger.isDebugEnabled()) {logger.debug("Switching JDBC Connection [" + con + "] to manual commit");}con.setAutoCommit(false);}// 5. 準備事務同步prepareTransactionalConnection(con, definition);txObject.getConnectionHolder().setTransactionActive(true);// 6. 超時設置int timeout = determineTimeout(definition);if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {txObject.getConnectionHolder().setTimeoutInSeconds(timeout);}// 7. 【關鍵】:將ConnectionHolder綁定到當前線程 if (txObject.isNewConnectionHolder()) {TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());}}catch (Throwable ex) {// 異常處理...}
}
關鍵步驟解析:
- 步驟 4:調用
con.setAutoCommit(false)
開啟事務模式 - 步驟 7:通過
TransactionSynchronizationManager.bindResource()
將Connection
綁定到當前線程
2.3.2 doCommit 方法:提交事務
protected void doCommit(DefaultTransactionStatus status) {DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();Connection con = txObject.getConnectionHolder().getConnection();try {// 核心:調用JDBC Connection的commit方法con.commit();}catch (SQLException ex) {throw new TransactionSystemException("Could not commit JDBC transaction", ex);}
}
2.3.3 doRollback 方法:回滾事務
protected void doRollback(DefaultTransactionStatus status) {DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();Connection con = txObject.getConnectionHolder().getConnection();try {// 核心:調用JDBC Connection的rollback方法con.rollback();}catch (SQLException ex) {throw new TransactionSystemException("Could not roll back JDBC transaction", ex);}
}
2.4 TransactionSynchronizationManager:線程級事務上下文管理
TransactionSynchronizationManager
是 Spring
事務管理的核心組件,使用ThreadLocal
存儲當前線程的事務資源:
// org.springframework.transaction.support.TransactionSynchronizationManager (Spring 5.3.23)
private static final ThreadLocal<Map<Object, Object>> resources =new NamedThreadLocal<>("Transactional resources");private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =new NamedThreadLocal<>("Transaction synchronizations");private static final ThreadLocal<String> currentTransactionName =new NamedThreadLocal<>("Current transaction name");// 綁定資源到當前線程
public static void bindResource(Object key, Object value) throws IllegalStateException {Map<Object, Object> map = resources.get();if (map == null) {map = new HashMap<>();resources.set(map);}Object oldValue = map.put(key, value);if (oldValue != null) {throw new IllegalStateException("Already value for key [" + key + "]");}
}// 從當前線程獲取資源
public static Object getResource(Object key) {Map<Object, Object> map = resources.get();return (map != null ? map.get(key) : null);
}
關鍵綁定點:
在DataSourceTransactionManager.doBegin()
方法中,通過以下代碼將 ConnectionHolder
綁定到當前線程:
TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
三、MyBatis 與 Spring 事務的協作機制
3.1 SqlSessionTemplate:Spring 環境下的 MyBatis 會話
SqlSessionTemplate
是 Spring
與 MyBatis
集成的核心組件,它會優先使用 Spring
管理的事務連接:
執行 SQL
的核心方法是通過動態代理實現的。具體來說,所有 SQL
操作都會被代理到SqlSessionInterceptor
類的invoke
方法中處理。這個方法會獲取一個 SqlSession
實例,并調用其對應的 SQL
執行方法(如selectOne
、insert
、update
等)
private class SqlSessionInterceptor implements InvocationHandler {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 1. 從Spring事務上下文中獲取SqlSession(或創建新的)SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,SqlSessionTemplate.this.executorType,SqlSessionTemplate.this.exceptionTranslator);try {// 2. 通過反射調用SqlSession的實際方法(如selectOne、insert等)Object result = method.invoke(sqlSession, args);// 3. 如果不是事務管理的SqlSession,則手動提交if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {sqlSession.commit(true);}return result;} catch (Throwable t) {// 異常處理...} finally {// 4. 關閉SqlSession(如果不是事務管理的)if (sqlSession != null) {closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);}}}
}
3.2 獲取 Spring 管理的 Connection
getSqlSession()
方法最終會調用SqlSessionUtils
工具類,嘗試從TransactionSynchronizationManager
獲取當前事務上下文中的 SqlSession
:
/*** 獲取MyBatis的SqlSession實例,支持事務同步管理*/
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,PersistenceExceptionTranslator exceptionTranslator) {// 參數校驗...// 從當前事務同步管理器中獲取已綁定的SqlSession資源// 【核心邏輯】:事務中的SqlSession會綁定到當前線程SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);// 從現有持有者中獲取SqlSession(優先使用已存在的會話)SqlSession session = sessionHolder(executorType, holder);if (session != null) {return session;}// 調用MyBatis工廠方法創建新會話(指定執行器類型)session = sessionFactory.openSession(executorType);// 注冊SqlSession到事務同步管理器(關鍵邏輯:實現事務內會話共享)registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);return session;}
四、完整鏈路總結:從注解到數據庫的七步旅程
- 注解解析
Spring
通過@Transactional
注解獲取事務屬性配置 - AOP 攔截
TransactionInterceptor
攔截目標方法調用 - 事務管理器獲取
根據配置獲取DataSourceTransactionManager
實例 - 開啟事務
調用doBegin()
:- 從數據源獲取
Connection
- 設置
autoCommit=false
- 將
Connection
綁定到TransactionSynchronizationManager
- 從數據源獲取
- 執行 SQL
MyBatis
通過SqlSessionTemplate
獲取Spring
管理的Connection
執行SQL
- 提交 / 回滾事務
根據執行結果調用doCommit()
或doRollback()
,最終調用Connection
的對應方法 - 資源清理
釋放Connection
,解除與當前線程的綁定
理解 Spring
與 MyBatis
的事務協作機制,不僅能幫助我們正確使用事務,更能在遇到問題時快速定位和解決。完結撒花(*^▽^)!!!*