淺談MyBatis 事務(wù)管理
1. 運行環(huán)境 Enviroment
當(dāng) MyBatis 與不同的應(yīng)用結(jié)合時,需要不同的事務(wù)管理機(jī)制。與 Spring 結(jié)合時,由 Spring 來管理事務(wù);單獨使用時需要自行管理事務(wù),在容器里運行時可能由容器進(jìn)行管理。
MyBatis 用 Enviroment 來表示運行環(huán)境,其封裝了三個屬性:
public class Configuration { // 一個 MyBatis 的配置只對應(yīng)一個環(huán)境 protected Environment environment; // 其他屬性 ..... } public final class Environment { private final String id; private final TransactionFactory transactionFactory; private final DataSource dataSource; }
2. 事務(wù)抽象
MyBatis 把事務(wù)管理抽象出 Transaction 接口,由 TransactionFactory 接口的實現(xiàn)類負(fù)責(zé)創(chuàng)建。
public interface Transaction { Connection getConnection() throws SQLException; void commit() throws SQLException; void rollback() throws SQLException; void close() throws SQLException; Integer getTimeout() throws SQLException; } public interface TransactionFactory { void setProperties(Properties props); Transaction newTransaction(Connection conn); Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit); }
Executor 的實現(xiàn)持有一個 SqlSession 實現(xiàn),事務(wù)控制是委托給 SqlSession 的方法來實現(xiàn)的。
public abstract class BaseExecutor implements Executor { protected Transaction transaction; public void commit(boolean required) throws SQLException { if (closed) { throw new ExecutorException("Cannot commit, transaction is already closed"); } clearLocalCache(); flushStatements(); if (required) { transaction.commit(); } } public void rollback(boolean required) throws SQLException { if (!closed) { try { clearLocalCache(); flushStatements(true); } finally { if (required) { transaction.rollback(); } } } } // 省略其他方法、屬性 }
3. 與 Spring 集成的事務(wù)管理
3.1 配置 TransactionFactory
與 Spring 集成時,通過 SqlSessionFactoryBean 來初始化 MyBatis 。
protected SqlSessionFactory buildSqlSessionFactory() throws IOException { Configuration configuration; XMLConfigBuilder xmlConfigBuilder = null; if (this.configuration != null) { configuration = this.configuration; if (configuration.getVariables() == null) { configuration.setVariables(this.configurationProperties); } else if (this.configurationProperties != null) { configuration.getVariables().putAll(this.configurationProperties); } } else if (this.configLocation != null) { xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties); configuration = xmlConfigBuilder.getConfiguration(); } else { configuration = new Configuration(); configuration.setVariables(this.configurationProperties); } if (this.objectFactory != null) { configuration.setObjectFactory(this.objectFactory); } if (this.objectWrapperFactory != null) { configuration.setObjectWrapperFactory(this.objectWrapperFactory); } if (this.vfs != null) { configuration.setVfsImpl(this.vfs); } if (hasLength(this.typeAliasesPackage)) { String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); for (String packageToScan : typeAliasPackageArray) { configuration.getTypeAliasRegistry().registerAliases(packageToScan, typeAliasesSuperType == null ? Object.class : typeAliasesSuperType); } } if (!isEmpty(this.typeAliases)) { for (Class<?> typeAlias : this.typeAliases) { configuration.getTypeAliasRegistry().registerAlias(typeAlias); } } if (!isEmpty(this.plugins)) { for (Interceptor plugin : this.plugins) { configuration.addInterceptor(plugin); } } if (hasLength(this.typeHandlersPackage)) { String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); for (String packageToScan : typeHandlersPackageArray) { configuration.getTypeHandlerRegistry().register(packageToScan); } } if (!isEmpty(this.typeHandlers)) { for (TypeHandler<?> typeHandler : this.typeHandlers) { configuration.getTypeHandlerRegistry().register(typeHandler); } } if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls try { configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource)); } catch (SQLException e) { throw new NestedIOException("Failed getting a databaseId", e); } } if (this.cache != null) { configuration.addCache(this.cache); } if (xmlConfigBuilder != null) { try { xmlConfigBuilder.parse(); } catch (Exception ex) { throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex); } finally { ErrorContext.instance().reset(); } } // 創(chuàng)建 SpringManagedTransactionFactory if (this.transactionFactory == null) { this.transactionFactory = new SpringManagedTransactionFactory(); } // 封裝成 Environment configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource)); if (!isEmpty(this.mapperLocations)) { for (Resource mapperLocation : this.mapperLocations) { if (mapperLocation == null) { continue; } try { XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments()); xmlMapperBuilder.parse(); } catch (Exception e) { throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e); } finally { ErrorContext.instance().reset(); } } } else { } return this.sqlSessionFactoryBuilder.build(configuration); }
public class SqlSessionFactoryBuilder { public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); } }
重點是在構(gòu)建 MyBatis Configuration 對象時,把 transactionFactory 配置成 SpringManagedTransactionFactory,再封裝成 Environment 對象。
3.2 運行時事務(wù)管理
Mapper 的代理對象持有的是 SqlSessionTemplate,其實現(xiàn)了 SqlSession 接口。
SqlSessionTemplate 的方法并不直接調(diào)用具體的 SqlSession 的方法,而是委托給一個動態(tài)代理,通過代理 SqlSessionInterceptor 對方法調(diào)用進(jìn)行攔截。
SqlSessionInterceptor 負(fù)責(zé)獲取真實的與數(shù)據(jù)庫關(guān)聯(lián)的 SqlSession 實現(xiàn),并在方法執(zhí)行完后決定提交或回滾事務(wù)、關(guān)閉會話。
public class SqlSessionTemplate implements SqlSession, DisposableBean { private final SqlSessionFactory sqlSessionFactory; private final ExecutorType executorType; private final SqlSession sqlSessionProxy; private final PersistenceExceptionTranslator exceptionTranslator; public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required"); notNull(executorType, "Property 'executorType' is required"); this.sqlSessionFactory = sqlSessionFactory; this.executorType = executorType; this.exceptionTranslator = exceptionTranslator; // 因為 SqlSession 接口聲明的方法也不少, // 在每個方法里添加事務(wù)相關(guān)的攔截比較麻煩, // 不如創(chuàng)建一個內(nèi)部的代理對象進(jìn)行統(tǒng)一處理。 this.sqlSessionProxy = (SqlSession) newProxyInstance( SqlSessionFactory.class.getClassLoader(), new Class[] { SqlSession.class }, new SqlSessionInterceptor()); } public int update(String statement) { // 在代理對象上執(zhí)行方法調(diào)用 return this.sqlSessionProxy.update(statement); } // 對方法調(diào)用進(jìn)行攔截,加入事務(wù)控制邏輯 private class SqlSessionInterceptor implements InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 獲取與數(shù)據(jù)庫關(guān)聯(lián)的會話 SqlSession sqlSession = SqlSessionUtils.getSqlSession( SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); try { // 執(zhí)行 SQL 操作 Object result = method.invoke(sqlSession, args); if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) { // 如果 sqlSession 不是 Spring 管理的,則要自行提交事務(wù) sqlSession.commit(true); } return result; } catch (Throwable t) { Throwable unwrapped = unwrapThrowable(t); if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) { SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); sqlSession = null; Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped); if (translated != null) { unwrapped = translated; } } throw unwrapped; } finally { if (sqlSession != null) { SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); } } } } }
SqlSessionUtils 封裝了對 Spring 事務(wù)管理機(jī)制的訪問。
// SqlSessionUtils public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { // 從 Spring 的事務(wù)管理機(jī)制那里獲取當(dāng)前事務(wù)關(guān)聯(lián)的會話 SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); SqlSession session = sessionHolder(executorType, holder); if (session != null) { // 已經(jīng)有一個會話則復(fù)用 return session; } // 創(chuàng)建新的 會話 session = sessionFactory.openSession(executorType); // 注冊到 Spring 的事務(wù)管理機(jī)制里 registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session); return session; } private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator, SqlSession session) { SqlSessionHolder holder; if (TransactionSynchronizationManager.isSynchronizationActive()) { Environment environment = sessionFactory.getConfiguration().getEnvironment(); if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) { holder = new SqlSessionHolder(session, executorType, exceptionTranslator); TransactionSynchronizationManager.bindResource(sessionFactory, holder); // 重點:注冊會話管理的回調(diào)鉤子,真正的關(guān)閉動作是在回調(diào)里完成的。 TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory)); holder.setSynchronizedWithTransaction(true); // 維護(hù)會話的引用計數(shù) holder.requested(); } else { if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) { } else { throw new TransientDataAccessResourceException( "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization"); } } } else { } } public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) { // 從線程本地變量里獲取 Spring 管理的會話 SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); if ((holder != null) && (holder.getSqlSession() == session)) { // Spring 管理的不直接關(guān)閉,由回調(diào)鉤子來關(guān)閉 holder.released(); } else { // 非 Spring 管理的直接關(guān)閉 session.close(); } }
SqlSessionSynchronization 是 SqlSessionUtils 的內(nèi)部私有類,用于作為回調(diào)鉤子與 Spring 的事務(wù)管理機(jī)制協(xié)調(diào)工作,TransactionSynchronizationManager 在適當(dāng)?shù)臅r候回調(diào)其方法。
private static final class SqlSessionSynchronization extends TransactionSynchronizationAdapter { private final SqlSessionHolder holder; private final SqlSessionFactory sessionFactory; private boolean holderActive = true; public SqlSessionSynchronization(SqlSessionHolder holder, SqlSessionFactory sessionFactory) { this.holder = holder; this.sessionFactory = sessionFactory; } public int getOrder() { return DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 1; } public void suspend() { if (this.holderActive) { TransactionSynchronizationManager.unbindResource(this.sessionFactory); } } public void resume() { if (this.holderActive) { TransactionSynchronizationManager.bindResource(this.sessionFactory, this.holder); } } public void beforeCommit(boolean readOnly) { if (TransactionSynchronizationManager.isActualTransactionActive()) { try { this.holder.getSqlSession().commit(); } catch (PersistenceException p) { if (this.holder.getPersistenceExceptionTranslator() != null) { DataAccessException translated = this.holder .getPersistenceExceptionTranslator() .translateExceptionIfPossible(p); if (translated != null) { throw translated; } } throw p; } } } public void beforeCompletion() { if (!this.holder.isOpen()) { TransactionSynchronizationManager.unbindResource(sessionFactory); this.holderActive = false; // 真正關(guān)閉數(shù)據(jù)庫會話 this.holder.getSqlSession().close(); } } public void afterCompletion(int status) { if (this.holderActive) { TransactionSynchronizationManager.unbindResourceIfPossible(sessionFactory); this.holderActive = false; // 真正關(guān)閉數(shù)據(jù)庫會話 this.holder.getSqlSession().close(); } this.holder.reset(); } }
3.3 創(chuàng)建新會話
// DefaultSqlSessionFactory private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { final Environment environment = configuration.getEnvironment(); // 獲取事務(wù)工廠實現(xiàn) final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); final Executor executor = configuration.newExecutor(tx, execType); return new DefaultSqlSession(configuration, executor, autoCommit); } catch (Exception e) { closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } } private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) { if (environment == null || environment.getTransactionFactory() == null) { return new ManagedTransactionFactory(); } return environment.getTransactionFactory(); }
4. 小結(jié)
- MyBatis 的核心組件 Executor 通過 Transaction 接口來進(jìn)行事務(wù)控制。
- 與 Spring 集成時,初始化 Configuration 時會把 transactionFactory 設(shè)置為 SpringManagedTransactionFactory 的實例。
- 每個 Mapper 代理里注入的 SqlSession 是 SqlSessionTemplate 的實例,其實現(xiàn)了 SqlSession 接口;
- SqlSessionTemplate 把對 SqlSession 接口里聲明的方法調(diào)用委托給內(nèi)部的一個動態(tài)代理,該代理的方法處理器為內(nèi)部類 SqlSessionInterceptor 。
- SqlSessionInterceptor 接收到方法調(diào)用時,通過 SqlSessionUtil 訪問 Spring 的事務(wù)設(shè)施,如果有與 Spring 當(dāng)前事務(wù)關(guān)聯(lián)的 SqlSession 則復(fù)用;沒有則創(chuàng)建一個。
- SqlSessionInterceptor 根據(jù) Spring 當(dāng)前事務(wù)的狀態(tài)來決定是否提交或回滾事務(wù)。會話的真正關(guān)閉是通過注冊在 TransactionSynchronizationManager 上的回調(diào)鉤子實現(xiàn)的。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
MongoDB中ObjectId的誤區(qū)及引起的一系列問題
這篇文章主要介紹了MongoDB中ObjectId的誤區(qū)及引起的一系列問題,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2016-12-12Springboot打包為Docker鏡像并部署的實現(xiàn)
這篇文章主要介紹了Springboot打包為Docker鏡像并部署的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12Java基礎(chǔ)之MapReduce框架總結(jié)與擴(kuò)展知識點
本章,是MapReduce的最終章,我在寫本章的時候,發(fā)現(xiàn)前面忘記介紹MpaTask與ReduceTask了,所以本章補(bǔ)上哈,另外還有兩個擴(kuò)展的知識點,講完這些,我會對整個MapReduce進(jìn)行總結(jié)一下,讓大家再次了解MapReduce的工作流程,更加清晰地認(rèn)識MapReduce ,需要的朋友可以參考下2021-05-05SpringBoot actuator 健康檢查不通過的解決方案
這篇文章主要介紹了SpringBoot actuator 健康檢查不通過的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07