mybatis源碼分析(四) mybatis與spring事務管理分析
一丶從jdbc的角度理解什么是事務
從mysql獲取一個連接之后, 默認是自動提交, 即執行完sql之后, 就會提交事務. 這種事務的范圍是一條sql語句.
將該連接設置非自動提交, 可以執行多條sql語句, 然后由程序決定是提交事務, 還是回滾事務. 這也是我們常說的事務.
Connection connection = dataSource.getConnection(); // connection.setTransactionIsolation(level.getLevel()); //設置事務隔離級別 // 設置是否自動提交, 如果不是自動提交, 則是"開啟"事務 connection.setAutoCommit(desiredAutoCommit); // connection預編譯statement, 並執行sql Statement stmt=connection.preparedStatement(); stmt.execute(sql); // 提交事務, 或者回滾 connection.commit(); //connection.rollback();
從jdbc使用事務的角度來看, 事務主要是圍繞connection展開的, 所以誰可獲得connection, 即可控制事務.
二丶mybatis是如何使用事務的
mybatis將jdbc中的事務操作抽象封裝成Transaction,用於管理connection的生命周期--創建, 准備, 提交/回滾 和關閉.
/** * Wraps a database connection. * Handles the connection lifecycle that comprises: its creation, preparation, commit/rollback and close. * * @author Clinton Begin */ public interface Transaction { /** * Retrieve inner database connection. * @return DataBase connection * @throws SQLException */ Connection getConnection() throws SQLException; /** * Commit inner database connection. * @throws SQLException */ void commit() throws SQLException; /** * Rollback inner database connection. * @throws SQLException */ void rollback() throws SQLException; /** * Close inner database connection. * @throws SQLException */ void close() throws SQLException; /** * Get transaction timeout if set. * @throws SQLException */ Integer getTimeout() throws SQLException; }
mybatis提供了兩種事務實現,一種是完全由jdbc實現的事務JdbcTransaction,包括實現提交和回滾.一種是供容器管理整個生命周期的事務ManagedTransaction,其中將忽略提交和回滾事務的請求, 將提交和回滾事務由容器實現, 但其實這種事務很少用.
JdbcTransaction:

public class JdbcTransaction implements Transaction { private static final Log log = LogFactory.getLog(JdbcTransaction.class); protected Connection connection; protected DataSource dataSource; protected TransactionIsolationLevel level; protected boolean autoCommit; public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) { dataSource = ds; level = desiredLevel; autoCommit = desiredAutoCommit; } public JdbcTransaction(Connection connection) { this.connection = connection; } @Override public Connection getConnection() throws SQLException { if (connection == null) { openConnection(); } return connection; } @Override public void commit() throws SQLException { if (connection != null && !connection.getAutoCommit()) { if (log.isDebugEnabled()) { log.debug("Committing JDBC Connection [" + connection + "]"); } connection.commit(); } } @Override public void rollback() throws SQLException { if (connection != null && !connection.getAutoCommit()) { if (log.isDebugEnabled()) { log.debug("Rolling back JDBC Connection [" + connection + "]"); } connection.rollback(); } } @Override public void close() throws SQLException { if (connection != null) { resetAutoCommit(); if (log.isDebugEnabled()) { log.debug("Closing JDBC Connection [" + connection + "]"); } connection.close(); } } protected void setDesiredAutoCommit(boolean desiredAutoCommit) { try { if (connection.getAutoCommit() != desiredAutoCommit) { if (log.isDebugEnabled()) { log.debug("Setting autocommit to " + desiredAutoCommit + " on JDBC Connection [" + connection + "]"); } connection.setAutoCommit(desiredAutoCommit); } } catch (SQLException e) { // Only a very poorly implemented driver would fail here, // and there's not much we can do about that. throw new TransactionException("Error configuring AutoCommit. " + "Your driver may not support getAutoCommit() or setAutoCommit(). " + "Requested setting: " + desiredAutoCommit + ". Cause: " + e, e); } } protected void resetAutoCommit() { try { if (!connection.getAutoCommit()) { // MyBatis does not call commit/rollback on a connection if just selects were performed. // Some databases start transactions with select statements // and they mandate a commit/rollback before closing the connection. // A workaround is setting the autocommit to true before closing the connection. // Sybase throws an exception here. if (log.isDebugEnabled()) { log.debug("Resetting autocommit to true on JDBC Connection [" + connection + "]"); } connection.setAutoCommit(true); } } catch (SQLException e) { if (log.isDebugEnabled()) { log.debug("Error resetting autocommit to true " + "before closing the connection. Cause: " + e); } } } protected void openConnection() throws SQLException { if (log.isDebugEnabled()) { log.debug("Opening JDBC Connection"); } connection = dataSource.getConnection(); if (level != null) { connection.setTransactionIsolation(level.getLevel()); } setDesiredAutoCommit(autoCommit); } @Override public Integer getTimeout() throws SQLException { return null; } }
mybatis事務執行流程:
1. 由於mybatis將事務抽取成一個接口, 便於管理, 所以可以在配置中配置事務管理的實現
2. 解析配置, 將事務管理對象, 保存到Configuration中
3. SqlSessionFactory創建SqlSession時, 將會同時注入tx對象
4. SqlSession執行sql語句時, 會委派給Executor執行, Executor處理主要的邏輯之外, 事務將會委派給事務對象處理, 如從事務對象中獲取連接, 使用事務對象提交事務.
//BaseExecutor // 在執行器里獲取Connection , 最后是委派給Transaction獲取, // 事務管理, 即是Connection是否設置自動提交, 以及將事務的回滾調用交給事務管理器管理 protected Connection getConnection(Log statementLog) throws SQLException { Connection connection = transaction.getConnection(); if (statementLog.isDebugEnabled()) { return ConnectionLogger.newInstance(connection, statementLog, queryStack); } else { return connection; } }
Transaction封裝了connection,然后在transaction內部封裝調用connection的操作,如提供不同的Transaction, 來管理connection的生命周期.
三丶spring是如何使用事務的
1) 入口
在配置了DataSourceProperties屬性之后, 會創建DataSource, 之后會創建DataSourceTransactionManager作為事務管理器
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration
@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, 從使用的角度來說,具有通用性 DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(this.dataSource); if (this.transactionManagerCustomizers != null) { this.transactionManagerCustomizers.customize(transactionManager); } return transactionManager; } } }
DataSourceTransactionManager繼承於AbstractPlatformTransactionManager,而AbstractPlatformTransactionManager位於spring的tx子項目中
2) spring-tx子項目
該項目最主要是用於實現事務管理.
2.1) 最核心接口就是PlatformTransactionManager接口, 定義了事務管理器.
a) #getTransaction(TransactionDefinition): TransactionStatus
Return a currently active transaction or create a new one, according to the specified propagation behavior.
b) #commit(TransactionStatus): void
Commit the given transaction, with regard to its status. If the transaction has been marked rollback-only programmatically, perform a rollback.
c) #rollback(TransactionStatus): void
Perform a rollback of the given transaction.
--更詳細的文檔則需要看源碼或者API文檔
2.2) TransactionDefinition 定義了事務的傳播行為, 隔離界別, 事務超時時間等
2.3)TransactionStatus 定義了事務的狀態, 以便於在提交事務或者回滾事務時決定如何后續行為.
3)使用@Transactional注解聲明事務
聲明式事務,s是基於AOP實現的.Spring會對使用@Transactinal注解聲明的方法進行動態代理, 生成使用org.springframework.transaction.interceptor.TransactionInterceptor增強對應方法的對象..
3.1) 事務切面方法實現
// org.springframework.transaction.interceptor.TransactionAspectSupport
@Override @Nullable 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<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null); // Adapt to TransactionAspectSupport's invokeWithinTransaction... return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed); }
/** * General delegate for around-advice-based subclasses, delegating to several other template * methods on this class. Able to handle {@link CallbackPreferringPlatformTransactionManager} * as well as regular {@link PlatformTransactionManager} implementations. * @param method the Method being invoked * @param targetClass the target class that we're invoking the method on * @param invocation the callback to use for proceeding with the target invocation * @return the return value of the method, if any * @throws Throwable propagated from the target invocation */ @Nullable protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass, final InvocationCallback invocation) throws Throwable { // If the transaction attribute is null, the method is non-transactional. TransactionAttributeSource tas = getTransactionAttributeSource(); final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null); 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; 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 { final ThrowableHolder throwableHolder = new ThrowableHolder(); // It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in. try { Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, 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. throwableHolder.throwable = ex; return null; } } finally { cleanupTransactionInfo(txInfo); } }); // Check result state: It might indicate a Throwable to rethrow. if (throwableHolder.throwable != null) { throw throwableHolder.throwable; } return result; } catch (ThrowableHolderException ex) { throw ex.getCause(); } catch (TransactionSystemException ex2) { if (throwableHolder.throwable != null) { logger.error("Application exception overridden by commit exception", throwableHolder.throwable); ex2.initApplicationException(throwableHolder.throwable); } throw ex2; } catch (Throwable ex2) { if (throwableHolder.throwable != null) { logger.error("Application exception overridden by commit exception", throwableHolder.throwable); } throw ex2; } } }
/** * Create a transaction if necessary based on the given TransactionAttribute. * <p>Allows callers to perform custom TransactionAttribute lookups through * the TransactionAttributeSource. * @param txAttr the TransactionAttribute (may be {@code null}) * @param joinpointIdentification the fully qualified method name * (used for monitoring and logging purposes) * @return a TransactionInfo object, whether or not a transaction was created. * The {@code hasTransaction()} method on TransactionInfo can be used to * tell if there was a transaction created. * @see #getTransactionAttributeSource() */ @SuppressWarnings("serial") protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm, @Nullable TransactionAttribute txAttr, final String joinpointIdentification) { // If no name specified, apply method identification as transaction name. 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) { status = tm.getTransaction(txAttr); // 這里使用了配置的PlatformTransactionManager獲取事務狀態 } else { if (logger.isDebugEnabled()) { logger.debug("Skipping transactional joinpoint [" + joinpointIdentification + "] because no transaction manager has been configured"); } } } return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status); }
3.2) 查看springboot自動配置DataSourceTransactionManager實現
@Override protected Object doGetTransaction() { DataSourceTransactionObject txObject = new DataSourceTransactionObject(); txObject.setSavepointAllowed(isNestedTransactionAllowed()); ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource()); txObject.setConnectionHolder(conHolder, false); return txObject; // 剛開始獲取事務時, 由於沒有開啟事務, 所以為null }
/** * This implementation sets the isolation level but ignores the timeout. */ @Override protected void doBegin(Object transaction, TransactionDefinition definition) { DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction; Connection con = null; try { if (!txObject.hasConnectionHolder() || txObject.getConnectionHolder().isSynchronizedWithTransaction()) { // 從數據源中獲取connection Connection newCon = obtainDataSource().getConnection(); if (logger.isDebugEnabled()) { logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction"); } txObject.setConnectionHolder(new ConnectionHolder(newCon), true); } txObject.getConnectionHolder().setSynchronizedWithTransaction(true); con = txObject.getConnectionHolder().getConnection(); Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition); txObject.setPreviousIsolationLevel(previousIsolationLevel); // Switch to manual commit if necessary. This is very expensive in some JDBC drivers, // so we don't want to do it unnecessarily (for example if we've explicitly // configured the connection pool to set it already). if (con.getAutoCommit()) { txObject.setMustRestoreAutoCommit(true); if (logger.isDebugEnabled()) { logger.debug("Switching JDBC Connection [" + con + "] to manual commit"); } con.setAutoCommit(false); } prepareTransactionalConnection(con, definition); txObject.getConnectionHolder().setTransactionActive(true); int timeout = determineTimeout(definition); if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) { txObject.getConnectionHolder().setTimeoutInSeconds(timeout); } // Bind the connection holder to the thread. if (txObject.isNewConnectionHolder()) {
// 將Connection和datasource關聯, 交由事務同步管理器保存管理, 使用ThreadLocal隔離
// TranscationSynchronizationManager也是Spring和mybatis-spring共同合作管理事務的橋梁
// ThreadLocal與當前線程綁定, 即線程隔離, 並且使用了同一個DataSource作為key, 可以獲取到同一個ConnectionHolder
TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder()); } } catch (Throwable ex) { if (txObject.isNewConnectionHolder()) { DataSourceUtils.releaseConnection(con, obtainDataSource()); txObject.setConnectionHolder(null, false); } throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex); } }
四丶mybatis和spring的事務是如何結合使用的
mybatis-spring項目,用於將Spring和mybatis整合
mybatis源碼分析(三) mybatis-spring整合源碼分析
mybatis-spring整合,需要配置SqlSessionFactoryBean構建生成SqlSessionFactory
SqlSessionFactoryBean#buildSqlSessionFactory()
//如果為空,則使用默認的SpringManagedTransactionFactory生成SpringManagedTransaction targetConfiguration.setEnvironment(new Environment(this.environment, this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory, this.dataSource));
SpringManagedTransaction#openConnection()
/** * Gets a connection from Spring transaction manager and discovers if this * {@code Transaction} should manage connection or let it to Spring. * <p> * It also reads autocommit setting because when using Spring Transaction MyBatis * thinks that autocommit is always false and will always call commit/rollback * so we need to no-op that calls. */ private void openConnection() throws SQLException {
// 從Spring transaction manager獲取之前由Spring獲取的connection this.connection = DataSourceUtils.getConnection(this.dataSource); this.autoCommit = this.connection.getAutoCommit(); this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource); LOGGER.debug(() -> "JDBC Connection [" + this.connection + "] will" + (this.isConnectionTransactional ? " " : " not ") + "be managed by Spring"); }
有本文第二節可知, mybatis執行事務相關操作, 如獲取Connection, 使用connection執行多條sql, 使用connection提交事務或者回滾事務, 都是委派給Transacation執行的,
要想將sql語句的執行由mybatis執行, 事務的提交或者回滾操作由Spring控制, 兩者需要關聯使用同一個connection, 在不同的方法中調用connection的相關方法操作, (所以, Spring並沒有直接使用mybatis sqlSession中提供的提交或者回滾方法) . 如何安全的獲取同一個connection?這就需要使用TransactionSynchronizationManager
ThreadLocal<Map<Object, Object>> resources
保存connection資源,的 key為DataSource, value為ConnectionHolder
(所以, 事務只支持在一個數據源中, 0.0)
學習資料: