Connection.prepareStatement(String sql)方法执行过程
1. prepareStatement(String sql)方法调用代码
// 主要作用:对预编译的sql语句与参数内容组装String sql = "select id, name, password, is_delete, create_by, create_time, modify_by, modify_time from user where name like concat_ws('', '%', ?,'%') and is_delete = ? order by create_time desc";log.info("== sql = " + sql);Connection conn = jdbcUtil.getConnection();// bobo 此处,已经将sql进行拆分了,以?为分隔符,进行分割,并存放到staticSql字节数组中,以ParseInfo类对象存储,进行数据传递// bobo 通过ParseInfo对象获取需要参数个数,并初始化参数数组对象bindValues,以ClientPreparedQueryBindings类对象存储,进行数据传递// bobo 此时,参数内容是null值,在设置参数环节,对bindValues数组对象进行赋值操作// byte[][] staticSql// T[] bindValuesPreparedStatement ps = conn.prepareStatement(sql);// bobo 参数的赋值操作,最终赋值给bindValues数组对象中,并且设置好mysql的参数类型,在执行sql的时候,使用拼装好的sql语句执行ps.setString(1, username);ps.setString(2, "0");ResultSet rs = ps.executeQuery();
2. Connection.prepareStatement(String sql)方法
ConnectionImpl.java
/*** - 对sql语句进行预编译过程*/@Overridepublic java.sql.PreparedStatement prepareStatement(String sql) throws SQLException {// bobo sql - 预编译的sql语句// bobo DEFAULT_RESULT_SET_TYPE - 默认的结果集类型// bobo DEFAULT_RESULT_SET_CONCURRENCY - 默认的结果集并发效果,指示可能不会更新的ResultSet对象的并发模式的常量。 return prepareStatement(sql, DEFAULT_RESULT_SET_TYPE, DEFAULT_RESULT_SET_CONCURRENCY);}
ConnectionImpl.java
/*** - 预编译sql语句*/@Overridepublic java.sql.PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {// bobo 并发,加锁synchronized (getConnectionMutex()) {// bobo 校验连接是否关闭了checkClosed();//// FIXME: Create warnings if can't create results of the given type or concurrency// - 修正:如果不能创建给定类型或并发性的结果,则创建警告//// bobo ClientPreparedStatement是StatementImpl实现类的子类ClientPreparedStatement pStmt = null;boolean canServerPrepare = true;// bobo 预编译的sql语句,本方法范围内String nativeSql = this.processEscapeCodesForPrepStmts.getValue() ? nativeSQL(sql) : sql;// bobo false && trueif (this.useServerPrepStmts.getValue() && this.emulateUnsupportedPstmts.getValue()) {canServerPrepare = canHandleAsServerPreparedStatement(nativeSql);}// bobo false && trueif (this.useServerPrepStmts.getValue() && canServerPrepare) {if (this.cachePrepStmts.getValue()) {synchronized (this.serverSideStatementCache) {pStmt = this.serverSideStatementCache.remove(new CompoundCacheKey(this.database, sql));if (pStmt != null) {((com.mysql.cj.jdbc.ServerPreparedStatement) pStmt).setClosed(false);pStmt.clearParameters();}if (pStmt == null) {try {pStmt = ServerPreparedStatement.getInstance(getMultiHostSafeProxy(), nativeSql, this.database, resultSetType,resultSetConcurrency);if (sql.length() < this.prepStmtCacheSqlLimit.getValue()) {((com.mysql.cj.jdbc.ServerPreparedStatement) pStmt).isCacheable = true;}pStmt.setResultSetType(resultSetType);pStmt.setResultSetConcurrency(resultSetConcurrency);} catch (SQLException sqlEx) {// Punt, if necessaryif (this.emulateUnsupportedPstmts.getValue()) {pStmt = (ClientPreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false);if (sql.length() < this.prepStmtCacheSqlLimit.getValue()) {this.serverSideStatementCheckCache.put(sql, Boolean.FALSE);}} else {throw sqlEx;}}}}} else {try {pStmt = ServerPreparedStatement.getInstance(getMultiHostSafeProxy(), nativeSql, this.database, resultSetType, resultSetConcurrency);pStmt.setResultSetType(resultSetType);pStmt.setResultSetConcurrency(resultSetConcurrency);} catch (SQLException sqlEx) {// Punt, if necessaryif (this.emulateUnsupportedPstmts.getValue()) {pStmt = (ClientPreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false);} else {throw sqlEx;}}}} else {// bobo 创建一个ClientPreparedStatement对象pStmt = (ClientPreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false);}return pStmt;}}
ConnectionImpl.java
/*** - 创建一个ClientPreparedStatement对象* - sql - 预编译sql语句* - resultSetType - 结果集类型* - resultSetConcurrency - 结果集并发效果*/public java.sql.PreparedStatement clientPrepareStatement(String sql, int resultSetType, int resultSetConcurrency, boolean processEscapeCodesIfNeeded)throws SQLException {// bobo 预编译sql语句String nativeSql = processEscapeCodesIfNeeded && this.processEscapeCodesForPrepStmts.getValue() ? nativeSQL(sql) : sql;// bobo 创建ClientPreparedStatement对象ClientPreparedStatement pStmt = null;// bobo 是否缓存预编译语句if (this.cachePrepStmts.getValue()) {ParseInfo pStmtInfo = this.cachedPreparedStatementParams.get(nativeSql);if (pStmtInfo == null) {pStmt = ClientPreparedStatement.getInstance(getMultiHostSafeProxy(), nativeSql, this.database);this.cachedPreparedStatementParams.put(nativeSql, pStmt.getParseInfo());} else {pStmt = ClientPreparedStatement.getInstance(getMultiHostSafeProxy(), nativeSql, this.database, pStmtInfo);}} else {// bobo 获取ClientPreparedStatement实例对象pStmt = ClientPreparedStatement.getInstance(getMultiHostSafeProxy(), nativeSql, this.database);}// bobo 设置结果集类型pStmt.setResultSetType(resultSetType);// bobo 设置结果集并发效果pStmt.setResultSetConcurrency(resultSetConcurrency);return pStmt;}
执行语句 ConnectionImpl.java 的 getInstance(JdbcConnection conn, String sql, String db)
ClientPreparedStatement.java
/*** Creates a prepared statement instance* - 创建一个预编译语句实例对象** @param conn* the connection creating this statement* - 创建此语句的连接* @param sql* the SQL for this statement* - 此语句的SQL* @param db* the database this statement should be issued against* - 执行此语句对应的数据库* @return ClientPreparedStatement* @throws SQLException* if a database access error occurs* - 数据库访问错误*/protected static ClientPreparedStatement getInstance(JdbcConnection conn, String sql, String db) throws SQLException {// bobo 创建一个新ClientPreparedStatement对象return new ClientPreparedStatement(conn, sql, db);}
ClientPreparedStatement.java
/*** Constructor for the PreparedStatement class.* - PreparedStatement类的构造函数** @param conn* the connection creating this statement* - 创建此语句的连接* @param sql* the SQL for this statement* - 此语句的SQL* @param db* the database this statement should be issued against* - 执行此语句对应的数据库** @throws SQLException* if a database error occurs.* - 数据库访问错误*/public ClientPreparedStatement(JdbcConnection conn, String sql, String db) throws SQLException {this(conn, sql, db, null);}
ClientPreparedStatement.java
/*** Creates a new PreparedStatement object.* - 创建一个新的PreparedStatement对象** @param conn* the connection creating this statement* - 创建此语句的连接* @param sql* the SQL for this statement* - 此语句的SQL* @param db* the database this statement should be issued against* - 执行此语句对应的数据库* @param cachedParseInfo* already created parseInfo or null.* - 已经创建的parseInfo或者是null。** @throws SQLException* if a database access error occurs* - 数据库访问错误*/public ClientPreparedStatement(JdbcConnection conn, String sql, String db, ParseInfo cachedParseInfo) throws SQLException {// bobo 构造,初始化对象数据this(conn, db);try {// bobo 校验sql语句是否是null或者""((PreparedQuery<?>) this.query).checkNullOrEmptyQuery(sql);// bobo 设置源sql语句((PreparedQuery<?>) this.query).setOriginalSql(sql);// bobo 设置ParseInfo对象,创建一个ParseInfo对象((PreparedQuery<?>) this.query).setParseInfo(cachedParseInfo != null ? cachedParseInfo : new ParseInfo(sql, this.session, this.charEncoding));} catch (CJException e) {throw SQLExceptionsMapping.translateException(e, this.exceptionInterceptor);}// bobo 是否需要先ping一下数据库连接this.doPingInstead = sql.startsWith(PING_MARKER);// 以ParseInfo对象为基础,初始化数据initializeFromParseInfo();}
执行语句 ClientPreparedStatement.java 的 this(conn, db);
ClientPreparedStatement.java
/*** Constructor used by server-side prepared statements* - 服务器端准备语句使用的构造函数** @param conn* the connection that created us* - 创建的连接* @param db* the database in use when we were created* - 创建时使用的数据库** @throws SQLException* if an error occurs* - 如果出现错误*/protected ClientPreparedStatement(JdbcConnection conn, String db) throws SQLException {// bobo 父类构造方法,StatementImplsuper(conn, db);// bobo 可以放到缓存池中setPoolable(true);// bobo 抵消重复更新 falsethis.compensateForOnDuplicateKeyUpdate = this.session.getPropertySet().getBooleanProperty(PropertyKey.compensateOnDuplicateKeyUpdateCounts).getValue();}
执行语句 ClientPreparedStatement.java 的 checkNullOrEmptyQuery(String sql);
AbstractPreparedQuery.java
/*** Method checkNullOrEmptyQuery.* - 校验查询sql是否是null或""的方法** @param sql* the SQL to check* - 需要校验的sql语句** @throws WrongArgumentException* if query is null or empty.* - 如果sql语句是null或者""*/public void checkNullOrEmptyQuery(String sql) {// bobo sql语句是否是nullif (sql == null) {throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("PreparedQuery.0"), this.session.getExceptionInterceptor());}// bobo sql语句是""if (sql.length() == 0) {throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("PreparedQuery.1"), this.session.getExceptionInterceptor());}}
执行语句 setParseInfo(ParseInfo parseInfo)
ParseInfo.java
/*** - 创建一个ParseInfo实例* - sql - 预编译sql语句* - session - 会话,调用者* - encoding - 编码格式*/public ParseInfo(String sql, Session session, String encoding) {// bobo 调用构造this(sql, session, encoding, true);}
此方法是将预编译的sql语句以预编译的参数 "?" 进行拆分,截取对应的sql语句数组。
例如:
sql语句 :
select id, name, password, is_delete, create_by, create_time, modify_by, modify_time from user where name like concat_ws('', '%', ?,'%') and is_delete = ? order by create_time desc
staticSql数组就会进行如下的拆分:
staticSql[0] = select id, name, password, is_delete, create_by, create_time, modify_by, modify_time from user where name like concat_ws('', '%',staticSql[1] = ,'%') and is_delete =staticSql[2] = order by create_time desc
ParseInfo.java
/*** - 创建一个ParseInfo对象* - sql - 预编译sql语句* - session - 会话,调用者* - encoding - 编码格式* - buildRewriteInfo - 构建重写信息*/public ParseInfo(String sql, Session session, String encoding, boolean buildRewriteInfo) {try {// bobo 校验sql语句if (sql == null) {throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("PreparedStatement.61"),session.getExceptionInterceptor());}// bobo 设置编码格式this.charEncoding = encoding;// bobo 设置上次调用时间点this.lastUsed = System.currentTimeMillis();// bobo 调用者字符串 是`值String quotedIdentifierString = session.getIdentifierQuoteString();// bobo 调用者的字符串char quotedIdentifierChar = 0;if ((quotedIdentifierString != null) && !quotedIdentifierString.equals(" ") && (quotedIdentifierString.length() > 0)) {quotedIdentifierChar = quotedIdentifierString.charAt(0);}// sql语句的长度this.statementLength = sql.length();// bobo 创建一个端点列表,用来存放两个端点的值,及开始字符串的位置和结束字符串的位置ArrayList<int[]> endpointList = new ArrayList<>();// bobo 是否在引号中boolean inQuotes = false;// bobo 引号字符串char值char quoteChar = 0;// bobo 是否在引号中的idboolean inQuotedId = false;// bobo 上次结束的位置int lastParmEnd = 0;int i;// bobo 没有反斜杠转义 falseboolean noBackslashEscapes = session.getServerSession().isNoBackslashEscapesSet();// we're not trying to be real pedantic here, but we'd like to skip comments at the beginning of statements, as frameworks such as Hibernate// use them to aid in debugging// bobo 我们不想在这里卖弄学问,但是我们想跳过语句开头的注释,就像Hibernate这样的框架一样// bobo 使用它们来帮助调试// bobo 查找sql语句中注释的位置, -- 或者 **/ 或者 #this.statementStartPos = findStartOfStatement(sql);// bobo 遍历sql语句的所有字符for (i = this.statementStartPos; i < this.statementLength; ++i) {// bobo 获取当前字符char c = sql.charAt(i);// bobo 第一个字符 && 是一个字母if ((this.firstStmtChar == 0) && Character.isLetter(c)) {// Determine what kind of statement we're doing (_S_elect, _I_nsert, etc.)// bobo 确定我们正在执行的语句类型(_S_elect, _I_nsert,等等)// bobo sql语句的首字母大写是 Sthis.firstStmtChar = Character.toUpperCase(c);// no need to search for "ON DUPLICATE KEY UPDATE" if not an INSERT statement// bobo 如果不是INSERT语句,不需要搜索“ON DUPLICATE KEY UPDATE”if (this.firstStmtChar == 'I') {this.locationOfOnDuplicateKeyUpdate = getOnDuplicateKeyLocation(sql,session.getPropertySet().getBooleanProperty(PropertyKey.dontCheckOnDuplicateKeyUpdateInSQL).getValue(),session.getPropertySet().getBooleanProperty(PropertyKey.rewriteBatchedStatements).getValue(),session.getServerSession().isNoBackslashEscapesSet());this.isOnDuplicateKeyUpdate = this.locationOfOnDuplicateKeyUpdate != -1;}}// bobo 是否是转义字符if (!noBackslashEscapes && c == '\\' && i < (this.statementLength - 1)) {i++;continue; // next character is escaped 下一个转义字符}// are we in a quoted identifier? (only valid when the id is not inside a 'string')// bobo 我们是在带引号的标识符中吗?(仅当id不在'string'内时有效)if (!inQuotes && (quotedIdentifierChar != 0) && (c == quotedIdentifierChar)) {inQuotedId = !inQuotedId;} else if (!inQuotedId) {// only respect quotes when not in a quoted identifier// bobo 只有在标识符中没有引号时才使用引号// bobo是否是引号if (inQuotes) {if (((c == '\'') || (c == '"')) && c == quoteChar) {if (i < (this.statementLength - 1) && sql.charAt(i + 1) == quoteChar) {i++;continue; // inline quote escape}inQuotes = !inQuotes;quoteChar = 0;} else if (((c == '\'') || (c == '"')) && c == quoteChar) {inQuotes = !inQuotes;quoteChar = 0;}} else {// bobo 是否是#或者--,即是否是注释语句,如果是,那么这段注释中的内容都不做处理,直接忽略if (c == '#' || (c == '-' && (i + 1) < this.statementLength && sql.charAt(i + 1) == '-')) {// run out to end of statement, or newline, whichever comes first// bobo 运行到语句结束,或换行,以最先出现的为限// bobo 最后一个字符的位置int endOfStmt = this.statementLength - 1;// bobo 从当前位置向后遍历for (; i < endOfStmt; i++) {// bobo 获取字符内容c = sql.charAt(i);// bobo 如果是\r 或者 \n 则跳出循环if (c == '\r' || c == '\n') {break;}}continue;} else if (c == '/' && (i + 1) < this.statementLength) {// bobo 如果是// Comment?// bobo 注释// bobo 下一个字符的值char cNext = sql.charAt(i + 1);// bobo 下一个字符内容是*,则是注释语句if (cNext == '*') {// bobo 跳过注释字符串,即/*i += 2;// bobo 从当前位置遍历for (int j = i; j < this.statementLength; j++) {// bobo 忽略注释中的内容i++;// bobo 获取字符内容cNext = sql.charAt(j);// bobo 是*字符if (cNext == '*' && (j + 1) < this.statementLength) {// bobo 是/,则判定注释已经结束if (sql.charAt(j + 1) == '/') {// bobo 跳到注释结束后的字符位置i++;// bobo 是否是最后一个字符的位置if (i < this.statementLength) {// bobo 获取注释结束后的第一个字符内容c = sql.charAt(i);}break; // comment done 注释完成}}}}} else if ((c == '\'') || (c == '"')) {// bobo 是单引号或双引号// bobo 在引号中inQuotes = true;// bobo 是哪个引号,即单引号还是双引号quoteChar = c;}}}// bobo 是否在引号中if (!inQuotes && !inQuotedId) {// bobo 是否是?,即需要带入参数的位置if ((c == '?')) {// bobo 端点列表中增加一组端点值,即上次记录的位置为起始值,当前位置为结束值endpointList.add(new int[] { lastParmEnd, i });// bobo 上次结束位置lastParmEnd = i + 1;// bobo 是否是重复更新if (this.isOnDuplicateKeyUpdate && i > this.locationOfOnDuplicateKeyUpdate) {this.parametersInDuplicateKeyClause = true;}} else if (c == ';') {// bobo 是分号;// bobo 下一个字符的位置int j = i + 1;// bobo 是否是语句的结尾位置if (j < this.statementLength) {// bobo 从当前位置遍历for (; j < this.statementLength; j++) {// bobo 是否是空白字符,不是空白字符,跳出循环if (!Character.isWhitespace(sql.charAt(j))) {break;}}// bobo 是否是语句的结尾位置if (j < this.statementLength) {// bobo 查询的数量+1,即多查询sql语句this.numberOfQueries++;}// bobo 分号后的第一个非空白字符i = j - 1;}}}}// bobo sql语句的首字符是否是Lif (this.firstStmtChar == 'L') {// bobo 是否是以Load data 开头的if (StringUtils.startsWithIgnoreCaseAndWs(sql, "LOAD DATA")) {this.foundLoadData = true;} else {this.foundLoadData = false;}} else {this.foundLoadData = false;}// bobo 端点列表增加最后一组数据,即上次结束的位置为起始值,sql语句的长度为结束值endpointList.add(new int[] { lastParmEnd, this.statementLength });// bobo 初始化静态的sql语句的字节数组,其实就是将sql语句按照占位符?进行裁剪,分成多个字节数组this.staticSql = new byte[endpointList.size()][];// bobo 是否有占位符,即? this.hasPlaceholders = this.staticSql.length > 1;// bobo 遍历静态sql语句的字节数组,并设置其内容for (i = 0; i < this.staticSql.length; i++) {// bobo 获取端点列表的数据int[] ep = endpointList.get(i);// bobo 结束位置int end = ep[1];// bobo 起始位置int begin = ep[0];// bobo 字符长度int len = end - begin;// bobo 是否加载数据if (this.foundLoadData) {this.staticSql[i] = StringUtils.getBytes(sql, begin, len);} else if (encoding == null) {// bobo 编码格式为nullbyte[] buf = new byte[len];for (int j = 0; j < len; j++) {buf[j] = (byte) sql.charAt(begin + j);}this.staticSql[i] = buf;} else {// bobo 截取sql语句的字符串,并转换为字节,存放到sataicSql字节数组中this.staticSql[i] = StringUtils.getBytes(sql, begin, len, encoding);}}} catch (StringIndexOutOfBoundsException oobEx) {throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("PreparedStatement.62", new Object[] { sql }), oobEx,session.getExceptionInterceptor());}// bobo 是否重新写入信息if (buildRewriteInfo) {// bobo 多值插入可以重新写入this.canRewriteAsMultiValueInsert = this.numberOfQueries == 1 && !this.parametersInDuplicateKeyClause&& canRewrite(sql, this.isOnDuplicateKeyUpdate, this.locationOfOnDuplicateKeyUpdate, this.statementStartPos);if (this.canRewriteAsMultiValueInsert && session.getPropertySet().getBooleanProperty(PropertyKey.rewriteBatchedStatements).getValue()) {buildRewriteBatchedParams(sql, session, encoding);}}}
执行语句 ClientPreparedStatement.java 的 initializeFromParseInfo();
ClientPreparedStatement.java
/*** - 以ParseInfo对象为基础,初始化数据,即初始化sql语句中占位符的数据*/@SuppressWarnings("unchecked")private void initializeFromParseInfo() throws SQLException {// bobo 同步代码块synchronized (checkClosed().getConnectionMutex()) {// bobo 预编译时参数的数量int parameterCount = ((PreparedQuery<ClientPreparedQueryBindings>) this.query).getParseInfo().getStaticSql().length - 1;// bobo 设置参数的数量((PreparedQuery<?>) this.query).setParameterCount(parameterCount);// bobo 设置预编译的参数数据((PreparedQuery<ClientPreparedQueryBindings>) this.query).setQueryBindings(new ClientPreparedQueryBindings(parameterCount, this.session));// bobo 设置加载数据查询((ClientPreparedQuery) this.query).getQueryBindings().setLoadDataQuery(((PreparedQuery<?>) this.query).getParseInfo().isFoundLoadData());// bobo 清除参数内容clearParameters();}}
执行语句 ClientPreparedStatement.java 的 ClientPreparedQueryBindings(int parameterCount, Session sess)
ClientPreparedQueryBindings.java
/*** - 创建ClientPreparedQueryBindings对象* - parameterCount - 参数数量* - sess - 调用者*/public ClientPreparedQueryBindings(int parameterCount, Session sess) {// bobo 父类构造super(parameterCount, sess);// bobo 获取请求的编码格式,是否需要进行转换if (((NativeSession) this.session).getRequiresEscapingEncoder()) {this.charsetEncoder = Charset.forName(this.charEncoding).newEncoder();}}
此方法是初始化预编译参数bindValues数组,即以 "?" 占位符的参数数组bindValues,在之后的setString()等方法时,将参数直接赋值给bindValues 数组的对应位置处。
执行语句 ClientPreparedQueryBindings.java 的super(parameterCount, sess)
AbstractQueryBindings.java
/*** - 创建AbstractQueryBindings对象* - parameterCount - 参数数量* - sess - 调用者*/public AbstractQueryBindings(int parameterCount, Session sess) {// bobo 初始化数据// bobo 调用者this.session = sess;// bobo 编码格式 UTF-8this.charEncoding = this.session.getPropertySet().getStringProperty(PropertyKey.characterEncoding).getValue();// bobo 发送小数的时间this.sendFractionalSeconds = this.session.getPropertySet().getBooleanProperty(PropertyKey.sendFractionalSeconds);// bobo 将UtilDate的时间格式视为时间戳格式的日期this.treatUtilDateAsTimestamp = this.session.getPropertySet().getBooleanProperty(PropertyKey.treatUtilDateAsTimestamp);// bobo 使用预编译的流的长度this.useStreamLengthsInPrepStmts = this.session.getPropertySet().getBooleanProperty(PropertyKey.useStreamLengthsInPrepStmts);// bobo 初始化bindValues的数据initBindValues(parameterCount);}
执行语句 AbstractQueryBindings.java 的 initBindValues(parameterCount)
ClientPreparedQueryBindings.java
/*** - 初始化bindValues的数据* - parameterCount - 参数数量*/@Overrideprotected void initBindValues(int parameterCount) {// bobo 为单个字段绑定值// bobo 创建bindValues数据对象,初始化数据长度,this.bindValues = new ClientPreparedQueryBindValue[parameterCount];// bobo 初始化数组的数据for (int i = 0; i < parameterCount; i++) {this.bindValues[i] = new ClientPreparedQueryBindValue();}}
执行语句 ClientPreparedStatement.java 的 clearParameters();
ClientPreparedStatement.java
/*** - 清除参数内容*/@Overridepublic void clearParameters() throws SQLException {// bobo 同步代码块,加锁synchronized (checkClosed().getConnectionMutex()) {// bobo 重置参数数据for (BindValue bv : ((PreparedQuery<?>) this.query).getQueryBindings().getBindValues()) {bv.reset();}}}




