0 概述
Statement 是 Java 执行数据库操作的一个重要接口,用于在已经建立数据库连接的基础上,向数据库发送要执行的SQL语句。
1 JDBC 使用流程
如下图所示,JDBC和相应的数据进行连接是由相应的驱动进行建立连接,连接可以创建不同Statement,然后由Statement接口向数据库发送要执行的SQL语句。
2 几种Statement对比
- Statement, 用于执行静态的sql语句或者说拼装好的sql语句
- PreparedStatement 对象用于执行带或不带 IN 参数的预编译 SQL 语句。
- CallableStatement 对象用于执行对数据库已存在的存储过程的调用。
PreparedStatement接口相比于Statement,有以下几个重要的特点:
提高安全,防SQL注入:Statement这种直接拼接sql的方式,容易发生sql注入,因此你就应该始终以PreparedStatement代替Statement(mybatis框架默认的就是PreparedStatement)。
预编译技术提高性能:PreparedStatement 创建后,向Server端发送执行带有占位符的SQL语句,把SQL语句在Server端的执行计划和执行逻辑缓存起来,通过参数来调用,减少Sql编译等过程,这里值得强调是在实际使用中mysql不一定开启了预编译技术,具体见后面分析,这个和具体的mysql版本也有一定的关系。
参数转义、编码简洁:Statement 所有的参数需要我们自己拼接好,这样就比较恶心而且有的参数比较难拼接。
看下面程序,首先开启mysql(本文使用的mysql Server版本5.7.13)的SQL执行过程日志,具体开启可以参考:MySQL开启general_log跟踪数据执行过程
import java.sql.*;
/**
* Created by hsc on 16/8/27.
*/
public class JdbcTest {
private static final String DRIVER = "com.mysql.jdbc.Driver";
private static String userName = "mysql"; //连接数据库用户名
private static String password = "test";//连接数据库的密码
private static String connectionUrl = "jdbc:mysql://127.0.0.1:3306/test";
public static void usePreparedStatement(String sql) throws Exception {
Class.forName(JdbcTest.DRIVER);
//数据库的连接
Connection connection = DriverManager.getConnection(connectionUrl, userName, password);
//预编译的Statement 向数据库发送sql语句,数据库那边进行编译,数据那边将编译结果存放在缓存
PreparedStatement preparedStatement = connection.prepareStatement(sql);
long start = System.currentTimeMillis();
preparedStatement.setObject(1, 10);
ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet == null) {
return;
}
while (resultSet.next()) {
System.out.println(resultSet.getString(1));
}
preparedStatement.close();
resultSet.close();
connection.close();
}
public static void main(String args[]) throws Exception {
//问号表示占位符
String sql = "select * from Users limit ?";
usePreparedStatement(sql);
}
}
运行程序,观察执行日志(发现没有预编译):
2017-11-29T14:10:47.695703Z 35 Query SET NAMES latin1
2017-11-29T14:10:47.696114Z 35 Query SET character_set_results = NULL
2017-11-29T14:10:47.696784Z 35 Query SET autocommit=1
2017-11-29T14:10:47.831764Z 35 Query select * from Users limit 10
2017-11-29T14:10:47.836862Z 35 Quit
当我们把connectUrl 换成 “jdbc:mysql://127.0.0.1:3306/test?useServerPrepStmts=true&cachePrepStmts=true”;参数说明:useServerPrepStmts 表示是否在sever端使用预编译,cachePrepStmts表示是否在Server端缓存预编译的SQL。更多可以参考官网:JDBC URL 语法
运行程序,观察执行日志(发现有预编译Prepare):
2017-11-29T14:14:34.665887Z 36 Query SET NAMES latin1
2017-11-29T14:14:34.666278Z 36 Query SET character_set_results = NULL
2017-11-29T14:14:34.666956Z 36 Query SET autocommit=1
2017-11-29T14:14:34.696959Z 36 Prepare select * from Users limit ?
2017-11-29T14:14:34.698873Z 36 Execute select * from Users limit 10
2017-11-29T14:14:34.704005Z 36 Quit
3 总结
应该始终以PreparedStatement代替Statement,要开启预编译功能还需要在连接的URL带上特定参数。
参考文献
http://lj6684.iteye.com/blog/1520681
https://dev.mysql.com/doc/connector-j/5.1/en/connector-j-reference-configuration-properties.html
http://cs-css.iteye.com/blog/1847772