共计 18064 个字符,预计需要花费 46 分钟才能阅读完成。
序
本文次要钻研一下 mysql jdbc 的 prepareStatement
prepareStatement
java/sql/Connection.java
/**
* Creates a <code>PreparedStatement</code> object for sending
* parameterized SQL statements to the database.
* <P>
* A SQL statement with or without IN parameters can be
* pre-compiled and stored in a <code>PreparedStatement</code> object. This
* object can then be used to efficiently execute this statement
* multiple times.
*
* <P><B>Note:</B> This method is optimized for handling
* parametric SQL statements that benefit from precompilation. If
* the driver supports precompilation,
* the method <code>prepareStatement</code> will send
* the statement to the database for precompilation. Some drivers
* may not support precompilation. In this case, the statement may
* not be sent to the database until the <code>PreparedStatement</code>
* object is executed. This has no direct effect on users; however, it does
* affect which methods throw certain <code>SQLException</code> objects.
* <P>
* Result sets created using the returned <code>PreparedStatement</code>
* object will by default be type <code>TYPE_FORWARD_ONLY</code>
* and have a concurrency level of <code>CONCUR_READ_ONLY</code>.
* The holdability of the created result sets can be determined by
* calling {@link #getHoldability}.
*
* @param sql an SQL statement that may contain one or more '?' IN
* parameter placeholders
* @return a new default <code>PreparedStatement</code> object containing the
* pre-compiled SQL statement
* @exception SQLException if a database access error occurs
* or this method is called on a closed connection
*/
PreparedStatement prepareStatement(String sql)
throws SQLException;
java.sql.Connection 定义了 prepareStatement 办法,依据 sql 创立 PreparedStatement
ConnectionImpl
mysql-connector-java-5.1.21-sources.jar!/com/mysql/jdbc/ConnectionImpl.java
/**
* A SQL statement with or without IN parameters can be pre-compiled and
* stored in a PreparedStatement object. This object can then be used to
* efficiently execute this statement multiple times.
* <p>
* <B>Note:</B> This method is optimized for handling parametric SQL
* statements that benefit from precompilation if the driver supports
* precompilation. In this case, the statement is not sent to the database
* until the PreparedStatement is executed. This has no direct effect on
* users; however it does affect which method throws certain
* java.sql.SQLExceptions
* </p>
* <p>
* MySQL does not support precompilation of statements, so they are handled
* by the driver.
* </p>
*
* @param sql
* a SQL statement that may contain one or more '?' IN parameter
* placeholders
* @return a new PreparedStatement object containing the pre-compiled
* statement.
* @exception SQLException
* if a database access error occurs.
*/
public java.sql.PreparedStatement prepareStatement(String sql)
throws SQLException {
return prepareStatement(sql, DEFAULT_RESULT_SET_TYPE,
DEFAULT_RESULT_SET_CONCURRENCY);
}
mysql jdbc 的 ConnectionImpl 实现了 prepareStatement 办法,依据正文,预编译次要是 driver 来解决
prepareStatement
mysql-connector-java-5.1.21-sources.jar!/com/mysql/jdbc/ConnectionImpl.java
/**
* JDBC 2.0 Same as prepareStatement() above, but allows the default result
* set type and result set concurrency type to be overridden.
*
* @param sql
* the SQL query containing place holders
* @param resultSetType
* a result set type, see ResultSet.TYPE_XXX
* @param resultSetConcurrency
* a concurrency type, see ResultSet.CONCUR_XXX
* @return a new PreparedStatement object containing the pre-compiled SQL
* statement
* @exception SQLException
* if a database-access error occurs.
*/
public synchronized java.sql.PreparedStatement prepareStatement(String sql,
int resultSetType, int resultSetConcurrency) throws SQLException {checkClosed();
//
// FIXME: Create warnings if can't create results of the given
// type or concurrency
//
PreparedStatement pStmt = null;
boolean canServerPrepare = true;
String nativeSql = getProcessEscapeCodesForPrepStmts() ? nativeSQL(sql): sql;
if (this.useServerPreparedStmts && getEmulateUnsupportedPstmts()) {canServerPrepare = canHandleAsServerPreparedStatement(nativeSql);
}
if (this.useServerPreparedStmts && canServerPrepare) {if (this.getCachePreparedStatements()) {synchronized (this.serverSideStatementCache) {pStmt = (com.mysql.jdbc.ServerPreparedStatement)this.serverSideStatementCache.remove(sql);
if (pStmt != null) {((com.mysql.jdbc.ServerPreparedStatement)pStmt).setClosed(false);
pStmt.clearParameters();}
if (pStmt == null) {
try {pStmt = ServerPreparedStatement.getInstance(getLoadBalanceSafeProxy(), nativeSql,
this.database, resultSetType, resultSetConcurrency);
if (sql.length() < getPreparedStatementCacheSqlLimit()) {((com.mysql.jdbc.ServerPreparedStatement)pStmt).isCached = true;
}
pStmt.setResultSetType(resultSetType);
pStmt.setResultSetConcurrency(resultSetConcurrency);
} catch (SQLException sqlEx) {
// Punt, if necessary
if (getEmulateUnsupportedPstmts()) {pStmt = (PreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false);
if (sql.length() < getPreparedStatementCacheSqlLimit()) {this.serverSideStatementCheckCache.put(sql, Boolean.FALSE);
}
} else {throw sqlEx;}
}
}
}
} else {
try {pStmt = ServerPreparedStatement.getInstance(getLoadBalanceSafeProxy(), nativeSql,
this.database, resultSetType, resultSetConcurrency);
pStmt.setResultSetType(resultSetType);
pStmt.setResultSetConcurrency(resultSetConcurrency);
} catch (SQLException sqlEx) {
// Punt, if necessary
if (getEmulateUnsupportedPstmts()) {pStmt = (PreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false);
} else {throw sqlEx;}
}
}
} else {pStmt = (PreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false);
}
return pStmt;
}
prepareStatement 首先依据 useServerPreparedStmts 以及 getEmulateUnsupportedPstmts 来判断是否要通过 canHandleAsServerPreparedStatement 判断 canServerPrepare;之后在 useServerPreparedStmts 及 canServerPrepare 为 true 时,依据 cachePreparedStatements 做 ServerPreparedStatement 的解决;如果不开启 serverPrepare 则执行 clientPrepareStatement
canHandleAsServerPreparedStatement
mysql-connector-java-5.1.21-sources.jar!/com/mysql/jdbc/ConnectionImpl.java
private boolean canHandleAsServerPreparedStatement(String sql)
throws SQLException {if (sql == null || sql.length() == 0) {return true;}
if (!this.useServerPreparedStmts) {return false;}
if (getCachePreparedStatements()) {synchronized (this.serverSideStatementCheckCache) {Boolean flag = (Boolean)this.serverSideStatementCheckCache.get(sql);
if (flag != null) {return flag.booleanValue();
}
boolean canHandle = canHandleAsServerPreparedStatementNoCache(sql);
if (sql.length() < getPreparedStatementCacheSqlLimit()) {
this.serverSideStatementCheckCache.put(sql,
canHandle ? Boolean.TRUE : Boolean.FALSE);
}
return canHandle;
}
}
return canHandleAsServerPreparedStatementNoCache(sql);
}
canHandleAsServerPreparedStatement 首先判断 useServerPreparedStmts,之后若 cachePreparedStatements 为 true 则做 serverSideStatementCheckCache 判断,最初都会通过 canHandleAsServerPreparedStatementNoCache 进行判断
canHandleAsServerPreparedStatementNoCache
mysql-connector-java-5.1.21-sources.jar!/com/mysql/jdbc/ConnectionImpl.java
private boolean canHandleAsServerPreparedStatementNoCache(String sql)
throws SQLException {
// Can't use server-side prepare for CALL
if (StringUtils.startsWithIgnoreCaseAndNonAlphaNumeric(sql, "CALL")) {return false;}
boolean canHandleAsStatement = true;
if (!versionMeetsMinimum(5, 0, 7) &&
(StringUtils.startsWithIgnoreCaseAndNonAlphaNumeric(sql, "SELECT")
|| StringUtils.startsWithIgnoreCaseAndNonAlphaNumeric(sql,
"DELETE")
|| StringUtils.startsWithIgnoreCaseAndNonAlphaNumeric(sql,
"INSERT")
|| StringUtils.startsWithIgnoreCaseAndNonAlphaNumeric(sql,
"UPDATE")
|| StringUtils.startsWithIgnoreCaseAndNonAlphaNumeric(sql,
"REPLACE"))) {// check for limit ?[,?]
/*
* The grammar for this (from the server) is: ULONG_NUM | ULONG_NUM
* ',' ULONG_NUM | ULONG_NUM OFFSET_SYM ULONG_NUM
*/
int currentPos = 0;
int statementLength = sql.length();
int lastPosToLook = statementLength - 7; // "LIMIT".length()
boolean allowBackslashEscapes = !this.noBackslashEscapes;
char quoteChar = this.useAnsiQuotes ? '"':'\'';
boolean foundLimitWithPlaceholder = false;
while (currentPos < lastPosToLook) {
int limitStart = StringUtils.indexOfIgnoreCaseRespectQuotes(
currentPos, sql, "LIMIT", quoteChar,
allowBackslashEscapes);
if (limitStart == -1) {break;}
currentPos = limitStart + 7;
while (currentPos < statementLength) {char c = sql.charAt(currentPos);
//
// Have we reached the end
// of what can be in a LIMIT clause?
//
if (!Character.isDigit(c) && !Character.isWhitespace(c)
&& c != ',' && c != '?') {break;}
if (c == '?') {
foundLimitWithPlaceholder = true;
break;
}
currentPos++;
}
}
canHandleAsStatement = !foundLimitWithPlaceholder;
} else if (StringUtils.startsWithIgnoreCaseAndWs(sql, "CREATE TABLE")) {canHandleAsStatement = false;} else if (StringUtils.startsWithIgnoreCaseAndWs(sql, "DO")) {canHandleAsStatement = false;} else if (StringUtils.startsWithIgnoreCaseAndWs(sql, "SET")) {canHandleAsStatement = false;}
return canHandleAsStatement;
}
canHandleAsServerPreparedStatementNoCache 办法针对 call、create table、do、set 返回 false,其余的针对小于 5.0.7 版本的做非凡判断,其余的默认返回 true
clientPrepareStatement
mysql-connector-java-5.1.21-sources.jar!/com/mysql/jdbc/ConnectionImpl.java
/** A cache of SQL to parsed prepared statement parameters. */
private CacheAdapter<String, ParseInfo> cachedPreparedStatementParams;
public java.sql.PreparedStatement clientPrepareStatement(String sql,
int resultSetType, int resultSetConcurrency,
boolean processEscapeCodesIfNeeded) throws SQLException {checkClosed();
String nativeSql = processEscapeCodesIfNeeded && getProcessEscapeCodesForPrepStmts() ? nativeSQL(sql): sql;
PreparedStatement pStmt = null;
if (getCachePreparedStatements()) {PreparedStatement.ParseInfo pStmtInfo = this.cachedPreparedStatementParams.get(nativeSql);
if (pStmtInfo == null) {pStmt = com.mysql.jdbc.PreparedStatement.getInstance(getLoadBalanceSafeProxy(), nativeSql,
this.database);
this.cachedPreparedStatementParams.put(nativeSql, pStmt
.getParseInfo());
} else {pStmt = new com.mysql.jdbc.PreparedStatement(getLoadBalanceSafeProxy(), nativeSql,
this.database, pStmtInfo);
}
} else {pStmt = com.mysql.jdbc.PreparedStatement.getInstance(getLoadBalanceSafeProxy(), nativeSql,
this.database);
}
pStmt.setResultSetType(resultSetType);
pStmt.setResultSetConcurrency(resultSetConcurrency);
return pStmt;
}
clientPrepareStatement 在 cachePreparedStatements 为 true 时会从 cachedPreparedStatementParams(
缓存的 key 为 nativeSql,value 为 ParseInfo
)去获取 ParseInfo,获取不到则执行 com.mysql.jdbc.PreparedStatement.getInstance 再放入缓存,获取到 ParseInfo 则通过 com.mysql.jdbc.PreparedStatement(getLoadBalanceSafeProxy(), nativeSql,this.database, pStmtInfo)创立 PreparedStatement;如果为 false 则间接通过 com.mysql.jdbc.PreparedStatement.getInstance 来创立
PreparedStatement.getInstance
mysql-connector-java-5.1.21-sources.jar!/com/mysql/jdbc/PreparedStatement.java
/**
* Creates a prepared statement instance -- We need to provide factory-style
* methods so we can support both JDBC3 (and older) and JDBC4 runtimes,
* otherwise the class verifier complains when it tries to load JDBC4-only
* interface classes that are present in JDBC4 method signatures.
*/
protected static PreparedStatement getInstance(MySQLConnection conn, String sql,
String catalog) throws SQLException {if (!Util.isJdbc4()) {return new PreparedStatement(conn, sql, catalog);
}
return (PreparedStatement) Util.handleNewInstance(JDBC_4_PSTMT_3_ARG_CTOR, new Object[] {conn, sql, catalog}, conn.getExceptionInterceptor());
}
getInstance 办法对于非 jdbc4 的间接 new 一个 PreparedStatement,若应用了 jdbc4 则通过 Util.handleNewInstance 应用 JDBC_4_PSTMT_3_ARG_CTOR 的结构器反射创立
JDBC_4_PSTMT_3_ARG_CTOR
mysql-connector-java-5.1.21-sources.jar!/com/mysql/jdbc/PreparedStatement.java
public class PreparedStatement extends com.mysql.jdbc.StatementImpl implements
java.sql.PreparedStatement {
private static final Constructor<?> JDBC_4_PSTMT_2_ARG_CTOR;
private static final Constructor<?> JDBC_4_PSTMT_3_ARG_CTOR;
private static final Constructor<?> JDBC_4_PSTMT_4_ARG_CTOR;
static {if (Util.isJdbc4()) {
try {
JDBC_4_PSTMT_2_ARG_CTOR = Class.forName("com.mysql.jdbc.JDBC4PreparedStatement")
.getConstructor(new Class[] {MySQLConnection.class, String.class});
JDBC_4_PSTMT_3_ARG_CTOR = Class.forName("com.mysql.jdbc.JDBC4PreparedStatement")
.getConstructor(new Class[] { MySQLConnection.class, String.class,
String.class });
JDBC_4_PSTMT_4_ARG_CTOR = Class.forName("com.mysql.jdbc.JDBC4PreparedStatement")
.getConstructor(new Class[] { MySQLConnection.class, String.class,
String.class, ParseInfo.class });
} catch (SecurityException e) {throw new RuntimeException(e);
} catch (NoSuchMethodException e) {throw new RuntimeException(e);
} catch (ClassNotFoundException e) {throw new RuntimeException(e);
}
} else {
JDBC_4_PSTMT_2_ARG_CTOR = null;
JDBC_4_PSTMT_3_ARG_CTOR = null;
JDBC_4_PSTMT_4_ARG_CTOR = null;
}
}
//......
}
com.mysql.jdbc.PreparedStatement 在 static 办法初始化了 JDBC_4_PSTMT_3_ARG_CTOR,其结构器有三个参数,别离是 MySQLConnection.class, String.class,String.class,应用的类是 com.mysql.jdbc.JDBC4PreparedStatement
JDBC4PreparedStatement
mysql-connector-java-5.1.21-sources.jar!/com/mysql/jdbc/JDBC4PreparedStatement.java
public class JDBC4PreparedStatement extends PreparedStatement {public JDBC4PreparedStatement(MySQLConnection conn, String catalog) throws SQLException {super(conn, catalog);
}
public JDBC4PreparedStatement(MySQLConnection conn, String sql, String catalog)
throws SQLException {super(conn, sql, catalog);
}
public JDBC4PreparedStatement(MySQLConnection conn, String sql, String catalog,
ParseInfo cachedParseInfo) throws SQLException {super(conn, sql, catalog, cachedParseInfo);
}
public void setRowId(int parameterIndex, RowId x) throws SQLException {JDBC4PreparedStatementHelper.setRowId(this, parameterIndex, x);
}
/**
* JDBC 4.0 Set a NCLOB parameter.
*
* @param i
* the first parameter is 1, the second is 2, ...
* @param x
* an object representing a NCLOB
*
* @throws SQLException
* if a database error occurs
*/
public void setNClob(int parameterIndex, NClob value) throws SQLException {JDBC4PreparedStatementHelper.setNClob(this, parameterIndex, value);
}
public void setSQLXML(int parameterIndex, SQLXML xmlObject)
throws SQLException {JDBC4PreparedStatementHelper.setSQLXML(this, parameterIndex, xmlObject);
}
}
JDBC4PreparedStatement 的三个参数结构器次要是调用了父类 PreparedStatement 的对应的结构器;JDBC4PreparedStatement 次要是反对了 setNClob、setSQLXML
new PreparedStatement
mysql-connector-java-5.1.21-sources.jar!/com/mysql/jdbc/PreparedStatement.java
/**
* Constructor for the PreparedStatement class.
*
* @param conn
* the connection creating this statement
* @param sql
* the SQL for this statement
* @param catalog
* the catalog/database this statement should be issued against
*
* @throws SQLException
* if a database error occurs.
*/
public PreparedStatement(MySQLConnection conn, String sql, String catalog)
throws SQLException {super(conn, catalog);
if (sql == null) {throw SQLError.createSQLException(Messages.getString("PreparedStatement.0"), //$NON-NLS-1$
SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
}
detectFractionalSecondsSupport();
this.originalSql = sql;
if (this.originalSql.startsWith(PING_MARKER)) {this.doPingInstead = true;} else {this.doPingInstead = false;}
this.dbmd = this.connection.getMetaData();
this.useTrueBoolean = this.connection.versionMeetsMinimum(3, 21, 23);
this.parseInfo = new ParseInfo(sql, this.connection, this.dbmd,
this.charEncoding, this.charConverter);
initializeFromParseInfo();
this.compensateForOnDuplicateKeyUpdate = this.connection.getCompensateOnDuplicateKeyUpdateCounts();
if (conn.getRequiresEscapingEncoder())
charsetEncoder = Charset.forName(conn.getEncoding()).newEncoder();}
这里次要是用了 connection 的 metaData、以及结构 ParseInfo
StatementImpl
mysql-connector-java-5.1.21-sources.jar!/com/mysql/jdbc/StatementImpl.java
public StatementImpl(MySQLConnection c, String catalog) throws SQLException {if ((c == null) || c.isClosed()) {
throw SQLError.createSQLException(Messages.getString("Statement.0"), //$NON-NLS-1$
SQLError.SQL_STATE_CONNECTION_NOT_OPEN, null); //$NON-NLS-1$ //$NON-NLS-2$
}
this.connection = c;
this.connectionId = this.connection.getId();
this.exceptionInterceptor = this.connection
.getExceptionInterceptor();
this.currentCatalog = catalog;
this.pedantic = this.connection.getPedantic();
this.continueBatchOnError = this.connection.getContinueBatchOnError();
this.useLegacyDatetimeCode = this.connection.getUseLegacyDatetimeCode();
if (!this.connection.getDontTrackOpenResources()) {this.connection.registerStatement(this);
}
//
// Adjust, if we know it
//
if (this.connection != null) {this.maxFieldSize = this.connection.getMaxAllowedPacket();
int defaultFetchSize = this.connection.getDefaultFetchSize();
if (defaultFetchSize != 0) {setFetchSize(defaultFetchSize);
}
if (this.connection.getUseUnicode()) {this.charEncoding = this.connection.getEncoding();
this.charConverter = this.connection.getCharsetConverter(this.charEncoding);
}
boolean profiling = this.connection.getProfileSql()
|| this.connection.getUseUsageAdvisor() || this.connection.getLogSlowQueries();
if (this.connection.getAutoGenerateTestcaseScript() || profiling) {this.statementId = statementCounter++;}
if (profiling) {this.pointOfOrigin = new Throwable();
this.profileSQL = this.connection.getProfileSql();
this.useUsageAdvisor = this.connection.getUseUsageAdvisor();
this.eventSink = ProfilerEventHandlerFactory.getInstance(this.connection);
}
int maxRowsConn = this.connection.getMaxRows();
if (maxRowsConn != -1) {setMaxRows(maxRowsConn);
}
this.holdResultsOpenOverClose = this.connection.getHoldResultsOpenOverStatementClose();}
version5013OrNewer = this.connection.versionMeetsMinimum(5, 0, 13);
}
这里会获取 connection 的一系列配置,同时对于须要 trackOpenResources 的会执行 registerStatement(
这个在 realClose 的时候会 unregister
)
参数值
isJdbc4
com/mysql/jdbc/Util.java
private static boolean isJdbc4 = false;
try {Class.forName("java.sql.NClob");
isJdbc4 = true;
} catch (Throwable t) {isJdbc4 = false;}
isJdbc4 默认为 false,在检测到 java.sql.NClob 类的时候为 true;jdk8 版本反对 jdbc4
useServerPreparedStmts
com/mysql/jdbc/ConnectionPropertiesImpl.java
private BooleanConnectionProperty detectServerPreparedStmts = new BooleanConnectionProperty(
"useServerPrepStmts", //$NON-NLS-1$
false,
Messages.getString("ConnectionProperties.useServerPrepStmts"), //$NON-NLS-1$
"3.1.0", MISC_CATEGORY, Integer.MIN_VALUE); //$NON-NLS-1$
useServerPreparedStmts 默认为 false
cachePrepStmts
com/mysql/jdbc/ConnectionPropertiesImpl.java
private BooleanConnectionProperty cachePreparedStatements = new BooleanConnectionProperty(
"cachePrepStmts", //$NON-NLS-1$
false,
Messages.getString("ConnectionProperties.cachePrepStmts"), //$NON-NLS-1$
"3.0.10", PERFORMANCE_CATEGORY, Integer.MIN_VALUE); //$NON-NLS-1$
cachePrepStmts 默认为 false
小结
- mysql 的 jdbc driver 的 prepareStatement 首先依据 useServerPreparedStmts 以及 getEmulateUnsupportedPstmts 来判断是否要通过 canHandleAsServerPreparedStatement 判断 canServerPrepare;之后在 useServerPreparedStmts 及 canServerPrepare 为 true 时,依据 cachePreparedStatements 做 ServerPreparedStatement 的解决;如果不开启 serverPrepare 则执行 clientPrepareStatement(
useServerPreparedStmts 及 cachePrepStmts 参数默认为 false
) - clientPrepareStatement 在 cachePreparedStatements 为 true 时会从 cachedPreparedStatementParams(
缓存的 key 为 nativeSql,value 为 ParseInfo
)去获取 ParseInfo,获取不到则执行 com.mysql.jdbc.PreparedStatement.getInstance 再放入缓存,获取到 ParseInfo 则通过 com.mysql.jdbc.PreparedStatement(getLoadBalanceSafeProxy(), nativeSql,this.database, pStmtInfo)创立 PreparedStatement;如果为 false 则间接通过 com.mysql.jdbc.PreparedStatement.getInstance 来创立 - useServerPreparedStmts 为 true 时,创立的是 ServerPreparedStatement(
创立的时候会触发 prepare 操作,往 mysql 服务端发送 COM_PREPARE 指令
),本地通过 serverSideStatementCache 类来缓存 ServerPreparedStatement,key 为 sql
doc
- 预编译语句 (Prepared Statements) 介绍,以 MySQL 为例