PreparedStatement
在说PreparedStatement之前,我们来看看什么是预编译。其实预编译是MySQL数据库本身都支持的。但是MySQL Server 4.1之前的版本是不支持预编译的。(具体是否包括4.1还得读者们亲自试验)
在这里,笔者用的是MySQL5.6绿色版。
MySQL中的预编译功能是这样的
预编译的好处:
大家平时都使用过JDBC中的PreparedStatement接口,它有预编译功能。什么是预编译功能呢?它有什么好处呢?
当客户发送一条SQL语句给服务器后,服务器总是需要校验SQL语句的语法格式是否正确,然后把SQL语句编译成可执行的函数,最后才是执行SQL语句。其中校验语法,和编译所花的时间可能比执行SQL语句花的时间还要多。
注意:可执行函数存储在MySQL服务器中,并且当前连接断开后,MySQL服务器会清除已经存储的可执行函数。
如果我们需要执行多次insert语句,但只是每次插入的值不同,MySQL服务器也是需要每次都去校验SQL语句的语法格式,以及编译,这就浪费了太多的时间。如果使用预编译功能,那么只对SQL语句进行一次语法校验和编译,所以效率要高。
- 1
- 2
- 3
- 4
MySQL执行预编译
MySQL执行预编译分为如三步:
1.执行预编译语句,例如:prepare showUsersByLikeName from 'select * from user where username like ?';
2.设置变量,例如:set @username='%小明%';
3.执行语句,例如:execute showUsersByLikeName using @username;
- 1
- 2
- 3
如果需要再次执行myfun,那么就不再需要第一步,即不需要再编译语句了:
1.设置变量,例如:set @username='%小宋%';
2.执行语句,例如:execute showUsersByLikeName using @username;
- 1
- 2
如果你看MySQL日志记录,你就会看到:
配置MySQL日志记录
路径地址可以自己修改。
log-output=FILE
general-log=1
general_log_file="E:\mysql.log"
slow-query-log=1
slow_query_log_file="E:\mysql_slow.log"
long_query_time=2
- 1
- 2
- 3
- 4
- 5
- 6
配置之后就重启MySQL服务器:
- 在cmd管理员界面执行以下操作。
net stop mysql
net start mysql
- 1
- 2
使用PreparedStatement执行sql查询
- JDBC MySQL驱动5.0.5以后的版本默认PreparedStatement是关闭预编译功能的,所以需要我们手动开启。而之前的JDBC MySQL驱动版本默认是开启预编译功能的。
- MySQL数据库服务器的预编译功能在4.1之后才支持预编译功能的。如果数据库服务器不支持预编译功能时,并且使用PreparedStatement开启预编译功能是会抛出异常的。这点非常重要。笔者用的是mysql-connector-jar-5.1.13版本的JDBC驱动。
在我们以前写项目的时候,貌似都没有注意是否开启PreparedStatement的预编译功能,以为它一直都是在使用的,现在看看不开启PreparedStatement的预编译,查看MySQL的日志输出到底是怎么样的。
@Test
public void showUser(){
//数据库连接
Connection connection = null;
//预编译的Statement,使用预编译的Statement提高数据库性能
PreparedStatement preparedStatement = null;
//结果 集
ResultSet resultSet = null;
try {
//加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
//通过驱动管理类获取数据库链接
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis", "root", "");
//定义sql语句 ?表示占位符
String sql = "select * from user where username = ?";
//获取预处理statement
preparedStatement = connection.prepareStatement(sql);
//设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值
preparedStatement.setString(1, "王五");
//向数据库发出sql执行查询,查询出结果集
resultSet = preparedStatement.executeQuery();
preparedStatement.setString(1, "张三");
resultSet = preparedStatement.executeQuery();
//遍历查询结果集
while(resultSet.next()){
System.out.println(resultSet.getString("id")+" "+resultSet.getString("username"));
}
resultSet.close();
preparedStatement.close();
System.out.println("#############################");
} catch (Exception e) {
e.printStackTrace();
}finally{
//释放资源
if(resultSet!=null){
try {
resultSet.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(preparedStatement!=null){
try {
preparedStatement.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(connection!=null){
try {
connection.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
这是输出日志:
20 Query /* mysql-connector-java-5.1.13 ( Revision: ${bzr.revision-id} ) */SELECT @@session.auto_increment_increment
20 Query SHOW COLLATION
20 Query SET NAMES utf8mb4
20 Query SET character_set_results = NULL
20 Query SET autocommit=1
20 Query select * from user where username = '王五'
20 Query select * from user where username = '张三'
20 Quit
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
可以看到,在日志中并没有看到"prepare"
命令来预编译"select * from user where username = ?"
这个sql模板。所以我们一般用的PreparedStatement并没有用到预编译功能的,只是用到了防止sql注入攻击的功能。防止sql注入攻击的实现是在PreparedStatement中实现的,和服务器无关。笔者在源码中看到,PreparedStatement对敏感字符已经转义过了。
在PreparedStatement中开启预编译功能
- 设置MySQL连接URL参数:useServerPrepStmts=true,如下所示。
jdbc:mysql://localhost:3306/mybatis?&useServerPrepStmts=true
- 这样才能保证mysql驱动会先把SQL语句发送给服务器进行预编译,然后在执行executeQuery()时只是把参数发送给服务器。
再次执行上面的程序看下MySQL日志输出:
21 Query SHOW WARNINGS
21 Query /* mysql-connector-java-5.1.13 ( Revision: ${bzr.revision-id} ) */SELECT @@session.auto_increment_increment
21 Query SHOW COLLATION
21 Query SET NAMES utf8mb4
21 Query SET character_set_results = NULL
21 Query SET autocommit=1
21 Prepare select * from user where username = ?
21 Execute select * from user where username = '王五'
21 Execute select * from user where username = '张三'
21 Close stmt
21 Quit
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
很明显已经进行了预编译,Prepare select * from user where username = ?
,这一句就是对sql语句模板进行预编译的日志。好的非常Nice。
注意:
我们设置的是MySQL连接参数,目的是告诉MySQL JDBC的PreparedStatement使用预编译功能(5.0.5之后的JDBC驱动版本需要手动开启,而之前的默认是开启的),不管我们是否使用预编译功能,MySQL Server4.1版本以后都是支持预编译功能的。
cachePrepStmts参数
当使用不同的PreparedStatement对象来执行相同的SQL语句时,还是会出现编译两次的现象,这是因为驱动没有缓存编译后的函数key,导致二次编译。如果希望缓存编译后函数的key,那么就要设置cachePrepStmts参数为true。例如:
jdbc:mysql://localhost:3306/mybatis?useServerPrepStmts=true&cachePrepStmts=true
- 1
程序代码:
@Test
public void showUser(){
//数据库连接
Connection connection = null;
//预编译的Statement,使用预编译的Statement提高数据库性能
PreparedStatement preparedStatement = null;
//结果 集
ResultSet resultSet = null;
try {
//加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
//通过驱动管理类获取数据库链接
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?&useServerPrepStmts=true&cachePrepStmts=true", "root", "");
preparedStatement=connection.prepareStatement("select * from user where username like ?");
preparedStatement.setString(1, "%小明%");
resultSet = preparedStatement.executeQuery();
//遍历查询结果集
while(resultSet.next()){
System.out.println(resultSet.getString("id")+" "+resultSet.getString("username"));
}
//注意这里必须要关闭当前PreparedStatement对象流,否则下次再次创建PreparedStatement对象的时候还是会再次预编译sql模板,使用PreparedStatement对象后不关闭当前PreparedStatement对象流是不会缓存预编译后的函数key的
resultSet.close();
preparedStatement.close();
preparedStatement=connection.prepareStatement("select * from user where username like ?");
preparedStatement.setString(1, "%三%");
resultSet = preparedStatement.executeQuery();
//遍历查询结果集
while(resultSet.next()){
System.out.println(resultSet.getString("id")+" "+resultSet.getString("username"));
}
resultSet.close();
preparedStatement.close();
} catch (Exception e) {
e.printStackTrace();
}finally{
//释放资源
if(resultSet!=null){
try {
resultSet.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(preparedStatement!=null){
try {
preparedStatement.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(connection!=null){
try {
connection.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
日志输出:
24 Query SHOW WARNINGS
24 Query /* mysql-connector-java-5.1.13 ( Revision: ${bzr.revision-id} ) */SELECT @@session.auto_increment_increment
24 Query SHOW COLLATION
24 Query SET NAMES utf8mb4
24 Query SET character_set_results = NULL
24 Query SET autocommit=1
24 Prepare select * from user where username like ?
24 Execute select * from user where username like '%小明%'
24 Execute select * from user where username like '%三%'
24 Quit
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
注意:每次使用PreparedStatement对象后都要关闭该PreparedStatement对象流,否则预编译后的函数key是不会缓存的。
Statement执行sql语句是否会对编译后的函数进行缓存
这个不好说,对于每个数据库的具体实现都是不一样的,对于预编译肯定都大体相同,但是对于Statement和普通sql,数据库一般都是先检查sql语句是否正确,然后编译sql语句成为函数,最后执行函数。其实也不乏某些数据库很疯狂,对于普通sql的函数进行缓存。但是目前的主流数据库都不会对sql函数进行缓存的。因为sql语句变化那么多,如果对所有函数缓存,那么对于内存的消耗也是非常巨大的。
如果你不确定普通sql语句的函数是否被存储,那要怎么做呢??
其实还是一个道理,查看MySQL日志记录:检查第二次执行相同sql语句时,是否是直接通过execute来进行查询的。
@Test
public void showUser(){
//数据库连接
Connection connection = null;
//预编译的Statement,使用预编译的Statement提高数据库性能
PreparedStatement preparedStatement = null;
//结果 集
ResultSet resultSet = null;
try {
//加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
//通过驱动管理类获取数据库链接
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?&useServerPrepStmts=true&cachePrepStmts=true", "root", "");
Statement statement=connection.createStatement();
resultSet = statement.executeQuery("select * from user where username='小天'");
//遍历查询结果集
while(resultSet.next()){
System.out.println(resultSet.getString("id")+" "+resultSet.getString("username"));
}
resultSet.close();
statement.close();
statement=connection.createStatement();
resultSet = statement.executeQuery("select * from user where username='小天'");
//遍历查询结果集
while(resultSet.next()){
System.out.println(resultSet.getString("id")+" "+resultSet.getString("username"));
}
resultSet.close();
statement.close();
} catch (Exception e) {
e.printStackTrace();
}finally{
//释放资源
if(resultSet!=null){
try {
resultSet.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(preparedStatement!=null){
try {
preparedStatement.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(connection!=null){
try {
connection.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
日志记录:
26 Query SHOW WARNINGS
26 Query /* mysql-connector-java-5.1.13 ( Revision: ${bzr.revision-id} ) */SELECT @@session.auto_increment_increment
26 Query SHOW COLLATION
26 Query SET NAMES utf8mb4
26 Query SET character_set_results = NULL
26 Query SET autocommit=1
26 Query select * from user where username='小天'
26 Query select * from user where username='小天'
26 Quit
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
看日志就会知道,都是Query命令,所以并没有存储函数。
总结:
- 所以到了这里我的疑惑都解开了,PreparedStatement的预编译是数据库进行的,编译后的函数key是缓存在PreparedStatement中的,编译后的函数是缓存在数据库服务器中的。预编译前有检查sql语句语法是否正确的操作。只有数据库服务器支持预编译功能时,JDBC驱动才能够使用数据库的预编译功能,否则会报错。预编译在比较新的JDBC驱动版本中默认是关闭的,需要配置连接参数才能够打开。在已经配置好了数据库连接参数的情况下,Statement对于MySQL数据库是不会对编译后的函数进行缓存的,数据库不会缓存函数,Statement也不会缓存函数的key,所以多次执行相同的一条sql语句的时候,还是会先检查sql语句语法是否正确,然后编译sql语句成函数,最后执行函数。
- 对于PreparedStatement在设置参数的时候会对参数进行转义处理。
- 因为PreparedStatement已经对sql模板进行了编译,并且存储了函数,所以PreparedStatement做的就是把参数进行转义后直接传入参数到数据库,然后让函数执行。这就是为什么PreparedStatement能够防止sql注入攻击的原因了。
- PreparedStatement的预编译还有注意的问题,在数据库端存储的函数和在PreparedStatement中存储的key值,都是建立在数据库连接的基础上的,如果当前数据库连接断开了,数据库端的函数会清空,建立在连接上的PreparedStatement里面的函数key也会被清空,各个连接之间的预编译都是互相独立的。
使用Statement执行预编译
使用Statement执行预编译就是把上面的原始SQL语句预编译执行一次。
Connection con = JdbcUtils.getConnection();
Statement stmt = con.createStatement();
stmt.executeUpdate("prepare myfun from 'select * from t_book where bid=?'");
stmt.executeUpdate("set @str='b1'");
ResultSet rs = stmt.executeQuery("execute myfun using @str");
while(rs.next()) {
System.out.print(rs.getString(1) + ", ");
System.out.print(rs.getString(2) + ", ");
System.out.print(rs.getString(3) + ", ");
System.out.println(rs.getString(4));
}
stmt.executeUpdate("set @str='b2'");
rs = stmt.executeQuery("execute myfun using @str");
while(rs.next()) {
System.out.print(rs.getString(1) + ", ");
System.out.print(rs.getString(2) + ", ");
System.out.print(rs.getString(3) + ", ");
System.out.println(rs.getString(4));
}
rs.close();
stmt.close();
con.close();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
在持久层框架中存在的问题
很多主流持久层框架(MyBatis,Hibernate)其实都没有真正的用上预编译,预编译是要我们自己在参数列表上面配置的,如果我们不手动开启,JDBC驱动程序5.0.5以后版本 默认预编译都是关闭的。
所以我们要在参数列表中配置,例如:
jdbc:mysql://localhost:3306/mybatis?&useServerPrepStmts=true&cachePrepStmts=true
相关推荐
C++预编译命令详解 预编译命令是C语言和C++语言中非常重要的一部分,它们可以在编译前对代码进行处理和修改,从而影响编译器的行为。本文将详细介绍各种C++预编译命令,包括#pragma指令的多种用法。 #pragma指令 ...
### 预编译头文件详解 #### 一、预编译头文件概念与作用 预编译头文件(Precompiled Headers, PCH)是一种优化技术,主要用于加速大型项目的编译过程。当项目中存在大量频繁使用的头文件时,通过预编译这些头文件...
本篇文章将详细讲解`jdbcTemplate`的预编译使用,以及如何通过回调方法进行定制化的数据处理。 首先,`jdbcTemplate`的核心功能是通过预编译的SQL语句(PreparedStatement)来执行数据库操作。预编译SQL可以有效...
在使用Visual Studio 2008 (VS2008) 进行软件开发时,开发者可能会遇到一个棘手的问题,即"fatal error C1853: 预编译头错误"。这个错误通常表示编译器在处理预编译头文件时遇到了不一致或意外的情况,导致编译过程...
预编译指令和预编译头文件 预编译是编译器在正式编译之前对源代码进行的预处理阶段,在这个阶段,编译器会对源代码进行分析和处理,以便生成可执行文件。在这个阶段,编译器会处理以 "#" 开头的预编译指令,如 #...
**OpenSSL 3.0.0 预编译二进制开发包详解** OpenSSL 是一个开源项目,提供了一套强大的加密库,包括各种安全协议、加密算法以及证书管理等,广泛应用于网络安全、服务器安全等领域。OpenSSL 3.0.0 是其最新版本,...
在Linux环境下进行C++开发时,预编译是提高构建效率和管理复杂项目的重要环节。预编译主要包括头文件预处理、预编译宏定义以及模块化预编译等概念。预编译过程有助于减少重复编译的工作量,特别是在大型项目中,可以...
网站的预编译是一个重要的开发流程,特别是在大型项目或者高性能网站的构建中,它能够显著提升网站的加载速度和运行效率。预编译是将动态语言(如PHP、Ruby on Rails、ASP.NET等)的代码在部署之前转换为静态HTML、...
**OpenSceneGraph (OSG) 预编译包详解** OpenSceneGraph(简称OSG)是一个开源的高性能3D图形库,专为实时可视化应用设计。它基于OpenGL API,提供了一套完整的工具集,包括场景图管理、图形渲染、动画、图像处理等...
Java 和 C 语言在编程范式上有显著差异,C 语言支持预编译指令,如 `#define`、`#ifdef`、`#endif` 等,这些指令在源代码编译之前进行处理,可以实现条件编译、宏定义等功能。然而,Java 本身并不直接支持这样的预...
JavaScript预编译是一种优化代码执行效率的技术,尤其在大型项目中尤为重要。预编译的主要目的是在实际运行前处理代码,减少解析和运行时的负担,提高应用的性能。本篇文章将深入探讨JavaScript预编译的概念、重要性...
预编译头(Precompiled Header,PCH)是C++编程中提高编译效率的一种技术。它的核心思想是将项目中频繁使用且不常改动的头文件预先编译成一个二进制文件,通常以.pch为扩展名,以便在后续的编译过程中快速加载这些...
MySQL 预编译功能 MySQL 预编译功能是指在执行 SQL 语句之前,对 SQL 语句进行编译和优化,以提高执行效率。在 MySQL 中,预编译功能可以通过使用 Prepare 语句来实现。 预编译功能的优点是可以减少 SQL 语句的...
【IIS网站预编译工具】是一个用于提升ASP.NET网站性能和优化服务器资源管理的实用程序,源码的提供使得用户可以根据自身需求进行定制。在ASP.NET框架中,预编译是一个重要的步骤,尤其在大型或者高流量的网站中,它...
《C语言预编译详解》 C语言的预编译机制是其独特之处,它将编译过程分为预处理和正式编译两个阶段。预处理阶段主要负责处理以符号“#”开头的预处理指令,这些指令不涉及语法和语义的检查,而是对源代码进行初步...
本示例主要关注如何在WebLogic环境中进行JSP的预编译,这有助于提升应用程序的启动速度和性能。预编译过程将JSP文件转换为Servlet源码,然后编译成.class文件,减少了运行时的解析和编译时间。 一、JSP预编译的重要...
开始我有个特殊应用方式需要用到Mesa,但是找了很久都没找到新的能用的预编译dll。自己尝试了在本机Win10、虚拟机Ubuntu、云服务器Ubuntu、云服务器Server 2012 R2上编译,结果都是失败的。只在云服务器Ubuntu上编译...
### GCC预编译详解 #### 一、GCC预编译概述 GCC (GNU Compiler Collection) 是一套开源的编译工具集,广泛应用于多种编程语言的编译任务中,特别是C和C++语言。预编译是GCC编译过程中非常关键的一环,它发生在实际...
本文提供了一个 JAVA 预编译示例代码,涵盖了预编译中使用 like、javaSQL 预编译异常、预编译语句支持 in 方式等多个方面的知识点。 1. 预编译中使用 like 在预编译中使用 like 时,需要在值的地方加 % 号,以便...
标题:C语言预编译命令用法 描述与知识点详解: C语言的预编译命令在编程中扮演着至关重要的角色,它们允许开发者在编译阶段执行一系列操作,包括但不限于宏定义、条件编译和文件包含等。在给定的描述中提到的“#...