本博客內容非自創,轉載自以下三位,侵刪:
https://juejin.im/post/5ab7bd11f265da23906bfbc5
https://my.oschina.net/fifadxj/blog/785621
https://www.jianshu.com/p/b864aecc0de1
JDBC相關概念
Java程序都是通過JDBC連接數據庫的,通過SQL對數據庫編程,JDBC是由SUN公司提出的一些列規范,只定義了接口規范,具體實現由各個數據庫廠商去實現,它是一種典型的橋接模式。ps:橋接模式是一種結構型設計模式,它的主要特點是把抽象與行為實現分離開來,分別定義接口,可以保持各部分的獨立性以及應對他們的功能擴展。
JDBC規范
所謂規范,就是自己定義了標准接口,做了如下抽象:用Connection代表和數據庫的連接,用Statement執行SQL,用ResultSet表示SQL返回的結果,提供了對數據的便利。從Connection可以創建Statement,Statement執行查詢得到ResultSet。
上面說的Connection、Statement、ResultSet都應該是接口,具體實現由各個數據庫提供商提供。有了規范,可以通過統一的接口,訪問多種類型的數據庫,可隨便切換數據庫。
數據庫驅動
上面提到,接口的實現由各個廠商提供,那么實現類的類名就會不統一,去創建Connection對象時,代碼就會寫死某個實現類,切換數據庫時,就需要修改代碼,這樣不太好。為了解決這個問題,抽象了Driver驅動的概念。
Connection con=MySqlConnectionImpl("127.0.0.1",3306,"mi_user",userName,pwd);
每個數據庫都需要實現Driver接口,通過Driver可獲得數據庫連接Connection,通過反射機制動態創建。
Class.forName("com.mysql.jdbc.Drier");
同一個程序可能訪問不同的數據庫,通過DriverManager來管理驅動,Driver在初始化的時候,需要注冊到DriverManager中。
DriverManager提供了一個getConnection方法,用於建立數據庫Connection:
Connection con=DriverManager.getConnection("127.0.0.1",3306,"mi_user",userName,pwd);
如果有多個數據庫驅動,DriverManager如何區分呢,需要在數據庫連接url中指定,比如mysql需要添加jdbc:mysql前綴:
String url= "jdbc:mysql://127.0.0.1:3306/mi_user";
Connection con=DriverManager.getConnection(url,userName,pwd)
數據源
數據源DataSource包含連接池和連接池管理2個部分,習慣上稱為連接池。在系統初始化的時候,將數據庫連接作為對象存儲在內存中,當需要訪問數據庫時,從連接池中取出一個已建立的空閑連接對象。
使用數據源,獲取其DataSource對象,通過該對象動態的獲取數據庫連接。另外,DataSource對象可以注冊到名字服務(JNDI)中,可以通過名字服務獲得DataSource對象,無需硬性編碼驅動。
DriverManager是JDBC1提供的,DataSource是JDBC2新增的功能,提供了更好的連接數據源的方法。
Mybatis核心組件:
- SqlSessionFactoryBuilder:會根據配置信息或代碼來生成SqlSessionFactory;
- SqlSessionFactory:依靠工廠來生成SqlSession;
- SqlSession:是一個既可以發送SQL去執行並返回結果,也可以獲取Mapper的接口;
- SQL Mapper:是MyBatis新設計的組件,由一個Java接口和XML文件構成,需要給出對應的SQL和映射規則。它負責發送SQL去執行,並返回結果。
Mybatis核心流程:
通過SqlSessionFactory獲取SqlSession
獲取MapperProxy
獲取Excutor
Mybatis事務:
DefaultTransactionDefinition def = new DefaultTransactionDefinition(); PlatformTransactionManager txManager = new DataSourceTransactionManager(dataSource); TransactionStatus status = txManager.getTransaction(def); try { //get jdbc connection... //execute sql... } catch (Exception e) { txManager.rollback(status); throw e; } txManager.commit(status);
可以看到PlatformTransactionManager的getTransaction(), rollback(), commit()是spring處理事務的核心api,分別對應事務的開始,提交和回滾。
spring事務處理的一個關鍵是保證在整個事務的生命周期里所有執行sql的jdbc connection和處理事務的jdbc connection始終是同一個。然后執行sql的業務代碼一般都分散在程序的不同地方,如何讓它們共享一個jdbc connection呢?這里spring做了一個前提假設:即一個事務的操作一定是在一個thread中執行,且一個thread中如果有多個不同jdbc connection生成的事務的話,他們必須順序執行,不能同時存在。(這個假設在絕大多數情況下都是成立的)。基於這個假設,spring在transaction創建時,會用ThreadLocal把創建這個事務的jdbc connection綁定到當前thread,接下來在事務的整個生命周期中都會從ThreadLocal中獲取同一個jdbc connection。
我們看一下詳細調用過程

- TransactionSynchronizationManager負責從ThreadLocal中存取jdbc connection
- 創建事務的時候會通過dataSource.getConnection()獲取一個新的jdbc connection,然后綁定到ThreadLocal
- 在業務代碼中執行sql時,通過DataSourceUtils.getConnection()從ThreadLocal中獲取當前事務的jdbc connection, 然后在該jdbc connection上執行sql
- commit和rollback事務時,從ThreadLocal中獲取當前事務的jdbc connection,然后對該jdbc connection進行commit和rollback
對spring jdbc的事務處理有了了解后,我們來看mybatis是如何通過spring處理事務的。
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="transactionFactory"> <bean class="org.apache.ibatis.spring.transaction.SpringManagedTransactionFactory" /> </property> </bean> <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg index="0" ref="sqlSessionFactory" /> </bean>
- mybatis-spring依賴DataSourceTransactionManager來處理事務,並沒有創建自己的PlatformTransactionManager實現。
- mybatis通過SqlSessionFactoryBuilder創建SqlSessionFactory,而mybatis-spring通過SqlSessionFactoryBean創建SqlSessionFactory。
- 配置使用SpringManagedTransactionFactory來創建MyBatis的Transaction實現SpringManagedTransaction
- 配置使用SqlSessionTemplate代替通過SqlSessionFactory.openSession()獲取SqlSession
然后看其調用過程
可以看到mybatis-spring處理事務的主要流程和spring jdbc處理事務並沒有什么區別,都是通過DataSourceTransactionManager的getTransaction(), rollback(), commit()完成事務的生命周期管理,而且jdbc connection的創建也是通過DataSourceTransactionManager.getTransaction()完成,mybatis並沒有參與其中,mybatis只是在執行sql時通過DataSourceUtils.getConnection()獲得當前thread的jdbc connection,然后在其上執行sql。
下面結合代碼來看
<SqlSessionUtils>: public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED); notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED); SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); SqlSession session = sessionHolder(executorType, holder); if (session != null) { return session; } if (LOGGER.isDebugEnabled()) { LOGGER.debug("Creating a new SqlSession"); } session = sessionFactory.openSession(executorType); 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) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Registering transaction synchronization for SqlSession [" + session + "]"); } holder = new SqlSessionHolder(session, executorType, exceptionTranslator); TransactionSynchronizationManager.bindResource(sessionFactory, holder); TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory)); holder.setSynchronizedWithTransaction(true); holder.requested(); } else { if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because DataSource is not transactional"); } } else { throw new TransientDataAccessResourceException( "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization"); } } } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because synchronization is not active"); } } }
執行sql時調用sqlSessionTemplate的insert,update,delete方法,sqlSessionTemplate是DefaultSqlSession的一個代理類,它通過SqlSessionUtils.getSqlSession()試圖從ThreadLocal獲取當前事務所使用的SqlSession。如果是第一次獲取時會調用SqlSessionFactory.openSession()創建一個SqlSession並綁定到ThreadLocal,同時還會通過TransactionSynchronizationManager注冊一個SqlSessionSynchronization。
<SqlSessionSynchronization>: public void beforeCommit(boolean readOnly) { // Connection commit or rollback will be handled by ConnectionSynchronization or // DataSourceTransactionManager. // But, do cleanup the SqlSession / Executor, including flushing BATCH statements so // they are actually executed. // SpringManagedTransaction will no-op the commit over the jdbc connection // TODO This updates 2nd level caches but the tx may be rolledback later on! if (TransactionSynchronizationManager.isActualTransactionActive()) { try { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Transaction synchronization committing SqlSession [" + this.holder.getSqlSession() + "]"); } 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; } }
SqlSessionSynchronization是一個事務生命周期的callback接口,mybatis-spring通過SqlSessionSynchronization在事務提交和回滾前分別調用DefaultSqlSession.commit()和DefaultSqlSession.rollback()
<BaseExecutor>: 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(); } } } } public void clearLocalCache() { if (!closed) { localCache.clear(); localOutputParameterCache.clear(); } }
<SpringManagedTransaction>: this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource); public void commit() throws SQLException { if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Committing JDBC Connection [" + this.connection + "]"); } this.connection.commit(); } } public void rollback() throws SQLException { if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Rolling back JDBC Connection [" + this.connection + "]"); } this.connection.rollback(); } }
<DataSourceUtils>: /** * Determine whether the given JDBC Connection is transactional, that is, * bound to the current thread by Spring's transaction facilities. * @param con the Connection to check * @param dataSource the DataSource that the Connection was obtained from * (may be {@code null}) * @return whether the Connection is transactional */ public static boolean isConnectionTransactional(Connection con, DataSource dataSource) { if (dataSource == null) { return false; } ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource); return (conHolder != null && connectionEquals(conHolder, con)); }
這里的DefaultSqlSession只會進行一些自身緩存的清理工作,並不會真正提交事務給數據庫,原因是這里的DefaultSqlSession使用的Transaction實現為SpringManagedTransaction,SpringManagedTransaction在提交事務前會檢查當前事務是否應該由spring控制,如果是,則不會自己提交事務,而將提交事務的任務交給spring,所以DefaultSqlSession並不會自己處理事務。
<SpringManagedTransaction>: public Connection getConnection() throws SQLException { if (this.connection == null) { openConnection(); } return this.connection; } /** * 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 { this.connection = DataSourceUtils.getConnection(this.dataSource); this.autoCommit = this.connection.getAutoCommit(); this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource); if (LOGGER.isDebugEnabled()) { LOGGER.debug( "JDBC Connection [" + this.connection + "] will" + (this.isConnectionTransactional ? " " : " not ") + "be managed by Spring"); } }
DefaultSqlSession執行sql時,會通過SpringManagedTransaction調用DataSourceUtils.getConnection()從ThreadLocal中獲取jdbc connection並在其上執行sql。
總結:mybatis-spring處理事務的主要流程和spring jdbc處理事務並沒有什么區別,都是通過DataSourceTransactionManager的getTransaction(), rollback(), commit()完成事務的生命周期管理,而且jdbc connection的創建也是通過DataSourceTransactionManager.getTransaction()完成,mybatis並沒有參與其中,mybatis只是在執行sql時通過DataSourceUtils.getConnection()獲得當前thread的jdbc connection,然后在其上執行sql。
mybatis-spring做的最主要的事情是:
- 在SqlSession執行sql時通過用SpringManagedTransaction代替mybatis的JdbcTransaction,讓SqlSession從spring的ThreadLocal中獲取jdbc connection。
- 通過注冊事務生命周期callback接口SqlSessionSynchronization,讓SqlSession有機會在spring管理的事務提交或回滾時清理自己的內部緩存。