建议同时学习@Transaction, spring对事务的管理
如果你只是阅读源码,只是为了“背诵”、“说出”它的过程,却不知道其原理,那过后就忘记了,你也不会明白源码的真正结构。
mybatis, 连接池与spring的事务管理配合, 共同使原本复杂的基于多句jdbc的调用, 变成只需对Mapper的一句调用即可完成. 它们是对jdbc的封装.
Mybatis在@Transactional中的精髓在于利用TransactionSynchronizationManager的ThreadLocal变量(resources)维持各个线程的DefaultSqlSession
在你调用Mapper方法时, 其实你调用的是实现了Mapper接口的代理对象, 该对象会将调用都转交给SqlSessionTemplate单例.
所以各个线程同时调用同一个Mapper时, 实际上SqlSessionTemplate也被同时调用了, 这就要求SqlSessionTemplate是线程安全的.
SqlSessionTemplate隐藏了DefaultSqlSession创建/释放/维持在ThreadLocal的细节.这样在同一个@Transactional下的多个Mapper调用中:
我们记得mybatis一级缓存是会话级别的, 由于在@Transactional下的多个Mapper调用实际用的是同一个DefaultSqlSession(也是同一个线程执行的, 因为线程是一路往下执行的), 所以这个一级缓存也会得以发挥作用.
在需要Mapper对象的Bean时, MapperFactoryBean被调用getObject并创建对应Mapper的代理对象.
status = tm.getTransaction(txAttr);
会一路调用DataSourceTransactionManager::doBegin Connection newCon = obtainDataSource().getConnection()
会从连接池取出连接, 并交给TransactionSynchronizationManager维护conn.setAutoCommit(false)
开启事务txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
->processCommit->doCommit会调用con.commit();
提交事务BaseExecutor::close
->SpringManagedTransaction::close
->releaseConnection
->doReleaseConnection
->doCloseConnection
中通过con.close();
关闭了连接ThreadLocal<Map<Object, Object>> resources
实现的, 该变量为每一个线程维护一个Map数据结构.附上下图是关于各个步骤所简化的地方.
2019-12-27 22-46-43 的屏幕截图.png
sqlSessionTemplate是全局共享的变量,所以应当保证线程安全.该变量由SqlSessionDaoSupport提供,
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
}
}
MapperFactoryBean继承了SqlSessionDaoSupport,在需要获取Mapper的Bean时,由父类含有的变量sqlSessionTemplate创建Mapper。
// MapperFactoryBean
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
// SqlSessionDaoSupport
public SqlSession getSqlSession() {
return this.sqlSessionTemplate;
}
SqlSessionInterceptor在invoke方法中调用了SqlSessionUtils::getSqlSession来获取SqlSession, 并调用了SqlSessionUtils::closeSqlSession来释放SqlSession.
getSqlSession的签名如下:
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator)
它要么借助TransactionSynchronizationManager来获取管理的SqlSession, 要么直接靠sessionFactory来创建一个SqlSession
SimpleExecutor.java中用到了该连接
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
BaseExecutor中
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
SpringManagedTransaction.java:
@Override
public Connection getConnection() throws SQLException {
if (this.connection == null) {
openConnection();
}
return this.connection;
}
debug到这里, connection会为null, 也就是说, SpringManagedTransaction一开始是没有设置Connection的, 但是没关系, 接下来我们调用openConnection设置这个连接. (该对象被构造出来时没设置Connection, 具体可以看第3章)
SpringManagedTransaction
private void openConnection() throws SQLException {
this.connection = DataSourceUtils.getConnection(this.dataSource);
this.autoCommit = this.connection.getAutoCommit();
this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
...
}
DataSourceUtils.java
public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
try {
return doGetConnection(dataSource);
}
catch (SQLException ex) {
...
}
}
DataSourceUtils.java
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
Assert.notNull(dataSource, "No DataSource specified");
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
...
}
在此从TransactionSynchronizationManager获取了线程级别的缓存的连接
在SqlSessionTemplate调用的SqlSessionUtils::getSession中
session = sessionFactory.openSession(executorType);
DefaultSqlSessionFactory.java
@Override
public SqlSession openSession(ExecutorType execType) {
return openSessionFromDataSource(execType, null, false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
...
} catch (Exception e) {
...
}
}
SpringManagedTransactionFactory::newTransaction
@Override
public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
return new SpringManagedTransaction(dataSource);
}
SpringManagedTransaction.java
public SpringManagedTransaction(DataSource dataSource) {
notNull(dataSource, "No DataSource specified");
this.dataSource = dataSource;
}
可见, SpringManagedTransaction构造出来时并没有设置Connection.
在
private void openConnection() throws SQLException {
this.connection = DataSourceUtils.getConnection(this.dataSource);
this.autoCommit = this.connection.getAutoCommit();
this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
中:
sqlSession.commit(true);
把它给commit了?
我的解答:
实际上我提了个不是问题的问题. 因为仔细想想, 线程只是会在代码上向前执行, 依次将两个mapper方法执行完毕. 不存在某个mapper方法在一个线程上执行后, 之后的mapper方法又交给另一个线程执行的情况.