[druid 源码解析] 6 执行SQL

我们今天来解析一下一个简单的 select SQL 在我们的系统的流转流程。我们知道,执行SQL主要的流程是:开启事务 -> 生成 PrepareStatement -> 将产生填充到 PrepareStatement 中并执行得到返回结果 -> 提交事务 (假如过程中遇到错误就进行回滚操作)。
我们这次使用注解事务 @Transactional 注解来测试这个场景。我们知道使用注解事务都是通过 Aop 生成代理对象实现的,我们先看一下代理对象执行了哪些操作。主要看 TransactionInterceptorinvoke 方法。最终执行了 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;
        }
    }

这里省略了部分代码,其主要的流程如下:

  1. 获取 @Transcaction 注解的相关属性。
  2. 通过 @Transaction 注解构建一个 TransactionManager 这里其实和我们手动使用 TransactionManager 类似。
  3. 通过 createTransactionIfNecessary(ptm, txAttr, joinpointIdentification); 方法创建 Transaction ,这里最终还是调用了 TransactionManagergetTransaction 方法获取。这里就会涉及到 Transaction 的七个传播级别 。
  • PROPAGATION_REQUIRED :默认的spring事务传播级别,如果上下文中已经存在事务,那么就加入到事务中执行,如果当前上下文中不存在事务,则新建事务执行。
  • PROPAGATION_SUPPORTS 如果上下文存在事务,则支持事务加入事务,如果没有事务,则使用非事务的方式执行。
  • PROPAGATION_MANDATORY 该级别的事务要求上下文中必须要存在事务,否则就会抛出异常!
  • PROPAGATION_REQUIRES_NEW 每次都要一个新事务
  • PROPAGATION_NOT_SUPPORTED 当前级别的特点就是上下文中存在事务,则挂起事务,执行当前逻辑,结束后恢复上下文的事务。
  • PROPAGATION_NEVER 传播级别要求上下文中不能存在事务,一旦有事务,就抛出runtime异常。
  • PROPAGATION_NESTED 如果上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务。
    (这里需要看起来和 PROPAGATION_REQUIRED 主要区别是嵌套事务,嵌套是子事务套在父事务中执行,子事务是父事务的一部分,在进入子事务之前,父事务建立一个回滚点,叫save point,然后执行子事务,这个子事务的执行也算是父事务的一部分,然后子事务执行结束,父事务继续执行。如果子事务回滚,父事务不会回滚,父事务回滚,子事务也会跟着回滚)
  1. 当需要生成事务的时候调用了 DataSourceTransactionManagerdoBegin 方法开启事务。
  2. 执行真正的 SQL ,这里调用了 invocation.proceedWithInvocation(); 这里其实就是到了执行 Mybatis 生成的代理类。最后是调用到了 SimpleExecutorQuery 方法,如下:
  public  List 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 然后调用 prepareStatementquery 方法。
我们先来看一下获取 prepareStatment,最终其实还是调用了 DruidDatasourceprepareStatement 方法:

 @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;
    }
  1. 先检查当前连接的状态。
  2. 生成该 SQL 对应的 statement 的 key, 然后到 StatementPool 缓存中查找。这里面的 StatementPool 是一个 LRUCache。
  3. 生成 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。

  1. 最后回到了提交事务,调用了 commitTransactionAfterReturning 方法,最后调用到了DataSourceTransactionManagerdoCommit 方法如下:
    @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);
        }
    }

即调用了 connectioncommit 方法。

你可能感兴趣的:([druid 源码解析] 6 执行SQL)