- 浏览: 326292 次
- 性别:
- 来自: 成都
最新评论
-
qyy20180308:
问下,这个问题后来是怎么解决的?
java自己实现的MD5算法,没用到sun的MD5服务 -
ikillmeba:
要中转服务器把两边内网的设备的外网地址知道就好了,就可以穿透了 ...
QQ通信原理及QQ是怎么穿透内网进行通信的? -
snoyc:
为啥打不通呢,前面的所有步骤都执行正确。最后一步Client ...
QQ通信原理及QQ是怎么穿透内网进行通信的? -
leidengyan:
写的真棒!解答了我的疑虑,多谢!
研究tomcat的URL编码笔记 -
壹块钱:
复习了,谢楼主!
对java:comp/env的研究
【转载】Java中连结MySQL启用预编译的先决条件是useServerPstmts=true.
Java中连结MySQL启用预编译的先决条件是useServerPstmts=true.
标签: mysqljavacharacterquerycollationtimezone
2011-10-28 10:47 7140人阅读 评论(4) 收藏 举报
分类: go deep into final(37) JDBC(28)
版权声明:本文为博主原创文章,未经博主允许不得转载。
在Java编程中,应用代码绝大多数使用了PreparedStatement,无论你是直接使用JDBC还是使用框架。
在Java编程中,绝大多数使用了使用了PreparedStatement连接MySQL的应用代码没有启用预编译,无论你是直接使用JDBC还是使用框架。
在我所能见到的项目中,几乎没有见过启用MySQL预编译功能的。网上更有文章说MySQL不支持预编译,实在是害人不浅。
要想知道你的应用是否真正的使用了预编译,请执行:show global status like '%prepare%';看看曾经编译过几条,当前Prepared_stmt_count 是多少。大多数是0吧?
这篇文章分以下几个方面:
一.MySQL是支持预编译的
打开MySQL日志功能,启动MySQL,然后 tail -f mysql.log.path(默认:/var/log/mysql/mysql.log).
create table axman_test (ID int(4) auto_increment primary key, name varchar(20),age int(4));
insert into axman_test (name,age) values ('axman',1000);
prepare myPreparedStmt from 'select * from axman_test where name = ?';
set @name='axman';
execute myPreparedStmt using @name;
控制台可以正确地输出:
mysql> execute myPreparedStmt using @name;
+----+-------+------+
| ID | name | age |
+----+-------+------+
| 1 | axman | 1000 |
+----+-------+------+
1 row in set (0.00 sec)
而log文件中也忠实地记录如下:
111028 9:25:06 51 Query prepare myPreparedStmt from 'select * from axman_test where name = ?'
51 Prepare select * from axman_test where name = ?
51 Query set @name='axman'
111028 9:25:08 51 Query execute myPreparedStmt using @name
51 Execute select * from axman_test where name = 'axman'
二.通过JDBC本身是可以预编译的,这个不用多说。相当于我们把控制台输入的命令直接通过JDBC语句来执行:
Class.forName("org.gjt.mm.mysql.Driver");
String url = "jdbc:mysql://localhost:3306/mysql";
Connection conn = null;
try {
conn = DriverManager.getConnection(url, "root", "12345678");
Statement stmt = conn.createStatement();
/*以下忽略返回值处理*/
stmt.executeUpdate("prepare mystmt from 'select * from axman_test where name = ?'");
stmt.execute("set @name='axman'");
stmt.executeQuery("execute mystmt using @name");
stmt.close();
} finally {
if (conn != null) {
conn.close();
}
}
看日志输出:
111028 9:30:19 52 Connect root@localhost on mysql
52 Query /* @MYSQL_CJ_FULL_PROD_NAME@ ( Revision: @MYSQL_CJ_REVISION@ ) */SHOW VARIABLES WHERE Variable_name ='language' OR Variable_name = 'net_write_timeout' OR Variable_name = 'interactive_timeout' OR Variable_name = 'wait_timeout' OR Variable_name = 'character_set_client' OR Variable_name = 'character_set_connection' OR Variable_name = 'character_set' OR Variable_name = 'character_set_server' OR Variable_name = 'tx_isolation' OR Variable_name = 'transaction_isolation' OR Variable_name = 'character_set_results' OR Variable_name = 'timezone' OR Variable_name = 'time_zone' OR Variable_name = 'system_time_zone' OR Variable_name = 'lower_case_table_names' OR Variable_name = 'max_allowed_packet' OR Variable_name = 'net_buffer_length' OR Variable_name = 'sql_mode' OR Variable_name = 'query_cache_type' OR Variable_name = 'query_cache_size' OR Variable_name = 'init_connect'
52 Query /* @MYSQL_CJ_FULL_PROD_NAME@ ( Revision: @MYSQL_CJ_REVISION@ ) */SELECT @@session.auto_increment_increment
52 Query SHOW COLLATION
52 Query SET NAMES latin1
52 Query SET character_set_results = NULL
52 Query SET autocommit=1
52 Query SET sql_mode='STRICT_TRANS_TABLES'
52 Query prepare mystmt from 'select * from axman_test where name = ?'
52 Prepare select * from axman_test where name = ?
52 Query set @name='axman'
52 Query execute mystmt using @name
52 Execute select * from axman_test where name = 'axman'
52 Quit
三.默认的PrearedStatement不能开启MySQL预编译功能:
虽然第二节中我们通过JDBC手工指定MySQL进行预编译,但是PrearedStatement却并不自动帮我们做这件事。
Class.forName("org.gjt.mm.mysql.Driver");
String url = "jdbc:mysql://localhost:3306/mysql";
Connection conn = null;
try {
conn = DriverManager.getConnection(url, "root", "12345678");
PreparedStatement ps = conn.prepareStatement("select * from axman_test where name = ?");
ps.setString(1, "axman' or 1==1");
ResultSet rs = ps.executeQuery();
if (rs.next()) {
System.out.println(rs.getString(1));
}
Thread.sleep(1000);
rs.close();
ps.clearParameters();
ps.setString(1, "axman");
rs = ps.executeQuery();
if (rs.next()) {
System.out.println(rs.getString(1));
}
rs.close();
ps.close();
} finally {
if (conn != null) {
conn.close();
}
}
废话少说,直接看日志:
111028 9:54:03 53 Connect root@localhost on mysql
53 Query /* @MYSQL_CJ_FULL_PROD_NAME@ ( Revision: @MYSQL_CJ_REVISION@ ) */SHOW VARIABLES WHERE Variable_name ='language' OR Variable_name = 'net_write_timeout' OR Variable_name = 'interactive_timeout' OR Variable_name = 'wait_timeout' OR Variable_name = 'character_set_client' OR Variable_name = 'character_set_connection' OR Variable_name = 'character_set' OR Variable_name = 'character_set_server' OR Variable_name = 'tx_isolation' OR Variable_name = 'transaction_isolation' OR Variable_name = 'character_set_results' OR Variable_name = 'timezone' OR Variable_name = 'time_zone' OR Variable_name = 'system_time_zone' OR Variable_name = 'lower_case_table_names' OR Variable_name = 'max_allowed_packet' OR Variable_name = 'net_buffer_length' OR Variable_name = 'sql_mode' OR Variable_name = 'query_cache_type' OR Variable_name = 'query_cache_size' OR Variable_name = 'init_connect'
53 Query /* @MYSQL_CJ_FULL_PROD_NAME@ ( Revision: @MYSQL_CJ_REVISION@ ) */SELECT @@session.auto_increment_increment
53 Query SHOW COLLATION
53 Query SET NAMES latin1
53 Query SET character_set_results = NULL
53 Query SET autocommit=1
53 Query SET sql_mode='STRICT_TRANS_TABLES'
53 Query select * from axman_test where name = 'axman\' or 1==1'
111028 9:54:04 53 Query select * from axman_test where name = 'axman'
53 Quit
两条语句都是直接执行,而没有预编译。注意我的第一条语句select * from axman_test where name = 'axman\' or 1==1',下面还会说到它。
接着我们改变一下jdbc.url的选项:
String url = "jdbc:mysql://localhost:3306/mysql?cachePrepStmts=true&prepStmtCacheSize=25&prepStmtCacheSqlLimit=256";
执行上面的代码还是没有开启Mysql的预编译。
四.只有使用了useServerPrepStmts=true才能开启Mysql的预编译。
上面的代码其它不变,只修改String url = "jdbc:mysql://localhost:3306/mysql?useServerPrepStmts=true";
查看日志:
111028 10:04:52 54 Connect root@localhost on mysql
54 Query /* @MYSQL_CJ_FULL_PROD_NAME@ ( Revision: @MYSQL_CJ_REVISION@ ) */SHOW VARIABLES WHERE Variable_name ='language' OR Variable_name = 'net_write_timeout' OR Variable_name = 'interactive_timeout' OR Variable_name = 'wait_timeout' OR Variable_name = 'character_set_client' OR Variable_name = 'character_set_connection' OR Variable_name = 'character_set' OR Variable_name = 'character_set_server' OR Variable_name = 'tx_isolation' OR Variable_name = 'transaction_isolation' OR Variable_name = 'character_set_results' OR Variable_name = 'timezone' OR Variable_name = 'time_zone' OR Variable_name = 'system_time_zone' OR Variable_name = 'lower_case_table_names' OR Variable_name = 'max_allowed_packet' OR Variable_name = 'net_buffer_length' OR Variable_name = 'sql_mode' OR Variable_name = 'query_cache_type' OR Variable_name = 'query_cache_size' OR Variable_name = 'init_connect'
54 Query /* @MYSQL_CJ_FULL_PROD_NAME@ ( Revision: @MYSQL_CJ_REVISION@ ) */SELECT @@session.auto_increment_increment
54 Query SHOW COLLATION
54 Query SET NAMES latin1
54 Query SET character_set_results = NULL
54 Query SET autocommit=1
54 Query SET sql_mode='STRICT_TRANS_TABLES'
54 Prepare select * from axman_test where name = ?
54 Execute select * from axman_test where name = 'axman\' or 1==1'
111028 10:04:53 54 Execute select * from axman_test where name = 'axman'
54 Close stmt
54 Quit
如果useServerPrepStmts=true,ConneciontImpl在prepareStatement时会产生一个ServerPreparedStatement.在这个ServerPreparedStatement对象构造时首先会把当前SQL语句发送给MySQL进行预编译,然后将返回的结果缓存起来,其中包含预编译的名称(我们可以看成是当前SQL语句编译后的函数名),签名(参数列表),然后执行的时候就会直接把参数传给这个函数请求MySQL执行这个函数。否则返回的是客户端预编译语句,它仅做参数化工作,见第五节。
ServerPreparedStatement在请求预编译和执行预编译后的SQL 函数时,虽然和我们上面手工预编译工作相同,但它与MySQL交互使用的是压缩格式,如prepare指令码是22,这样可以减少交互时传输的数据量。
注意上面的代码中,两次执行使用的是同一个PreparedStatement句柄.如果使用个不同的PreparedStatement句柄,把代码改成:
Class.forName("org.gjt.mm.mysql.Driver");
String url = "jdbc:mysql://localhost:3306/mysql?useServerPrepStmts=true";
Connection conn = null;
try {
conn = DriverManager.getConnection(url, "root", "12345678");
PreparedStatement ps = conn.prepareStatement("select * from axman_test where name = ?");
ps.setString(1, "axman' or 1==1");
ResultSet rs = ps.executeQuery();
if (rs.next()) {
System.out.println(rs.getString(1));
}
Thread.sleep(1000);
rs.close();
ps.close();
ps = conn.prepareStatement("select * from axman_test where name = ?");
ps.setString(1, "axman");
rs = ps.executeQuery();
if (rs.next()) {
System.out.println(rs.getString(1));
}
rs.close();
ps.close();
} finally {
if (conn != null) {
conn.close();
}
}
再看日志输出:
Connect root@localhost on mysql
55 Query /* @MYSQL_CJ_FULL_PROD_NAME@ ( Revision: @MYSQL_CJ_REVISION@ ) */SHOW VARIABLES WHERE Variable_name ='language' OR Variable_name = 'net_write_timeout' OR Variable_name = 'interactive_timeout' OR Variable_name = 'wait_timeout' OR Variable_name = 'character_set_client' OR Variable_name = 'character_set_connection' OR Variable_name = 'character_set' OR Variable_name = 'character_set_server' OR Variable_name = 'tx_isolation' OR Variable_name = 'transaction_isolation' OR Variable_name = 'character_set_results' OR Variable_name = 'timezone' OR Variable_name = 'time_zone' OR Variable_name = 'system_time_zone' OR Variable_name = 'lower_case_table_names' OR Variable_name = 'max_allowed_packet' OR Variable_name = 'net_buffer_length' OR Variable_name = 'sql_mode' OR Variable_name = 'query_cache_type' OR Variable_name = 'query_cache_size' OR Variable_name = 'init_connect'
55 Query /* @MYSQL_CJ_FULL_PROD_NAME@ ( Revision: @MYSQL_CJ_REVISION@ ) */SELECT @@session.auto_increment_increment
55 Query SHOW COLLATION
55 Query SET NAMES latin1
55 Query SET character_set_results = NULL
55 Query SET autocommit=1
55 Query SET sql_mode='STRICT_TRANS_TABLES'
55 Prepare select * from axman_test where name = ?
55 Execute select * from axman_test where name = 'axman\' or 1==1'
111028 10:10:24 55 Close stmt
55 Prepare select * from axman_test where name = ?
55 Execute select * from axman_test where name = 'axman'
55 Close stmt
55 Quit
55 Quit
同一个SQL语句发生了两次预编译。这不是我们想要的效果,要想对同一SQL语句多次执行不是每次都预编译,就要使用cachePrepStmts=true,这个选项可以让JVM端缓存每个SQL语句的预编译结果,说白了就是以SQL语句为key, 将预编译结果缓存起来,下次遇到相同的SQL语句时作为key去get一下看看有没有这个SQL语句的预编译结果,有就直接合出来用。我们还是以事实来说明:
上面的代码只修改String url = "jdbc:mysql://localhost:3306/mysql?useServerPrepStmts=true&cachePrepStmts=true&prepStmtCacheSize=25&prepStmtCacheSqlLimit=256";
这行代码中有其它参数自己去读文档,我不多啰嗦,执行的结果:
111028 10:27:23 58 Connect root@localhost on mysql
58 Query /* mysql-connector-java-5.1.18 ( Revision: tonci.grgin@oracle.com-20110930151701-jfj14ddfq48ifkfq ) */SHOW VARIABLES WHERE Variable_name ='language' OR Variable_name = 'net_write_timeout' OR Variable_name = 'interactive_timeout' OR Variable_name = 'wait_timeout' OR Variable_name = 'character_set_client' OR Variable_name = 'character_set_connection' OR Variable_name = 'character_set' OR Variable_name = 'character_set_server' OR Variable_name = 'tx_isolation' OR Variable_name = 'transaction_isolation' OR Variable_name = 'character_set_results' OR Variable_name = 'timezone' OR Variable_name = 'time_zone' OR Variable_name = 'system_time_zone' OR Variable_name = 'lower_case_table_names' OR Variable_name = 'max_allowed_packet' OR Variable_name = 'net_buffer_length' OR Variable_name = 'sql_mode' OR Variable_name = 'query_cache_type' OR Variable_name = 'query_cache_size' OR Variable_name = 'init_connect'
58 Query /* mysql-connector-java-5.1.18 ( Revision: tonci.grgin@oracle.com-20110930151701-jfj14ddfq48ifkfq ) */SELECT @@session.auto_increment_increment
58 Query SHOW COLLATION
58 Query SET NAMES latin1
58 Query SET character_set_results = NULL
58 Query SET autocommit=1
58 Query SET sql_mode='STRICT_TRANS_TABLES'
58 Prepare select * from axman_test where name = ?
58 Execute select * from axman_test where name = 'axman\' or 1==1'
111028 10:27:24 58 Execute select * from axman_test where name = 'axman'
58 Quit
注意仅发生一次预编译,尽管代码本身在第一次执行后关闭了ps.close();但因为使用了cachePrepStmts=true,底层并没有真实关闭。
千万注意,同一条SQL语句尽量在一个全局的地方定义,然后在不同地方引用,这样做一是为了DBA方便地对SQL做统一检查和优化,就象IBatis把SQL语句定义在XML文件中一样。二是同一语句不同写法,即使空格不同,大小写不同也会重新预编译,因为JVM端缓存是直接以SQL本身为key而不会对SQL格式化以后再做为key。
我们来看下面的输出:
35 Prepare select * from axman_test where name = ?
35 Execute select * from axman_test where name = 'axman\' or 1==1'
111029 9:54:31 35 Prepare select * FROM axman_test where name = ?
35 Execute select * FROM axman_test where name = 'axman'
第一条语句和第二条语句的差别是FROM在第二条语句中被大写了,这样还是发生了两次预编译。
37 Prepare select * from axman_test where name = ?
37 Execute select * from axman_test where name = 'axman\' or 1==1'
111029 9:59:00 37 Prepare select * from axman_test where name = ?
37 Execute select * from axman_test where name = 'axman'
这里两条语句只是第二条的from后面多了个空格,因为你现在看到是HTML格式,如果不加转义符,两个空格也显示一个空格,所以你能可看不到区别,但你可以在自己的机器上试一下。
五.即使没有开启MySQL的预编译,坚持使用PreparedStatement仍然非常必要。
在第三节的最后我说到"注意我的第一条语句select * from axman_test where name = 'axman\' or 1==1',下面还会说到它。",现在我们回过头来看,即使没有开启MySQL端的预编译,我们仍然要坚持使用PreparedStatement,因为JVM端对PreparedStatement的SQL语句进行了参数化,即用占位符替换参数,以后任何内容输入都是字符串或其它类型的值,而不会和原始的SQL语句拚接产生SQL注入,对字符串中的任何字符都会做检查,如果可能是SQL语句使用的标识符,会进行转义。然后发送一个合法的安全的SQL语句给数据库执行。
标签: mysqljavacharacterquerycollationtimezone
2011-10-28 10:47 7140人阅读 评论(4) 收藏 举报
分类: go deep into final(37) JDBC(28)
版权声明:本文为博主原创文章,未经博主允许不得转载。
在Java编程中,应用代码绝大多数使用了PreparedStatement,无论你是直接使用JDBC还是使用框架。
在Java编程中,绝大多数使用了使用了PreparedStatement连接MySQL的应用代码没有启用预编译,无论你是直接使用JDBC还是使用框架。
在我所能见到的项目中,几乎没有见过启用MySQL预编译功能的。网上更有文章说MySQL不支持预编译,实在是害人不浅。
要想知道你的应用是否真正的使用了预编译,请执行:show global status like '%prepare%';看看曾经编译过几条,当前Prepared_stmt_count 是多少。大多数是0吧?
这篇文章分以下几个方面:
一.MySQL是支持预编译的
打开MySQL日志功能,启动MySQL,然后 tail -f mysql.log.path(默认:/var/log/mysql/mysql.log).
create table axman_test (ID int(4) auto_increment primary key, name varchar(20),age int(4));
insert into axman_test (name,age) values ('axman',1000);
prepare myPreparedStmt from 'select * from axman_test where name = ?';
set @name='axman';
execute myPreparedStmt using @name;
控制台可以正确地输出:
mysql> execute myPreparedStmt using @name;
+----+-------+------+
| ID | name | age |
+----+-------+------+
| 1 | axman | 1000 |
+----+-------+------+
1 row in set (0.00 sec)
而log文件中也忠实地记录如下:
111028 9:25:06 51 Query prepare myPreparedStmt from 'select * from axman_test where name = ?'
51 Prepare select * from axman_test where name = ?
51 Query set @name='axman'
111028 9:25:08 51 Query execute myPreparedStmt using @name
51 Execute select * from axman_test where name = 'axman'
二.通过JDBC本身是可以预编译的,这个不用多说。相当于我们把控制台输入的命令直接通过JDBC语句来执行:
Class.forName("org.gjt.mm.mysql.Driver");
String url = "jdbc:mysql://localhost:3306/mysql";
Connection conn = null;
try {
conn = DriverManager.getConnection(url, "root", "12345678");
Statement stmt = conn.createStatement();
/*以下忽略返回值处理*/
stmt.executeUpdate("prepare mystmt from 'select * from axman_test where name = ?'");
stmt.execute("set @name='axman'");
stmt.executeQuery("execute mystmt using @name");
stmt.close();
} finally {
if (conn != null) {
conn.close();
}
}
看日志输出:
111028 9:30:19 52 Connect root@localhost on mysql
52 Query /* @MYSQL_CJ_FULL_PROD_NAME@ ( Revision: @MYSQL_CJ_REVISION@ ) */SHOW VARIABLES WHERE Variable_name ='language' OR Variable_name = 'net_write_timeout' OR Variable_name = 'interactive_timeout' OR Variable_name = 'wait_timeout' OR Variable_name = 'character_set_client' OR Variable_name = 'character_set_connection' OR Variable_name = 'character_set' OR Variable_name = 'character_set_server' OR Variable_name = 'tx_isolation' OR Variable_name = 'transaction_isolation' OR Variable_name = 'character_set_results' OR Variable_name = 'timezone' OR Variable_name = 'time_zone' OR Variable_name = 'system_time_zone' OR Variable_name = 'lower_case_table_names' OR Variable_name = 'max_allowed_packet' OR Variable_name = 'net_buffer_length' OR Variable_name = 'sql_mode' OR Variable_name = 'query_cache_type' OR Variable_name = 'query_cache_size' OR Variable_name = 'init_connect'
52 Query /* @MYSQL_CJ_FULL_PROD_NAME@ ( Revision: @MYSQL_CJ_REVISION@ ) */SELECT @@session.auto_increment_increment
52 Query SHOW COLLATION
52 Query SET NAMES latin1
52 Query SET character_set_results = NULL
52 Query SET autocommit=1
52 Query SET sql_mode='STRICT_TRANS_TABLES'
52 Query prepare mystmt from 'select * from axman_test where name = ?'
52 Prepare select * from axman_test where name = ?
52 Query set @name='axman'
52 Query execute mystmt using @name
52 Execute select * from axman_test where name = 'axman'
52 Quit
三.默认的PrearedStatement不能开启MySQL预编译功能:
虽然第二节中我们通过JDBC手工指定MySQL进行预编译,但是PrearedStatement却并不自动帮我们做这件事。
Class.forName("org.gjt.mm.mysql.Driver");
String url = "jdbc:mysql://localhost:3306/mysql";
Connection conn = null;
try {
conn = DriverManager.getConnection(url, "root", "12345678");
PreparedStatement ps = conn.prepareStatement("select * from axman_test where name = ?");
ps.setString(1, "axman' or 1==1");
ResultSet rs = ps.executeQuery();
if (rs.next()) {
System.out.println(rs.getString(1));
}
Thread.sleep(1000);
rs.close();
ps.clearParameters();
ps.setString(1, "axman");
rs = ps.executeQuery();
if (rs.next()) {
System.out.println(rs.getString(1));
}
rs.close();
ps.close();
} finally {
if (conn != null) {
conn.close();
}
}
废话少说,直接看日志:
111028 9:54:03 53 Connect root@localhost on mysql
53 Query /* @MYSQL_CJ_FULL_PROD_NAME@ ( Revision: @MYSQL_CJ_REVISION@ ) */SHOW VARIABLES WHERE Variable_name ='language' OR Variable_name = 'net_write_timeout' OR Variable_name = 'interactive_timeout' OR Variable_name = 'wait_timeout' OR Variable_name = 'character_set_client' OR Variable_name = 'character_set_connection' OR Variable_name = 'character_set' OR Variable_name = 'character_set_server' OR Variable_name = 'tx_isolation' OR Variable_name = 'transaction_isolation' OR Variable_name = 'character_set_results' OR Variable_name = 'timezone' OR Variable_name = 'time_zone' OR Variable_name = 'system_time_zone' OR Variable_name = 'lower_case_table_names' OR Variable_name = 'max_allowed_packet' OR Variable_name = 'net_buffer_length' OR Variable_name = 'sql_mode' OR Variable_name = 'query_cache_type' OR Variable_name = 'query_cache_size' OR Variable_name = 'init_connect'
53 Query /* @MYSQL_CJ_FULL_PROD_NAME@ ( Revision: @MYSQL_CJ_REVISION@ ) */SELECT @@session.auto_increment_increment
53 Query SHOW COLLATION
53 Query SET NAMES latin1
53 Query SET character_set_results = NULL
53 Query SET autocommit=1
53 Query SET sql_mode='STRICT_TRANS_TABLES'
53 Query select * from axman_test where name = 'axman\' or 1==1'
111028 9:54:04 53 Query select * from axman_test where name = 'axman'
53 Quit
两条语句都是直接执行,而没有预编译。注意我的第一条语句select * from axman_test where name = 'axman\' or 1==1',下面还会说到它。
接着我们改变一下jdbc.url的选项:
String url = "jdbc:mysql://localhost:3306/mysql?cachePrepStmts=true&prepStmtCacheSize=25&prepStmtCacheSqlLimit=256";
执行上面的代码还是没有开启Mysql的预编译。
四.只有使用了useServerPrepStmts=true才能开启Mysql的预编译。
上面的代码其它不变,只修改String url = "jdbc:mysql://localhost:3306/mysql?useServerPrepStmts=true";
查看日志:
111028 10:04:52 54 Connect root@localhost on mysql
54 Query /* @MYSQL_CJ_FULL_PROD_NAME@ ( Revision: @MYSQL_CJ_REVISION@ ) */SHOW VARIABLES WHERE Variable_name ='language' OR Variable_name = 'net_write_timeout' OR Variable_name = 'interactive_timeout' OR Variable_name = 'wait_timeout' OR Variable_name = 'character_set_client' OR Variable_name = 'character_set_connection' OR Variable_name = 'character_set' OR Variable_name = 'character_set_server' OR Variable_name = 'tx_isolation' OR Variable_name = 'transaction_isolation' OR Variable_name = 'character_set_results' OR Variable_name = 'timezone' OR Variable_name = 'time_zone' OR Variable_name = 'system_time_zone' OR Variable_name = 'lower_case_table_names' OR Variable_name = 'max_allowed_packet' OR Variable_name = 'net_buffer_length' OR Variable_name = 'sql_mode' OR Variable_name = 'query_cache_type' OR Variable_name = 'query_cache_size' OR Variable_name = 'init_connect'
54 Query /* @MYSQL_CJ_FULL_PROD_NAME@ ( Revision: @MYSQL_CJ_REVISION@ ) */SELECT @@session.auto_increment_increment
54 Query SHOW COLLATION
54 Query SET NAMES latin1
54 Query SET character_set_results = NULL
54 Query SET autocommit=1
54 Query SET sql_mode='STRICT_TRANS_TABLES'
54 Prepare select * from axman_test where name = ?
54 Execute select * from axman_test where name = 'axman\' or 1==1'
111028 10:04:53 54 Execute select * from axman_test where name = 'axman'
54 Close stmt
54 Quit
如果useServerPrepStmts=true,ConneciontImpl在prepareStatement时会产生一个ServerPreparedStatement.在这个ServerPreparedStatement对象构造时首先会把当前SQL语句发送给MySQL进行预编译,然后将返回的结果缓存起来,其中包含预编译的名称(我们可以看成是当前SQL语句编译后的函数名),签名(参数列表),然后执行的时候就会直接把参数传给这个函数请求MySQL执行这个函数。否则返回的是客户端预编译语句,它仅做参数化工作,见第五节。
ServerPreparedStatement在请求预编译和执行预编译后的SQL 函数时,虽然和我们上面手工预编译工作相同,但它与MySQL交互使用的是压缩格式,如prepare指令码是22,这样可以减少交互时传输的数据量。
注意上面的代码中,两次执行使用的是同一个PreparedStatement句柄.如果使用个不同的PreparedStatement句柄,把代码改成:
Class.forName("org.gjt.mm.mysql.Driver");
String url = "jdbc:mysql://localhost:3306/mysql?useServerPrepStmts=true";
Connection conn = null;
try {
conn = DriverManager.getConnection(url, "root", "12345678");
PreparedStatement ps = conn.prepareStatement("select * from axman_test where name = ?");
ps.setString(1, "axman' or 1==1");
ResultSet rs = ps.executeQuery();
if (rs.next()) {
System.out.println(rs.getString(1));
}
Thread.sleep(1000);
rs.close();
ps.close();
ps = conn.prepareStatement("select * from axman_test where name = ?");
ps.setString(1, "axman");
rs = ps.executeQuery();
if (rs.next()) {
System.out.println(rs.getString(1));
}
rs.close();
ps.close();
} finally {
if (conn != null) {
conn.close();
}
}
再看日志输出:
Connect root@localhost on mysql
55 Query /* @MYSQL_CJ_FULL_PROD_NAME@ ( Revision: @MYSQL_CJ_REVISION@ ) */SHOW VARIABLES WHERE Variable_name ='language' OR Variable_name = 'net_write_timeout' OR Variable_name = 'interactive_timeout' OR Variable_name = 'wait_timeout' OR Variable_name = 'character_set_client' OR Variable_name = 'character_set_connection' OR Variable_name = 'character_set' OR Variable_name = 'character_set_server' OR Variable_name = 'tx_isolation' OR Variable_name = 'transaction_isolation' OR Variable_name = 'character_set_results' OR Variable_name = 'timezone' OR Variable_name = 'time_zone' OR Variable_name = 'system_time_zone' OR Variable_name = 'lower_case_table_names' OR Variable_name = 'max_allowed_packet' OR Variable_name = 'net_buffer_length' OR Variable_name = 'sql_mode' OR Variable_name = 'query_cache_type' OR Variable_name = 'query_cache_size' OR Variable_name = 'init_connect'
55 Query /* @MYSQL_CJ_FULL_PROD_NAME@ ( Revision: @MYSQL_CJ_REVISION@ ) */SELECT @@session.auto_increment_increment
55 Query SHOW COLLATION
55 Query SET NAMES latin1
55 Query SET character_set_results = NULL
55 Query SET autocommit=1
55 Query SET sql_mode='STRICT_TRANS_TABLES'
55 Prepare select * from axman_test where name = ?
55 Execute select * from axman_test where name = 'axman\' or 1==1'
111028 10:10:24 55 Close stmt
55 Prepare select * from axman_test where name = ?
55 Execute select * from axman_test where name = 'axman'
55 Close stmt
55 Quit
55 Quit
同一个SQL语句发生了两次预编译。这不是我们想要的效果,要想对同一SQL语句多次执行不是每次都预编译,就要使用cachePrepStmts=true,这个选项可以让JVM端缓存每个SQL语句的预编译结果,说白了就是以SQL语句为key, 将预编译结果缓存起来,下次遇到相同的SQL语句时作为key去get一下看看有没有这个SQL语句的预编译结果,有就直接合出来用。我们还是以事实来说明:
上面的代码只修改String url = "jdbc:mysql://localhost:3306/mysql?useServerPrepStmts=true&cachePrepStmts=true&prepStmtCacheSize=25&prepStmtCacheSqlLimit=256";
这行代码中有其它参数自己去读文档,我不多啰嗦,执行的结果:
111028 10:27:23 58 Connect root@localhost on mysql
58 Query /* mysql-connector-java-5.1.18 ( Revision: tonci.grgin@oracle.com-20110930151701-jfj14ddfq48ifkfq ) */SHOW VARIABLES WHERE Variable_name ='language' OR Variable_name = 'net_write_timeout' OR Variable_name = 'interactive_timeout' OR Variable_name = 'wait_timeout' OR Variable_name = 'character_set_client' OR Variable_name = 'character_set_connection' OR Variable_name = 'character_set' OR Variable_name = 'character_set_server' OR Variable_name = 'tx_isolation' OR Variable_name = 'transaction_isolation' OR Variable_name = 'character_set_results' OR Variable_name = 'timezone' OR Variable_name = 'time_zone' OR Variable_name = 'system_time_zone' OR Variable_name = 'lower_case_table_names' OR Variable_name = 'max_allowed_packet' OR Variable_name = 'net_buffer_length' OR Variable_name = 'sql_mode' OR Variable_name = 'query_cache_type' OR Variable_name = 'query_cache_size' OR Variable_name = 'init_connect'
58 Query /* mysql-connector-java-5.1.18 ( Revision: tonci.grgin@oracle.com-20110930151701-jfj14ddfq48ifkfq ) */SELECT @@session.auto_increment_increment
58 Query SHOW COLLATION
58 Query SET NAMES latin1
58 Query SET character_set_results = NULL
58 Query SET autocommit=1
58 Query SET sql_mode='STRICT_TRANS_TABLES'
58 Prepare select * from axman_test where name = ?
58 Execute select * from axman_test where name = 'axman\' or 1==1'
111028 10:27:24 58 Execute select * from axman_test where name = 'axman'
58 Quit
注意仅发生一次预编译,尽管代码本身在第一次执行后关闭了ps.close();但因为使用了cachePrepStmts=true,底层并没有真实关闭。
千万注意,同一条SQL语句尽量在一个全局的地方定义,然后在不同地方引用,这样做一是为了DBA方便地对SQL做统一检查和优化,就象IBatis把SQL语句定义在XML文件中一样。二是同一语句不同写法,即使空格不同,大小写不同也会重新预编译,因为JVM端缓存是直接以SQL本身为key而不会对SQL格式化以后再做为key。
我们来看下面的输出:
35 Prepare select * from axman_test where name = ?
35 Execute select * from axman_test where name = 'axman\' or 1==1'
111029 9:54:31 35 Prepare select * FROM axman_test where name = ?
35 Execute select * FROM axman_test where name = 'axman'
第一条语句和第二条语句的差别是FROM在第二条语句中被大写了,这样还是发生了两次预编译。
37 Prepare select * from axman_test where name = ?
37 Execute select * from axman_test where name = 'axman\' or 1==1'
111029 9:59:00 37 Prepare select * from axman_test where name = ?
37 Execute select * from axman_test where name = 'axman'
这里两条语句只是第二条的from后面多了个空格,因为你现在看到是HTML格式,如果不加转义符,两个空格也显示一个空格,所以你能可看不到区别,但你可以在自己的机器上试一下。
五.即使没有开启MySQL的预编译,坚持使用PreparedStatement仍然非常必要。
在第三节的最后我说到"注意我的第一条语句select * from axman_test where name = 'axman\' or 1==1',下面还会说到它。",现在我们回过头来看,即使没有开启MySQL端的预编译,我们仍然要坚持使用PreparedStatement,因为JVM端对PreparedStatement的SQL语句进行了参数化,即用占位符替换参数,以后任何内容输入都是字符串或其它类型的值,而不会和原始的SQL语句拚接产生SQL注入,对字符串中的任何字符都会做检查,如果可能是SQL语句使用的标识符,会进行转义。然后发送一个合法的安全的SQL语句给数据库执行。
相关推荐
在Java编程中,数据连接池(Data Source或Connection Pool)是一种管理数据库连接的机制,它提高了应用程序的性能和效率。JDBC(Java Database Connectivity)是Java中用来与数据库交互的标准API。在这里,我们讨论...
- **描述**:用于划分文档中的区域,便于应用样式或布局。 - **示例**:`<DIV>...</DIV>`。 4. **区分对齐 `<DIV ALIGN="...">`** - **描述**:设置`<DIV>`元素内的内容对齐方式。 - **示例**:`<DIV ALIGN=...
在本文中,我们将深入探讨如何在JavaServer Pages (JSP) 中连接到不同的数据库系统,包括Oracle、SQL Server和DB2。这些示例代码对于初学者来说是非常实用的学习资源,但需要注意,直接在JSP中处理数据库逻辑并不...
MySQL中的连结查询是数据库操作中的重要组成部分,它允许我们从多个表中获取相关数据,合并成一个结果集。在本文件中,我们探讨了几种不同的连接类型:交叉连接、自然连接、内连接、外连接以及联合查询。 1. **交叉...
`Statement`适用于静态SQL,而`PreparedStatement`则用于预编译的SQL,提供更好的性能和安全性。 5. **处理结果集** 执行查询后,会返回一个`ResultSet`对象,可以通过迭代遍历结果集,获取每一行的数据。例如: ...
- PreparedStatement:预编译的SQL语句,提高了性能且能防止SQL注入。 - CallableStatement:用于调用存储过程。 2. **数据库驱动** - 不同的数据库有不同的JDBC驱动,例如MySQL的Connector/J,Oracle的ojdbc,...
DP_res 将numpy导入为np file = open(“ C://Users//WANGFEN//Desktop//idkp1-10.txt”,“ r”)list = file.readlines()#每一行数据写入到列表中print(list)列表= [] #将txt文件转换成二进制格式保存代表...
对于初学者来说,了解如何在JSP中进行数据库操作是至关重要的,尽管这可能不是最佳实践,但有助于学习基本概念。在实际项目中,通常会采用MVC(Model-View-Controller)架构来分离业务逻辑和视图。 首先,我们来看...
基于JAVA的mysql数据库管理软件,可以做毕业设计,有专门论文,如果需要另行联系,实现可视化的mysql数据库操作,以及对数据库服务器的监控,界面友好
基于GitHub2023-03-28最新代码编译,适用于苹果arm架构芯片M1、M2以及Macos,版本1.12.3,本资源支持MySQL和数据库连结池。 Poco C++库是: 一系列C++类库,类似Java类库,.Net框架,Apple的Cocoa; 侧重于互联网...
根据提供的标题、描述、标签以及部分代码内容,我们可以总结出以下关于VB.NET中数据库连接与操作的相关知识点: ### VB.NET中的数据库连接与操作 #### 一、标题:xiaosongshu 虽然“xiaosongshu”这个标题本身并...
compiler是Java中的一种编译器,用于将源代码编译成机器代码。 container(容器) container是Java中的一种容器,用于存储和管理对象。 iterator(迭代器) iterator是Java中的一种迭代器,用于遍历容器中的...
4. 一个完整的编译程序中,不仅包含词法分析、语法分析、语义分析、中间代码生成、代码优化、目标代码生成,还应包括表格处理和出错处理。 5. 汇编程序是将高级语言或汇编语言翻译成机器语言,而编译程序是将高级...
在Web应用程序开发中,数据库连接是必不可少的部分,尤其是对于使用Java技术栈的开发者,如JSP和Servlet。本文将详细讲解如何使用JSP和Servlet进行数据库连接,以帮助初学者理解并实践这一基础技能。 首先,JSP...
重要连结 统计书 概率统计链接 统计中的68-95-99.7规则 from scipy . stats import norm mean = 0 sigma = 1 # cdf differences sig1 = norm . cdf ( sigma , mean , sigma ) - norm . cdf ( - sigma , mean , sigma...
根据提供的标题、描述、标签及部分内容,我们可以深入探讨编译原理相关的重要知识点,特别是关于文法、句型、短语、句柄等概念及其应用。 ### 关键知识点解析 #### 文法与句型 1. **文法表示**: 文法是形式语言的...
然而,在实际应用中,可能会遇到由于数据库驱动程序与Delphi应用程序之间兼容性问题而导致的数据类型不支持错误。 #### 二、问题分析 **原因分析:** 1. **数据库驱动版本不匹配:** 使用的Oracle OLE DB ...
【标题】"jsp连结数据库大全.rar"是一个包含多种数据库连接方法的资源包,主要针对使用Java Server Pages(JSP)技术的开发者。这个压缩包提供了全面的示例和指南,帮助开发者学习如何在JSP应用程序中连接和操作各种...
超连结 不变的酷网址。 超链接提供了不可变URL的纯Python实现。 基于和 ,超链接URL使同时使用URI和IRI变得容易。 Hyperlink已针对Python 2.7、3.4、3.5、3.6、3.7、3.8和PyPy进行了测试。 完整文档可在“。 安装 ...
在计算机图形学中,多边形的拓扑构建与左转算法是两个关键概念,尤其在二维几何处理和游戏开发中扮演着重要角色。本文将深入探讨如何利用实验数据实现多边形左转算法,以及如何自动构建多边形。 首先,让我们了解...