本次源碼分析基于 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è)流程:
- 獲取 SQL 二進(jìn)字符數(shù)組
- 若存在攔截器,則調(diào)用攔截器的前置處理方法
preProcess
對(duì) SQL 進(jìn)行處理,并返回結(jié)果 - 發(fā)送查詢命令并返回結(jié)果集包頭(里面包含攔截器)
- 讀取結(jié)果集字段包和行數(shù)據(jù)包
- 若存在攔截器,則調(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í)序圖(無攔截器)
二、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
方法中前置攔截作用于兩處:
- 第一處作用于
NativeProtocol#sendQueryPacket
方法標(biāo)記為 ② 的地方 - 第二處作用于
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
方法中后置攔截作用于兩處:
- 第一處作用于
NativeProtocol#sendQueryPacket
方法標(biāo)記為 ⑤ 的地方 - 第二處作用于
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é)果集包頭