美文网首页druid 源码之旅Druid收藏
[druid 源码解析] 6 执行SQL

[druid 源码解析] 6 执行SQL

作者: AndyWei123 | 来源:发表于2021-11-14 11:38 被阅读0次

    我们今天来解析一下一个简单的 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 <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 然后调用 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

        本文链接:https://www.haomeiwen.com/subject/csbttrtx.html