mybatis原始碼 (五) —— mybatis的事務如何被spring管理
要想使用spring的事務,要加入mybatis-spring依賴包
<!-- 引用外掛依賴:MyBatis整合Spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
配置檔案:
<?xml version="1.0" encoding="UTF-8"?>
<context:property-placeholder location="classpath:properties/*.properties" />
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="driverClassName" value="${jdbc.driver}" />
<property name="maxActive" value="10" />
<property name="minIdle" value="5" />
</bean>
<!-- sqlsessionfactory -->
<!-- 讓spring管理sqlsessionfactory 使用mybatis和spring整合包中的 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 資料庫連線池 -->
<property name="dataSource" ref="dataSource" />
<!-- 載入mybatis的全域性配置檔案 -->
<property name="configLocation" value="classpath:mybatis/SqlMapConfig.xml" />
</bean>
<!-- mapper掃描器 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.taotao.mapper" />
</bean>
配置sqlSessionFactory給spring來管理
SqlSessionFactoryBean這是一個FactoryBean相信讀過spring原始碼的都知道
org.mybatis.spring.SqlSessionFactoryBean#getObject
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
//sqlSessionFactory沒有構建好,那麼就呼叫afterPropertiesSet
afterPropertiesSet();
}
//返回構建好的sqlSessionFactory
return this.sqlSessionFactory;
}
org.mybatis.spring.SqlSessionFactoryBean#afterPropertiesSet
@Override
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
.....
//構建SqlSessionFactory
this.sqlSessionFactory = buildSqlSessionFactory();
}
org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
.....
if (this.transactionFactory == null) {
this.transactionFactory = new SpringManagedTransactionFactory();
}
configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
......
return this.sqlSessionFactoryBuilder.build(configuration);
}
這段程式碼可以看出transactionFactory 等於null的時候回去建立一個SpringMngTransFactory
並且把transFactory設定給Environment然後再把Environment設定給configuration,最後
sqlSessionFactoryBuilder.build(configuration);返回sqlSessionFactory
在DefaultSqlSessionFactory#openSessionFromConnection方法中建立了Transaction
private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
try {
boolean autoCommit;
try {
autoCommit = connection.getAutoCommit();
} catch (SQLException e) {
// Failover to true, as most poor drivers
// or databases won't support transactions
autoCommit = true;
}
//從configuration中取出environment物件
final Environment environment = configuration.getEnvironment();
//從environment中取出TransactionFactory
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
//建立Transaction
final Transaction tx = transactionFactory.newTransaction(connection);
//建立包含事務操作的執行器
final Executor executor = configuration.newExecutor(tx, execType);
//構建包含執行器的SqlSession
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
DefaultSqlSessionFactory#getTransactionFactoryFromEnvironment
private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
//如果沒有配置事務工廠,則返回託管事務工廠
if (environment == null || environment.getTransactionFactory() == null) {
return new ManagedTransactionFactory();
}
return environment.getTransactionFactory();
}
這裡返回SpringManagedTransactionFactory物件,然後呼叫
SpringManagedTransactionFactory#newTransaction(javax.sql.DataSource, org.apache.ibatis.session.TransactionIsolationLevel, boolean)
@Override
public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
return new SpringManagedTransaction(dataSource);
}
也就是說mybatis的執行事務的事務管理器就切換成了SpringManagedTransaction
我們再看org.mybatis.spring.transaction.SpringManagedTransaction#getConnection
@Override
public Connection getConnection() throws SQLException {
if (this.connection == null) {
//獲取connection
openConnection();
}
return this.connection;
}
private void openConnection() throws SQLException {
//呼叫DataSourceUtils 拿到資料庫連線
this.connection = DataSourceUtils.getConnection(this.dataSource);
this.autoCommit = this.connection.getAutoCommit();
this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
.....
org.springframework.jdbc.datasource.DataSourceUtils#getConnection
public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
try {
return doGetConnection(dataSource);
}
catch (SQLException ex) {
throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex);
}
}
此處進入spring-jdbc的模組程式碼,猜想:
- spring的@trans事務操作的conn會儲存在一個ThreadLocal中
- 當mybatis操作資料庫時從這個ThreadLocal中去取conn,這樣就可以做到spring來控制mybatis的資料庫操作了
下面我們就來驗證我們的猜想:
org.springframework.jdbc.datasource.DataSourceUtils#doGetConnection
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
Assert.notNull(dataSource, "No DataSource specified");
//獲取ConnectionHolder
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
/**
* conHolder不為空 有連結
*/
if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
conHolder.requested();
//如果conHolder沒有連線
if (!conHolder.hasConnection()) {
logger.debug("Fetching resumed JDBC Connection from DataSource");
//取出一個連線設定給conHolder
conHolder.setConnection(fetchConnection(dataSource));
}
//返回conHolder中的連線
return conHolder.getConnection();
}
.....
想看怎麼獲取connHolder
org.springframework.transaction.support.TransactionSynchronizationManager#getResource
@Nullable
public static Object getResource(Object key) {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
//獲取ConnectionHolder
Object value = doGetResource(actualKey);
....
return value;
}
//儲存資料庫連線的ThreadLocal
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
@Nullable
private static Object doGetResource(Object actualKey) {
/**
* 從threadlocal <Map<Object, Object>>中取出來當前執行緒繫結的map
* map裡面存的是<dataSource,ConnectionHolder>
*/
Map<Object, Object> map = resources.get();
if (map == null) {
return null;
}
//map中取出來對應dataSource的ConnectionHolder
Object value = map.get(actualKey);
// Transparently remove ResourceHolder that was marked as void...
if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
map.remove(actualKey);
// Remove entire ThreadLocal if empty...
if (map.isEmpty()) {
resources.remove();
}
value = null;
}
return value;
}
可以看到確實是從ThreadLocal中取出來的conn,而spring自己的事務也是操作的這個ThreadLocal中的conn來進行事務的開啟和回滾
然後就取出來connHolder中的conn返回,當取出來的conn為空時候,呼叫
org.springframework.jdbc.datasource.DataSourceUtils#fetchConnection
private static Connection fetchConnection(DataSource dataSource) throws SQLException {
//從資料來源取出來conn
Connection con = dataSource.getConnection();
if (con == null) {
throw new IllegalStateException("DataSource returned null from getConnection(): " + dataSource);
}
return con;
}
然後把從資料來源取出來的連線返回
到此mybatis的事務是怎麼被spring管理的就顯而易見了