PrepareStatement真的是预编译语句么?
ChatGPT对PrepareStatement的定义是:
PrepareStatement 是 Java 数据库连接(JDBC)API 中用于执行预编译 SQL 语句的接口。通过使用 PreparedStatement,可以预编译 SQL 语句,并且在执行时可以重复使用这个预编译的语句,这样可以提高执行效率并且增强安全性(特别是防止 SQL 注入攻击)。
然而我在wireshark对PrepareStatement语句抓包却显示,它是一个Statement
代表的普通SQL,没有占位符?
。
mybatis mapper对应的xml文件中有个占位符id
,而实际上发出的数据包中这个id
占位符被替换成了具体数字15
,这使得此次的mysql请求是一个简单的纯sql语句。
后面查阅资料得知,默认情况下是jdbc客户端自己先去编译PreparedStatement
,编译替换占位符后,再把这个纯粹的SQL语句发送给mysql server。如果想让服务端编译PreparedStatement
,需要在jdbc连接url上加上useServerPrepStmts=true
,这个参数的默认值是false
从下面两张截图是方法com.mysql.cj.jdbc.ConnectionImpl#prepareStatement(java.lang.String, int, int)
创建预编译语句的主逻辑:当参数useServerPrepStmts
是true
时创建服务端预编译语句ServerPreparedStatement
,否则就创建客户端预编译语句ClientPreparedStatement
.
在jdbc连接url加上useServerPrepStmts=true
参数,再重启项目,再次执行这个查询接口。wireshark抓包显示此时真正地向mysql server发送了预编译语句,sql脚本中出现了占位符?
和useServerPrepStmts
相关的参数还有 cachePrepStmts
、prepStmtCacheSize
、 prepStmtCacheSqlLimit
.
cachePrepStmts
:是否启用PrepareStatement缓存,默认值是false,若遇到相同sql就不再构建新的PrepareStatement对象,直接复用缓存中的prepStmt。
prepStmtCacheSize
:PrepareStatement缓存的最大数量,默认值是25,超过这个阈值就驱逐老的prepStmt。
prepStmtCacheSqlLimit
: 可以缓存的prepStmt对应sql的最大字符长度,默认值256,超过这个上限便不缓存。
注意:只有在cachePrepStmts
为true
时,prepStmtCacheSize
、prepStmtCacheSqlLimit
才有意义。