今天分析了一下mysql 5.1 版本驱动包的prepareStatement实现源码,发现驱动包并没有实现真正的服务器预编译,还是跟普通的Statement一样,在客户端拼装好完整的sql,底层还是用socket与服务器通过二进制协议流进行数据交互,然后把请求返回的结果,生成resultSet数据集合,以方便后续的数据迭代处理.
public java.sql.ResultSet executeQuery() throws SQLException {
checkClosed();
ConnectionImpl locallyScopedConn = this.connection;
checkForDml(this.originalSql, this.firstCharOfStmt);
CachedResultSetMetaData cachedMetadata = null;
// We need to execute this all together
// So synchronize on the Connection's mutex (because
// even queries going through there synchronize
// on the same mutex.
synchronized (locallyScopedConn.getMutex()) {
clearWarnings();
boolean doStreaming = createStreamingResultSet();
this.batchedGeneratedKeys = null;
// Adjust net_write_timeout to a higher value if we're
// streaming result sets. More often than not, someone runs into
// an issue where they blow net_write_timeout when using this
// feature, and if they're willing to hold a result set open
// for 30 seconds or more, one more round-trip isn't going to hurt
//
// This is reset by RowDataDynamic.close().
if (doStreaming
&& this.connection.getNetTimeoutForStreamingResults() > 0) {
locallyScopedConn.execSQL(this, "SET net_write_timeout="
+ this.connection.getNetTimeoutForStreamingResults(),
-1, null, ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_READ_ONLY, false, this.currentCatalog,
null, false);
}
/*
这里是通过方法 fillSendPacket 把相关的请求数据组装成二进制字节流,以方便后续发送给服务端,通过二进制协议,把sql进行了一层包装
*/
Buffer sendPacket = fillSendPacket();
if (this.results != null) {
if (!this.connection.getHoldResultsOpenOverStatementClose()) {
if (!this.holdResultsOpenOverClose) {
this.results.realClose(false);
}
}
}
String oldCatalog = null;
if (!locallyScopedConn.getCatalog().equals(this.currentCatalog)) {
oldCatalog = locallyScopedConn.getCatalog();
locallyScopedConn.setCatalog(this.currentCatalog);
}
//
// Check if we have cached metadata for this query...
//
if (locallyScopedConn.getCacheResultSetMetadata()) {
cachedMetadata = locallyScopedConn.getCachedMetaData(this.originalSql);
}
Field[] metadataFromCache = null;
if (cachedMetadata != null) {
metadataFromCache = cachedMetadata.fields;
}
if (locallyScopedConn.useMaxRows()) {
// If there isn't a limit clause in the SQL
// then limit the number of rows to return in
// an efficient manner. Only do this if
// setMaxRows() hasn't been used on any Statements
// generated from the current Connection (saves
// a query, and network traffic).
if (this.hasLimitClause) {
this.results = executeInternal(this.maxRows, sendPacket,
createStreamingResultSet(), true,
metadataFromCache, false);
} else {
if (this.maxRows <= 0) {
executeSimpleNonQuery(locallyScopedConn,
"SET OPTION SQL_SELECT_LIMIT=DEFAULT");
} else {
executeSimpleNonQuery(locallyScopedConn,
"SET OPTION SQL_SELECT_LIMIT=" + this.maxRows);
}
this.results = executeInternal(-1, sendPacket,
doStreaming, true,
metadataFromCache, false);
if (oldCatalog != null) {
this.connection.setCatalog(oldCatalog);
}
}
} else {
this.results = executeInternal(-1, sendPacket,
doStreaming, true,
metadataFromCache, false);
}
if (oldCatalog != null) {
locallyScopedConn.setCatalog(oldCatalog);
}
if (cachedMetadata != null) {
locallyScopedConn.initializeResultsMetadataFromCache(this.originalSql,
cachedMetadata, this.results);
} else {
if (locallyScopedConn.getCacheResultSetMetadata()) {
locallyScopedConn.initializeResultsMetadataFromCache(this.originalSql,
null /* will be created */, this.results);
}
}
}
this.lastInsertId = this.results.getUpdateID();
return this.results;
}
下面我们再来一齐分析看看,fillsendPacket如何进行请求数据的包装.
/* 这里的 parameterValues parameterStreams存放了对应的sql与数据值. parameterValues 就是存放着下述的sql,不过它会根据 ? 号把sql分拆存到parameterValues二维数组去. sql = "select * from userjf where aid = ?"; PreparedStatement ps = conn.prepareStatement(sql,ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); parameterStreams 存放着下述的数据值,通过二维数组对应存放 ps.setString(1, "param1'); ps.setString(2, "param2'); */ protected Buffer fillSendPacket() throws SQLException { return fillSendPacket(this.parameterValues, this.parameterStreams, this.isStream, this.streamLengths); }
protected Buffer fillSendPacket(byte[][] batchedParameterStrings, InputStream[] batchedParameterStreams, boolean[] batchedIsStream, int[] batchedStreamLengths) throws SQLException { /* 这里是获取数据发送缓冲区 */ Buffer sendPacket = this.connection.getIO().getSharedSendPacket(); /* 缓冲区的事前清理 */ sendPacket.clear(); /* 下面开始组装二进制协议流,第一字节写入了3,表示这是查询协议 */ sendPacket.writeByte((byte) MysqlDefs.QUERY); boolean useStreamLengths = this.connection .getUseStreamLengthsInPrepStmts(); // // Try and get this allocation as close as possible // for BLOBs // int ensurePacketSize = 0; String statementComment = this.connection.getStatementComment(); byte[] commentAsBytes = null; if (statementComment != null) { if (this.charConverter != null) { commentAsBytes = this.charConverter.toBytes(statementComment); } else { commentAsBytes = StringUtils.getBytes(statementComment, this.charConverter, this.charEncoding, this.connection .getServerCharacterEncoding(), this.connection .parserKnowsUnicode()); } ensurePacketSize += commentAsBytes.length; ensurePacketSize += 6; // for /*[space] [space]*/ } for (int i = 0; i < batchedParameterStrings.length; i++) { if (batchedIsStream[i] && useStreamLengths) { ensurePacketSize += batchedStreamLengths[i]; } } if (ensurePacketSize != 0) { sendPacket.ensureCapacity(ensurePacketSize); } if (commentAsBytes != null) { sendPacket.writeBytesNoNull(Constants.SLASH_STAR_SPACE_AS_BYTES); sendPacket.writeBytesNoNull(commentAsBytes); sendPacket.writeBytesNoNull(Constants.SPACE_STAR_SLASH_SPACE_AS_BYTES); } /* 这里有两个重要的数据,staticSqlStrings batchedParameterStreams,均为二维数组,分别记录的sql与对应值. 例如我们 sql为 select * from user where id = ? and name = ?,对应设置的值为 123, jack则staticSqlStrings batchedParameterStreams两个二维数组记录的内容为: staticSqlStrings: {"select * from user where id =","","and name = ",""} batchedParameterStreams: {123,"jack"} 下面接下来的处理,实际上就是把 staticSqlStrings与batchedParameterStreams的数据进行组装,组装成实际的sql,select * from user where id = 123 and name = 'jack',然后 再拼接到请求协议数据sendPacket缓存去. */ for (int i = 0; i < batchedParameterStrings.length; i++) { if ((batchedParameterStrings[i] == null) && (batchedParameterStreams[i] == null)) { throw SQLError.createSQLException(Messages .getString("PreparedStatement.40") //$NON-NLS-1$ + (i + 1), SQLError.SQL_STATE_WRONG_NO_OF_PARAMETERS); } sendPacket.writeBytesNoNull(this.staticSqlStrings[i]); if (batchedIsStream[i]) { streamToBytes(sendPacket, batchedParameterStreams[i], true, batchedStreamLengths[i], useStreamLengths); } else { sendPacket.writeBytesNoNull(batchedParameterStrings[i]); } } sendPacket.writeBytesNoNull(this.staticSqlStrings[batchedParameterStrings.length]); return sendPacket; }
因此从这里我们可以发现,实际上mysql的prepareStatement,只是把 sql与对应的参数值进行了组装,变成了完整的sql,然后再进行数据请求.
针对普通的Statement,存在着依赖注入的问题.如:
登录模块
如:
User validUser = login.getUserInfo(user.getName());这里的user.getName()是前台从Textfield控件中获得得值,没有做任何处理,于是再看看getUserInfo的方法,如下:
public User getUserInfo(String userName) { User validUser = null; String sql = "Select * from WEB_USER where NAME='" + userName + "'"; Database db = null; ResultSet rs = null; try { db = new Database("XXX"); rs = db.execQuery(sql); if (rs != null && rs.next()) { validUser = new User(); .... } } }
我们看到从前台传过来的userName没有经过任何处理而直接凭凑的SQL语句,所以如果输入者精心构造的话,就可以突破第一个屏障,生成一个有效的用户对象,比如:
输入: cjcj' or '1'='1
这样的字符串输入到后台的SQL语句就为:
select * from web_user where name='cjcj' or '1'='1'
显然,这个rs肯定是非空的。我们完成了突破第一个屏障的任务。
而在prepareStatement得到了改善,它在进行参数设置时,对存在依赖注入风险的参数值进行了特殊处理,如上述输入: cjcj' or '1'='1 ,它会处理成 cjcj\' or \'1\'=\'1 ,从而最后拼装成sql时,变成了 select * from web_user where name='cjcj\' or \'1\'=\'1',下面我们抽取 prepareStatement.setString(int parameterIndex, String x)方法 一齐来看看源码实现.
/** * Set a parameter to a Java String value. The driver converts this to a SQL * VARCHAR or LONGVARCHAR value (depending on the arguments size relative to * the driver's limits on VARCHARs) when it sends it to the database. * * @param parameterIndex * the first parameter is 1... * @param x * the parameter value * * @exception SQLException * if a database access error occurs */ public void setString(int parameterIndex, String x) throws SQLException { // if the passed string is null, then set this column to null if (x == null) { setNull(parameterIndex, Types.CHAR); } else { checkClosed(); int stringLength = x.length(); if (this.connection.isNoBackslashEscapesSet()) { // Scan for any nasty chars boolean needsHexEscape = isEscapeNeededForString(x, stringLength); if (!needsHexEscape) { byte[] parameterAsBytes = null; StringBuffer quotedString = new StringBuffer(x.length() + 2); quotedString.append('\''); quotedString.append(x); quotedString.append('\''); if (!this.isLoadDataQuery) { parameterAsBytes = StringUtils.getBytes(quotedString.toString(), this.charConverter, this.charEncoding, this.connection.getServerCharacterEncoding(), this.connection.parserKnowsUnicode()); } else { // Send with platform character encoding parameterAsBytes = quotedString.toString().getBytes(); } setInternal(parameterIndex, parameterAsBytes); } else { byte[] parameterAsBytes = null; if (!this.isLoadDataQuery) { parameterAsBytes = StringUtils.getBytes(x, this.charConverter, this.charEncoding, this.connection.getServerCharacterEncoding(), this.connection.parserKnowsUnicode()); } else { // Send with platform character encoding parameterAsBytes = x.getBytes(); } setBytes(parameterIndex, parameterAsBytes); } return; } String parameterAsString = x; boolean needsQuoted = true; if (this.isLoadDataQuery || isEscapeNeededForString(x, stringLength)) { needsQuoted = false; // saves an allocation later /* 1 创建缓冲区 buf 2 缓存区开始位置放入 ' 号(你懂的) 3 把字符串类型的参数值X,一个字符一个字符的进行判断处理,若为特殊符号则进行转义处理,如遇到 ' 号,则转义为 \' . 4 缓存区最后位置放入 ' 号(你懂的) 若x值为 cjcj' or '1'='1 ,则最后缓存区内容为 'cjcj\' or \'1\'=\'1' */ StringBuffer buf = new StringBuffer((int) (x.length() * 1.1)); buf.append('\''); for (int i = 0; i < stringLength; ++i) { char c = x.charAt(i); switch (c) { case 0: /* Must be escaped for 'mysql' */ buf.append('\\'); buf.append('0'); break; case '\n': /* Must be escaped for logs */ buf.append('\\'); buf.append('n'); break; case '\r': buf.append('\\'); buf.append('r'); break; case '\\': buf.append('\\'); buf.append('\\'); break; case '\'': buf.append('\\'); buf.append('\''); break; case '"': /* Better safe than sorry */ if (this.usingAnsiMode) { buf.append('\\'); } buf.append('"'); break; case '\032': /* This gives problems on Win32 */ buf.append('\\'); buf.append('Z'); break; default: buf.append(c); } } buf.append('\''); parameterAsString = buf.toString(); } byte[] parameterAsBytes = null; if (!this.isLoadDataQuery) { if (needsQuoted) { parameterAsBytes = StringUtils.getBytesWrapped(parameterAsString, '\'', '\'', this.charConverter, this.charEncoding, this.connection .getServerCharacterEncoding(), this.connection .parserKnowsUnicode()); } else { parameterAsBytes = StringUtils.getBytes(parameterAsString, this.charConverter, this.charEncoding, this.connection .getServerCharacterEncoding(), this.connection .parserKnowsUnicode()); } } else { // Send with platform character encoding parameterAsBytes = parameterAsString.getBytes(); } setInternal(parameterIndex, parameterAsBytes); this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.VARCHAR; } }
相关推荐
在Java编程领域,JDBC...总之,Java通过JDBC连接MySQL提供了灵活且强大的数据访问能力,而源码包的分析则有助于开发者深入理解底层机制,提升技能水平。学习并掌握这部分内容对于任何Java开发者来说都是极其有价值的。
PreparedStatement stmt = conn.prepareStatement(sql); stmt.setString(1, username); stmt.setString(2, encryptedPassword); stmt.setString(3, email); stmt.executeUpdate(); } catch (SQLException e) { ...
OraclePreparedStatement pstmt = (OraclePreparedStatement) conn.prepareStatement(sql); pstmt.setInt(1, 1); // 假设id为1 File imageFile = new File("path_to_your_image.jpg"); OracleLob lob = Oracle...
PreparedStatement ptmt=conn.prepareStatement(sql); ResultSet rs= ptmt.executeQuery(); //查看结果 while(rs.next()){ System.out.println( rs.getString("name")); //资源回收 } rs.close(); ptmt....
Blob(Binary Large Object)在数据库中用于存储二进制大对象,如图片、音频或视频文件等...源码中可能包含了类似的实现,通过解压提供的"向数据库插入blob数据实例源码"压缩包,你可以查看并运行完整的Java代码示例。
PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.setString(1, "Demo.java"); pstmt.setString(2, "John Doe"); pstmt.setString(3, "public class Demo { public static void main(String[] args...
7. **执行 SQL 语句**:通过 `java.sql.Connection` 接口创建的连接对象,可以调用 `createStatement()` 或 `prepareStatement()` 方法来创建 `Statement` 或 `PreparedStatement` 对象,然后使用它们来执行 SQL ...
ps = conn.prepareStatement(sql); // 创建PreparedStatement对象 ps.setString(1, loginName); ps.setString(2, loginPwd); rs = ps.executeQuery(); // 执行SQL语句 if (rs.next()) { loginSuccess = true; ...
Java Database Connectivity(JDBC)是Java编程语言中用于与各种数据库进行交互的一种API。它为开发者提供了一套标准...在提供的源码中,你可以找到具体实现这些操作的示例,这对于学习和提升JDBC使用技巧非常有帮助。
PreparedStatement oracleStmt = oracleConn.prepareStatement("INSERT INTO mytable (col1, col2) VALUES (?, ?)"); while (rs.next()) { oracleStmt.setString(1, rs.getString("col1")); oracleStmt....
PreparedStatement pstmt = conn.prepareStatement(sql)) { pstmt.setString(1, user.getName()); pstmt.setString(2, user.getEmail()); pstmt.executeUpdate(); } catch (Exception e) { e.printStackTrace...
PreparedStatement pstmt = conn.prepareStatement(sql)) { pstmt.executeUpdate(); } catch (Exception e) { e.printStackTrace(); } } // 其他增删查改的方法,如insert, delete, select等 } ``` 请注意...
PreparedStatement pstmt = conn.prepareStatement("SELECT * FROM mytable WHERE id = ?"); ``` 5. **执行SQL**: 使用`executeQuery()`或`executeUpdate()`方法执行SQL语句。前者用于查询,返回`ResultSet`;后...
PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.setString(1, "John Doe"); pstmt.setInt(2, 30); pstmt.executeUpdate(); ``` 3. **比较与选择** - **性能**:由于预编译,`...
本资源提供的"纯sql分页源码java版"聚焦于如何在Java中利用SQL语句实现分页查询,支持Oracle、MySQLser(可能是MySQL Server的误写)和MySQL三种不同的数据库系统。以下将详细讲解这个主题。 首先,分页的核心思想...
try (PreparedStatement pstmt = conn.prepareStatement( "INSERT INTO Employees (id, name, age) VALUES (?, ?, ?)")) { pstmt.setInt(1, 100); pstmt.setString(2, "John Doe"); pstmt.setInt(3, 30); pstmt...
PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.setInt(1, pageSize); pstmt.setInt(2, (currentPage - 1) * pageSize); ResultSet rs = pstmt.executeQuery(); while (rs.next()) { // 处理...
PreparedStatement pstmt = connection.prepareStatement(sql); pstmt.setInt(1, (pageNo - 1) * pageSize); // pageNo为当前页数,pageSize为每页大小 pstmt.setInt(2, pageSize); ResultSet rs = pstmt....
PreparedStatement pstmt = conn.prepareStatement("INSERT INTO your_table (column1, column2) VALUES (?, ?)"); for (Row row : sheet) { if (row.getRowNum() == 0) continue; // 跳过表头 Cell cell1 = ...
5. **执行SQL语句**:`java.sql.Connection`接口提供了`createStatement()`,`prepareStatement()`等方法创建SQL语句对象。`Statement`用于执行简单SQL,而`PreparedStatement`则用于预编译的SQL,能提高性能并防止...