mysql-connector-j SQL 請(qǐng)求執(zhí)行過程梳理

本次源碼分析基于 mysql-connector-8.0.20

一、SQL 請(qǐng)求調(diào)用鏈路跟蹤

當(dāng)上層應(yīng)用或 ORM 框架調(diào)用 PreparedStatement#execute 方法時(shí),會(huì)直接調(diào)用 mysql-connector-j 包中 ClientPreparedStatement#execute 方法,從此處開始正式進(jìn)入 MySQL 驅(qū)動(dòng)程序的邏輯。后續(xù)的源碼我會(huì)進(jìn)行相應(yīng)的簡化,突出重點(diǎn),方便理解

1.1 ClientPreparedStatement:SQL 請(qǐng)求入口

// ClientPreparedStatement.class
@Override
public boolean execute() throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
        JdbcConnection locallyScopedConn = this.connection;
        ResultSetInternalMethods rs = null;
        Message sendPacket = ((PreparedQuery<?>) this.query).fillSendPacket();
        rs = executeInternal(this.maxRows, sendPacket, createStreamingResultSet(),
                (((PreparedQuery<?>) this.query).getParseInfo().getFirstStmtChar() == 'S'), cachedMetadata, false);
        if (rs != null) {
            this.results = rs;
        }
        return ((rs != null) && rs.hasRows());
    }
}

我們可以看到獲取 ResultSet 的詳細(xì)過程在 ClientPreparedStatement#executeInternal 方法中

// ClientPreparedStatement.class
protected <M extends Message> ResultSetInternalMethods executeInternal(int maxRowsToRetrieve, M sendPacket, boolean createStreamingResultSet,
        boolean queryIsSelectOnly, ColumnDefinition metadata, boolean isBatch) throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
        JdbcConnection locallyScopedConnection = this.connection;
        ((PreparedQuery<?>) this.query).getQueryBindings()
                .setNumberOfExecutions(((PreparedQuery<?>) this.query).getQueryBindings().getNumberOfExecutions() + 1);
        ResultSetInternalMethods rs = ((NativeSession) locallyScopedConnection.getSession()).execSQL(this, null, maxRowsToRetrieve, (NativePacketPayload) sendPacket, createStreamingResultSet, getResultSetFactory(), metadata, isBatch);
        return rs;
    }
}

核心過程在 NativeSession#execSQL 方法中

// NativeSession.class
public <T extends Resultset> T execSQL(Query callingQuery, String query, int maxRows, NativePacketPayload packet, boolean streamResults,
        ProtocolEntityFactory<T, NativePacketPayload> resultSetFactory, ColumnDefinition cachedMetadata, boolean isBatch) {
    return packet == null ? 
        ((NativeProtocol) this.protocol).sendQueryString(callingQuery, query, this.characterEncoding.getValue(), maxRows, streamResults, cachedMetadata, resultSetFactory) : 
        ((NativeProtocol) this.protocol).sendQueryPacket(callingQuery, packet, maxRows, streamResults, cachedMetadata, resultSetFactory);
}

可以看到在 execSQL 中,若命令包不為 null,則執(zhí)行 NativeProtocol#sendQueryPacket 方法

1.2 NativeProtocol:執(zhí)行 SQL 請(qǐng)求并返回結(jié)果

public final <T extends Resultset> T sendQueryPacket(Query callingQuery, NativePacketPayload queryPacket, int maxRows, boolean streamResults,
        ColumnDefinition cachedMetadata, ProtocolEntityFactory<T, NativePacketPayload> resultSetFactory) throws IOException {

    final long queryStartTime = getCurrentTimeNanosOrMillis();

    this.statementExecutionDepth++;
    // 1 獲取 SQL 二進(jìn)字符數(shù)組
    byte[] queryBuf = queryPacket.getByteBuffer();
    int oldPacketPosition = queryPacket.getPosition(); // save the packet position

    LazyString query = new LazyString(queryBuf, 1, (oldPacketPosition - 1));

    try {
        // 2 若存在攔截器,則調(diào)用攔截器的前置處理方法 preProcess 對(duì) SQL 進(jìn)行處理,并返回結(jié)果
        if (this.queryInterceptors != null) {
            // 若攔截器 preProcess 方法返回非 null,則直接返回?cái)r截處理后的 Resultset
            T interceptedResults = invokeQueryInterceptorsPre(query, callingQuery, false);
            if (interceptedResults != null) {
                return interceptedResults;
            }
        }

        // 3 Send query command and sql query string
        NativePacketPayload resultPacket = sendCommand(queryPacket, false, 0);

        // 4 讀取 SQL 執(zhí)行結(jié)果
        T rs = readAllResults(maxRows, streamResults, resultPacket, false, cachedMetadata, resultSetFactory);
        
        // 5 若存在攔截器,則調(diào)用攔截器的后置處理方法 postProcess 對(duì) SQL、 Resultset 等進(jìn)行處理,并返回結(jié)果
        if (this.queryInterceptors != null) {
            rs = invokeQueryInterceptorsPost(query, callingQuery, rs, false);
        }

        return rs;

    } catch (CJException sqlEx) {
        if (this.queryInterceptors != null) {
            // TODO why doing this?
            // we don't do anything with the result set in this case
            invokeQueryInterceptorsPost(query, callingQuery, null, false); 
        }

        if (callingQuery != null) {
            callingQuery.checkCancelTimeout();
        }

        throw sqlEx;

    } finally {
        this.statementExecutionDepth--;
    }
}

NativeProtocol#sendQueryPacket 主要有 5 個(gè)流程:

  1. 獲取 SQL 二進(jìn)字符數(shù)組
  2. 若存在攔截器,則調(diào)用攔截器的前置處理方法 preProcess 對(duì) SQL 進(jìn)行處理,并返回結(jié)果
  3. 發(fā)送查詢命令并返回結(jié)果集包頭(里面包含攔截器)
  4. 讀取結(jié)果集字段包和行數(shù)據(jù)包
  5. 若存在攔截器,則調(diào)用攔截器的后置處理方法 postProcess 對(duì) SQL、 結(jié)果集等進(jìn)行攔截處理,并返回?cái)r截處理后的結(jié)果

我們此處先重點(diǎn)分析不存在攔截器的情況,攔截器的部分將在后續(xù)章節(jié)中詳述

1.2.1 發(fā)送 SQl 命令包,獲取結(jié)果集包頭

// NativeProtocol.class
@Override
public final NativePacketPayload sendCommand(Message queryPacket, boolean skipCheck, int timeoutMillis) {
    // 1 若存在攔截器,則調(diào)用攔截器的后置處理方法 preProcess(M queryPacket) 對(duì) SQL 命令執(zhí)行包進(jìn)行處理并返回 SQL 命令執(zhí)行包
    if (this.queryInterceptors != null) {
        NativePacketPayload interceptedPacketPayload = (NativePacketPayload) invokeQueryInterceptorsPre(queryPacket, false);
        if (interceptedPacketPayload != null) {
            return interceptedPacketPayload;
        }
    }
    // 2 發(fā)送 SQL 命令執(zhí)行包
    send(queryPacket, queryPacket.getPosition());
    // 3 Checks for errors in the reply packet, and if none, returns the reply packet, ready for reading
    returnPacket = checkErrorMessage(command);
    // 4 若存在攔截器,則調(diào)用攔截器的后置處理方法 postProcess(M queryPacket, M originalResponsePacket) 進(jìn)行攔截處理并返回結(jié)果集包頭
    if (this.queryInterceptors != null) {
        returnPacket = (NativePacketPayload) invokeQueryInterceptorsPost(queryPacket, returnPacket, false);
    }
    return returnPacket;
}

1.2.2 讀取結(jié)果集包的字段包和行數(shù)據(jù)包

// NativeProtocol.class
public <T extends Resultset> T readAllResults(int maxRows, boolean streamResults, NativePacketPayload resultPacket, boolean isBinaryEncoded,
        ColumnDefinition metadata, ProtocolEntityFactory<T, NativePacketPayload> resultSetFactory) throws IOException {

    resultPacket.setPosition(0);
    T topLevelResultSet = read(Resultset.class, maxRows, streamResults, resultPacket, isBinaryEncoded, metadata, resultSetFactory);

    if (this.serverSession.hasMoreResults()) {
        T currentResultSet = topLevelResultSet;
        if (streamResults) {
            currentResultSet = readNextResultset(currentResultSet, maxRows, true, isBinaryEncoded, resultSetFactory);
        } else {
            while (this.serverSession.hasMoreResults()) {
                currentResultSet = readNextResultset(currentResultSet, maxRows, false, isBinaryEncoded, resultSetFactory);
            }
            clearInputStream();
        }
    }

    if (this.hadWarnings) {
        scanForAndThrowDataTruncation();
    }

    reclaimLargeReusablePacket();
    return topLevelResultSet;
}

1.3 SQL 請(qǐng)求執(zhí)行時(shí)序圖(無攔截器)

SQL 請(qǐng)求執(zhí)行時(shí)序圖(無攔截器)

二、QueryInterceptor

mysql-connector-j 提供了 QueryInterceptor 接口供我們對(duì) SQL 請(qǐng)求進(jìn)行攔截處理,接口定義如下:

public interface QueryInterceptor {

    /**
     * Called once per connection that wants to use the interceptor
     * 
     * The properties are the same ones passed in in the URL or arguments to
     * Driver.connect() or DriverManager.getConnection().
     * 
     * @param conn
     *            the connection for which this interceptor is being created
     * @param props
     *            configuration values as passed to the connection. Note that
     *            in order to support javax.sql.DataSources, configuration properties specific
     *            to an interceptor <strong>must</strong> be passed via setURL() on the
     *            DataSource. QueryInterceptor properties are not exposed via
     *            accessor/mutator methods on DataSources.
     * @param log
     *            logger
     * @return {@link QueryInterceptor}
     */
    QueryInterceptor init(MysqlConnection conn, Properties props, Log log);

    /**
     * Called before the given query is going to be sent to the server for processing.
     * 
     * Interceptors are free to return a result set (which must implement the
     * interface {@link Resultset}), and if so,
     * the server will not execute the query, and the given result set will be
     * returned to the application instead.
     * 
     * This method will be called while the connection-level mutex is held, so
     * it will only be called from one thread at a time.
     * 
     * @param sql
     *            the Supplier for SQL representation of the query
     * @param interceptedQuery
     *            the actual {@link Query} instance being intercepted
     * @param <T>
     *            {@link Resultset} object
     * 
     * @return a {@link Resultset} that should be returned to the application instead
     *         of results that are created from actual execution of the intercepted
     *         query.
     */
    <T extends Resultset> T preProcess(Supplier<String> sql, Query interceptedQuery);

    /**
     * Called before the given query packet is going to be sent to the server for processing.
     * 
     * Interceptors are free to return a PacketPayload, and if so,
     * the server will not execute the query, and the given PacketPayload will be
     * returned to the application instead.
     * 
     * This method will be called while the connection-level mutex is held, so
     * it will only be called from one thread at a time.
     * 
     * @param queryPacket
     *            original {@link Message}
     * @param <M>
     *            {@link Message} object
     * @return processed {@link Message}
     */
    default <M extends Message> M preProcess(M queryPacket) {
        return null;
    }

    /**
     * Should the driver execute this interceptor only for the
     * "original" top-level query, and not put it in the execution
     * path for queries that may be executed from other interceptors?
     * 
     * If an interceptor issues queries using the connection it was created for,
     * and does not return <code>true</code> for this method, it must ensure
     * that it does not cause infinite recursion.
     * 
     * @return true if the driver should ensure that this interceptor is only
     *         executed for the top-level "original" query.
     */
    boolean executeTopLevelOnly();

    /**
     * Called by the driver when this extension should release any resources
     * it is holding and cleanup internally before the connection is
     * closed.
     */
    void destroy();

    /**
     * Called after the given query has been sent to the server for processing.
     * 
     * Interceptors are free to inspect the "original" result set, and if a
     * different result set is returned by the interceptor, it is used in place
     * of the "original" result set.
     * 
     * This method will be called while the connection-level mutex is held, so
     * it will only be called from one thread at a time.
     * 
     * @param sql
     *            the Supplier for SQL representation of the query
     * @param interceptedQuery
     *            the actual {@link Query} instance being intercepted
     * @param originalResultSet
     *            a {@link Resultset} created from query execution
     * @param serverSession
     *            {@link ServerSession} object after the query execution
     * @param <T>
     *            {@link Resultset} object
     * 
     * @return a {@link Resultset} that should be returned to the application instead
     *         of results that are created from actual execution of the intercepted
     *         query.
     */
    <T extends Resultset> T postProcess(Supplier<String> sql, Query interceptedQuery, T originalResultSet, ServerSession serverSession);

    /**
     * Called after the given query packet has been sent to the server for processing.
     * 
     * Interceptors are free to return either a different PacketPayload than the originalResponsePacket or null.
     * 
     * This method will be called while the connection-level mutex is held, so
     * it will only be called from one thread at a time.
     * 
     * @param queryPacket
     *            query {@link Message}
     * @param originalResponsePacket
     *            response {@link Message}
     * @param <M>
     *            {@link Message} object
     * @return {@link Message}
     */
    default <M extends Message> M postProcess(M queryPacket, M originalResponsePacket) {
        return null;
    }
}

在自定義攔截器時(shí),我們可以重寫一下 4 個(gè)方法,這四個(gè)方法分為兩組,分別處理前置攔截邏輯和后置攔截邏輯

<T extends Resultset> T preProcess(Supplier<String> sql, Query interceptedQuery);
<M extends Message> M preProcess(M queryPacket);
<T extends Resultset> T postProcess(Supplier<String> sql, Query interceptedQuery, T originalResultSet, ServerSession serverSession);
<M extends Message> M postProcess(M queryPacket, M originalResponsePacket);

由于 PreparedStatement#execute 調(diào)用時(shí)獲取數(shù)據(jù)庫連接級(jí)別的鎖:synchronized (checkClosed().getConnectionMutex()),故這四個(gè)方法是線程安全的

sendQueryPacket

NativeProtocol#sendQueryPacket 方法中前置攔截作用于兩處:

public final <T extends Resultset> T sendQueryPacket(Query callingQuery, NativePacketPayload queryPacket, int maxRows, boolean streamResults,
        ColumnDefinition cachedMetadata, ProtocolEntityFactory<T, NativePacketPayload> resultSetFactory) throws IOException {

    final long queryStartTime = getCurrentTimeNanosOrMillis();

    this.statementExecutionDepth++;
    // 1 獲取 SQL 二進(jìn)字符數(shù)組
    byte[] queryBuf = queryPacket.getByteBuffer();
    int oldPacketPosition = queryPacket.getPosition(); // save the packet position

    LazyString query = new LazyString(queryBuf, 1, (oldPacketPosition - 1));

    try {
        // 2 若存在攔截器,則調(diào)用攔截器的前置處理方法 preProcess 對(duì) SQL 進(jìn)行處理,并返回結(jié)果
        if (this.queryInterceptors != null) {
            // 若攔截器 preProcess 方法返回非 null,則直接返回?cái)r截處理后的 Resultset
            T interceptedResults = invokeQueryInterceptorsPre(query, callingQuery, false);
            if (interceptedResults != null) {
                return interceptedResults;
            }
        }

        // 3 Send query command and sql query string
        NativePacketPayload resultPacket = sendCommand(queryPacket, false, 0);

        // 4 讀取 SQL 執(zhí)行結(jié)果
        T rs = readAllResults(maxRows, streamResults, resultPacket, false, cachedMetadata, resultSetFactory);

        // 5 若存在攔截器,則調(diào)用攔截器的后置處理方法 postProcess 對(duì) SQL、 Resultset 等進(jìn)行處理,并返回結(jié)果
        if (this.queryInterceptors != null) {
            rs = invokeQueryInterceptorsPost(query, callingQuery, rs, false);
        }

        return rs;

    } catch (CJException sqlEx) {
        if (this.queryInterceptors != null) {
            // TODO why doing this?
            // we don't do anything with the result set in this case
            invokeQueryInterceptorsPost(query, callingQuery, null, false);
        }

        if (callingQuery != null) {
            callingQuery.checkCancelTimeout();
        }

        throw sqlEx;

    } finally {
        this.statementExecutionDepth--;
    }
}

sendCommand

public final NativePacketPayload sendCommand(Message queryPacket, boolean skipCheck, int timeoutMillis) {
    // 1 若存在攔截器,則調(diào)用攔截器的后置處理方法 preProcess(M queryPacket) 對(duì) SQL 命令執(zhí)行包進(jìn)行處理并返回 SQL 命令執(zhí)行包
    if (this.queryInterceptors != null) {
        NativePacketPayload interceptedPacketPayload = (NativePacketPayload) invokeQueryInterceptorsPre(queryPacket, false);
        if (interceptedPacketPayload != null) {
            return interceptedPacketPayload;
        }
    }
    // 2 發(fā)送 SQL 命令執(zhí)行包
    send(queryPacket, queryPacket.getPosition());
    // 3 Checks for errors in the reply packet, and if none, returns the reply packet, ready for reading
    returnPacket = checkErrorMessage(command);
    // 4 若存在攔截器,則調(diào)用攔截器的后置處理方法 postProcess(M queryPacket, M originalResponsePacket) 進(jìn)行攔截處理并返回結(jié)果集包頭
    if (this.queryInterceptors != null) {
        returnPacket = (NativePacketPayload) invokeQueryInterceptorsPost(queryPacket, returnPacket, false);
    }
    return returnPacket;
}

2.1 前置攔截

NativeProtocol#sendQueryPacket 方法中前置攔截作用于兩處:

  1. 第一處作用于 NativeProtocol#sendQueryPacket 方法標(biāo)記為 ② 的地方
  2. 第二處作用于 NativeProtocol#sendQueryPacket 方法標(biāo)記為 ③ 的地方,內(nèi)部作用于 sendCommand 方法中標(biāo)記為 ① 的地方

2.1.1 第一處攔截

第一處攔截的核心邏輯于 NativeProtocol#invokeQueryInterceptorsPre 方法中

public <T extends Resultset> T invokeQueryInterceptorsPre(Supplier<String> sql, Query interceptedQuery, boolean forceExecute) {
    T previousResultSet = null;

    for (int i = 0, s = this.queryInterceptors.size(); i < s; i++) {
        QueryInterceptor interceptor = this.queryInterceptors.get(i);

        boolean executeTopLevelOnly = interceptor.executeTopLevelOnly();
        boolean shouldExecute = (executeTopLevelOnly && (this.statementExecutionDepth == 1 || forceExecute)) || (!executeTopLevelOnly);

        if (shouldExecute) {
            T interceptedResultSet = interceptor.preProcess(sql, interceptedQuery);

            if (interceptedResultSet != null) {
                previousResultSet = interceptedResultSet;
            }
        }
    }

    return previousResultSet;
}

此處前置攔截器可以攔截 SQL 請(qǐng)求,并直接生成結(jié)果集包返回。若 invokeQueryInterceptorsPre 方法返回 null,則接續(xù)執(zhí)行后續(xù) SQL 請(qǐng)求邏輯

2.1.2 第二處攔截

第二處攔截的核心邏輯于 NativeProtocol#invokeQueryInterceptorsPre 方法中

public <M extends Message> M invokeQueryInterceptorsPre(M queryPacket, boolean forceExecute) {
    M previousPacketPayload = null;

    for (int i = 0, s = this.queryInterceptors.size(); i < s; i++) {
        QueryInterceptor interceptor = this.queryInterceptors.get(i);
        M interceptedPacketPayload = interceptor.preProcess(queryPacket);
        if (interceptedPacketPayload != null) {
            previousPacketPayload = interceptedPacketPayload;
        }
    }

    return previousPacketPayload;
}

此處攔截 SQL 命令包,并調(diào)用 QueryInterceptor#preProcess 方法執(zhí)行攔截邏輯,可自定義返回結(jié)果集包頭。若 QueryInterceptor#preProcess 方法返回 null,則接續(xù)執(zhí)行后續(xù) SQL 請(qǐng)求邏輯,否則直接返回結(jié)果集包頭

2.2 后置攔截

NativeProtocol#sendQueryPacket 方法中后置攔截作用于兩處:

  1. 第一處作用于 NativeProtocol#sendQueryPacket 方法標(biāo)記為 ⑤ 的地方
  2. 第二處作用于 NativeProtocol#sendQueryPacket 方法標(biāo)記為 ③ 的地方,內(nèi)部作用于 sendCommand 方法中標(biāo)記為 ④ 的地方

2.2.1 第一處攔截

第一處攔截的核心邏輯于 NativeProtocol#invokeQueryInterceptorsPre 方法中

public <T extends Resultset> T invokeQueryInterceptorsPost(Supplier<String> sql, Query interceptedQuery, T originalResultSet, boolean forceExecute) {

    for (int i = 0, s = this.queryInterceptors.size(); i < s; i++) {
        QueryInterceptor interceptor = this.queryInterceptors.get(i);

        boolean executeTopLevelOnly = interceptor.executeTopLevelOnly();
        boolean shouldExecute = (executeTopLevelOnly && (this.statementExecutionDepth == 1 || forceExecute)) || (!executeTopLevelOnly);

        if (shouldExecute) {
            T interceptedResultSet = interceptor.postProcess(sql, interceptedQuery, originalResultSet, this.serverSession);

            if (interceptedResultSet != null) {
                originalResultSet = interceptedResultSet;
            }
        }
    }

    return originalResultSet;
}

此處 invokeQueryInterceptorsPost 方法調(diào)用 QueryInterceptor#postProcess 方法執(zhí)行攔截操作,若 QueryInterceptor#postProcess 方法返回 null,則接續(xù)執(zhí)行后續(xù) SQL 請(qǐng)求邏輯,否則直接返回?cái)r截處理后的結(jié)果集包

2.2.2 第二處攔截

第二處攔截的核心邏輯于 NativeProtocol#invokeQueryInterceptorsPre 方法中

public <M extends Message> M invokeQueryInterceptorsPost(M queryPacket, M originalResponsePacket, boolean forceExecute) {

    for (int i = 0, s = this.queryInterceptors.size(); i < s; i++) {
        QueryInterceptor interceptor = this.queryInterceptors.get(i);

        M interceptedPacketPayload = interceptor.postProcess(queryPacket, originalResponsePacket);
        if (interceptedPacketPayload != null) {
            originalResponsePacket = interceptedPacketPayload;
        }
    }

    return originalResponsePacket;
}

此處 invokeQueryInterceptorsPost 方法調(diào)用 QueryInterceptor#postProcess 方法執(zhí)行攔截操作,可自定義返回結(jié)果集包頭。若 QueryInterceptor#postProcess 方法返回 null,則接續(xù)執(zhí)行后續(xù) SQL 請(qǐng)求邏輯,否則直接返回結(jié)果集包頭

2.3 攔截器總結(jié)

攔截器流程圖
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。