序
本文次要钻研一下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为例