我们今天来解析一下一个简单的 select
SQL 在我们的系统的流转流程。我们知道,执行SQL主要的流程是:开启事务 -> 生成 PrepareStatement
-> 将产生填充到 PrepareStatement
中并执行得到返回结果 -> 提交事务 (假如过程中遇到错误就进行回滚操作)。
我们这次使用注解事务 @Transactional
注解来测试这个场景。我们知道使用注解事务都是通过 Aop 生成代理对象实现的,我们先看一下代理对象执行了哪些操作。主要看 TransactionInterceptor
的 invoke
方法。最终执行了 invokeWithinTransaction
方法,具体如下:
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 TransactionManager tm = determineTransactionManager(txAttr);
PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
........
if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
// Standard transaction demarcation with getTransaction and commit/rollback calls.
TransactionInfo txInfo = createTransactionIfNecessary(ptm, 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);
}
if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {
// Set rollback-only in case of Vavr failure matching our rollback rules...
TransactionStatus status = txInfo.getTransactionStatus();
if (status != null && txAttr != null) {
retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
}
}
commitTransactionAfterReturning(txInfo);
return retVal;
}
.........
// Check result state: It might indicate a Throwable to rethrow.
if (throwableHolder.throwable != null) {
throw throwableHolder.throwable;
}
return result;
}
}
这里省略了部分代码,其主要的流程如下:
- 获取
@Transcaction
注解的相关属性。 - 通过
@Transaction
注解构建一个TransactionManager
这里其实和我们手动使用 TransactionManager 类似。 - 通过
createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
方法创建Transaction
,这里最终还是调用了TransactionManager
的getTransaction
方法获取。这里就会涉及到Transaction
的七个传播级别 。
- PROPAGATION_REQUIRED :默认的spring事务传播级别,如果上下文中已经存在事务,那么就加入到事务中执行,如果当前上下文中不存在事务,则新建事务执行。
- PROPAGATION_SUPPORTS 如果上下文存在事务,则支持事务加入事务,如果没有事务,则使用非事务的方式执行。
- PROPAGATION_MANDATORY 该级别的事务要求上下文中必须要存在事务,否则就会抛出异常!
- PROPAGATION_REQUIRES_NEW 每次都要一个新事务
- PROPAGATION_NOT_SUPPORTED 当前级别的特点就是上下文中存在事务,则挂起事务,执行当前逻辑,结束后恢复上下文的事务。
- PROPAGATION_NEVER 传播级别要求上下文中不能存在事务,一旦有事务,就抛出runtime异常。
- PROPAGATION_NESTED 如果上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务。
(这里需要看起来和 PROPAGATION_REQUIRED 主要区别是嵌套事务,嵌套是子事务套在父事务中执行,子事务是父事务的一部分,在进入子事务之前,父事务建立一个回滚点,叫save point,然后执行子事务,这个子事务的执行也算是父事务的一部分,然后子事务执行结束,父事务继续执行。如果子事务回滚,父事务不会回滚,父事务回滚,子事务也会跟着回滚)
- 当需要生成事务的时候调用了
DataSourceTransactionManager
的doBegin
方法开启事务。 - 执行真正的 SQL ,这里调用了
invocation.proceedWithInvocation();
这里其实就是到了执行 Mybatis 生成的代理类。最后是调用到了SimpleExecutor
的Query
方法,如下:
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
首先是先获取 prepareStatement
然后调用 prepareStatement
的 query
方法。
我们先来看一下获取 prepareStatment
,最终其实还是调用了 DruidDatasource
的 prepareStatement
方法:
@Override
public PreparedStatement prepareStatement(String sql) throws SQLException {
checkState();
PreparedStatementHolder stmtHolder = null;
PreparedStatementKey key = new PreparedStatementKey(sql, getCatalog(), MethodType.M1);
boolean poolPreparedStatements = holder.isPoolPreparedStatements();
if (poolPreparedStatements) {
stmtHolder = holder.getStatementPool().get(key);
}
if (stmtHolder == null) {
try {
stmtHolder = new PreparedStatementHolder(key, conn.prepareStatement(sql));
holder.getDataSource().incrementPreparedStatementCount();
} catch (SQLException ex) {
handleException(ex, sql);
}
}
initStatement(stmtHolder);
DruidPooledPreparedStatement rtnVal = new DruidPooledPreparedStatement(this, stmtHolder);
holder.addTrace(rtnVal);
return rtnVal;
}
- 先检查当前连接的状态。
- 生成该 SQL 对应的 statement 的 key, 然后到
StatementPool
缓存中查找。这里面的StatementPool
是一个 LRUCache。 - 生成
DruidPooledPreparedStatement
对象,DruidPooledPreparedStatement
对象并不是真正的PreparedStatement
她最终调用的是自己的一个成员变量 stmt 其实是PreparedStatementProxyImpl
,它里面才是真正持有JDBC4MysqlPreparedStatement
。我们看一下它的 execute 方法。
@Override
public boolean execute() throws SQLException {
updateCount = null;
lastExecuteSql = sql;
lastExecuteType = StatementExecuteType.Execute;
lastExecuteStartNano = -1L;
lastExecuteTimeNano = -1L;
firstResultSet = createChain().preparedStatement_execute(this);
return firstResultSet;
}
这里还需要经过FilterChainImpl
完成顾虑后才真正执行 SQL,这里也是我们之前提到的责任链模式,到最后才调用我们的的 JDBC4MysqlPreparedStatement
执行 excute。
- 最后回到了提交事务,调用了
commitTransactionAfterReturning
方法,最后调用到了DataSourceTransactionManager
的doCommit
方法如下:
@Override
protected void doCommit(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
if (status.isDebug()) {
logger.debug("Committing JDBC transaction on Connection [" + con + "]");
}
try {
con.commit();
}
catch (SQLException ex) {
throw new TransactionSystemException("Could not commit JDBC transaction", ex);
}
}
即调用了 connection
的 commit
方法。