PreparedStatement
Statement接口的子接口
强大之处
防SQL攻击
提高代码的可读性、可维护性
提高效率
学习PreoaredStatement的用法
如何得到PreparedStatement对象
给出SQL模版
调用Connection的PreparedStatement preparedStatement(String sql 模版)
调用pstmt的setXxx()系列方法sql模版中的?赋值
调用pstmt的executeUpdate()或executeQuery(),但它的方法都没有参数
预处理的原理
服务器的工作
校验sql语句的语法
编译:一个与函数相似的东西
执行:调用函数
PreparedStatement(预编译)
前提:连接的数据库必须支持预处理。几乎没有不支持的
每个pstmt都与一个sql模版绑在一起,先把sql模版给数据库,数据库先进行校验,再进行编译。执行时只是把参数传递过去而已
若二次执行时,就不用再次校验语法,也不用再次编译。直接执行
MySQL的预编译功能
预编译的好处
当客户发送一条SQL语句给服务器后,服务器总是需要校验SQL语句的语法格式是否正确,然后把SQL语句编译成可执行的函数,最后才是执行SQL语句。
其中校验语法,和编译所花的事件可能比执行SQL语句花的时间还要多
如果我们需要执行多次insert语句,但知识每次插入的值不同,MySQL服务器也是需要每次去校验SQL语句的语法格式,以及编译,就浪费了太多的事件,如果使用预编译功能,那么只对SQL语句进行一次语法校验和编译,所以效率要高
MySQL执行预编译
MySQL执行预编译分为三步
执行预编译语句:例如:prepare myfun from 'select * from t_book where bid=?'
设置变量 例如 set @str='b1'
执行语句例如 execute myfun using @str
如果需要再次执行myfun那么就不再需要第一步,即不需要再编译语句了
设置变量 例如 set @str='b2'
执行语句 例如 execute myfun using@str
通过查看MySQL日志可以看到执行的过程
使用Statement执行预编译
使用Statement执行预编译就是把上面的SQL语句执行一次
userServerPrepstmts参数
默认使用PreparedStatement是不能执行预编译的,这需要在url中给出useServerPrepStmts=true 参数(MySQL Server 4.1之前的版本是不支持预编译的,二Connector/J在5.0.5以后的版本,默认是没有开启预编译功能的)
例如:jdbc:mysql://localhost:3306/db1?useServerPrepStmts=true
这样才能保证mysql驱动会先把SQL语句发送给服务器进行预编译,然后在执行executeQuery()时知识把参数发送给服务器
cachePrepStmts参数
当使用不同的PreparedStatement对象来执行相同的SQL语句时,还是会出现编译两次的现象,这是因为驱动没有缓存编译后的函数key,导致二次编译。如果希望缓存编译后函数的key,那么就要设置cachePrepStmts参数为true
jdbc:mysql://localhost:3306/db1?useServertPrepStmts=true&cachePrepStmts=true
打开批处理
MySQL的批处理也需要通过参数来打开 rewriteBatchedStatements=true
1、什么是SQL攻击
在需要用户输入的地方,用户输入的是SQL语句的片段,最终用户输入的SQL片段与我们DAO中写的SQL语句合成一个完整的SQL语句。例如用户在登陆时输入的用户名和密码都是为SQL语句的片段
2演示SQL攻击
首先需要创建一张用户表,用来存储用户的信息
/**
*PreparedStatement
*/
public class Demo3 {
/**
* 登录
* 使用username和password去查询数据
* 若查询出结果集,说明正确!返回true
* 若查不出结果,说明用户名或密码错误,返回false
* @throws Exception
*/
public boolean login(String username,String password) throws Exception{
/*
* 一、得到Connection
* 二、得到Statement
* 三、得到ResultSet
* 四、rs.next()返回的是什么,我们就返回什么
*/
//准备四大参数
String driverClassName = "com.mysql.jdbc.Driver";
String url = "jdbc:mysql://localhost:3306/db1";
String mysqlUsername = "root";
String mysqlPassword = "123";
//加载驱动类
Class.forName(driverClassName);
//得到Connection
Connection con = DriverManager.getConnection(url,mysqlUsername,mysqlPassword);
//得到Statement
Statement stmt = con.createStatement();
//给出sql语句,调用stmt的executeQuery(),得到ResultSet
String sql = "select * from t_user where username ='"+username+"'and password='"+password+"'";
System.out.println(sql);
ResultSet rs = stmt.executeQuery(sql);
return rs.next();
}
/**
* SQL攻击
* @throws Exception
*/
@Test
public void fun1() throws Exception{
//select * from t_user where username ='a' or 'a'='a'and password='a' or 'a'='a'
String username = "a' or 'a'='a";
String password = "a' or 'a'='a";
boolean bool =login(username,password);
System.out.println(bool);
}
public boolean login2(String username,String password) throws Exception{
/*
* 一、得到Connection
* 二、得到Statement
* 三、得到ResultSet
* 四、rs.next()返回的是什么,我们就返回什么
*/
//准备四大参数
String driverClassName = "com.mysql.jdbc.Driver";
String url = "jdbc:mysql://localhost:3306/db1";
String mysqlUsername = "root";
String mysqlPassword = "123";
//加载驱动类
Class.forName(driverClassName);
//得到Connection
Connection con = DriverManager.getConnection(url,mysqlUsername,mysqlPassword);
/*
* 得到PreparedStatement
* 1、给出sql模版:所有的参数使用?来替代
* 2、调用Connection方法,得到PreparedStatement
*/
String sql ="select * from t_user where username=? and password=?";
PreparedStatement pstmt = con.prepareStatement(sql);
/*
* 二、为参数赋值
* 多了少了都不行会报异常
*/
pstmt.setString(1, username);//给第一个问号赋值,值为username
pstmt.setString(2, password);//给第二个问号赋值,值为password
ResultSet rs = pstmt.executeQuery();//调用查询方法,向数据库发送查询语句
return rs.next();
}
@Test
public void fun2() throws Exception{
//select * from t_user where username ='a' or 'a'='a'and password='a' or 'a'='a'
String username = "a' or 'a'='a";
String password = "a' or 'a'='a";
boolean bool =login2(username,password);
System.out.println(bool);
}
/**
*测试JdbcUtils.getConnection()
* @throws SQLException
* @throws IOException
* @throws ClassNotFoundException
*/
@Test
public void fun3() throws ClassNotFoundException, IOException, SQLException{
Connection con = JdbcUtils.getConnections();
System.out.println(con);
Connection con1 = JdbcUtils.getConnections();
System.out.println(con1);
}
}
public class JdbcUtils {
public static Connection getConnections() throws IOException, ClassNotFoundException, SQLException{
/*
* 1.加载配置文件
* 2、加载驱动类
* 3、调用DriverManager.getConnection()
*/
//加载配置文件
InputStream in = JdbcUtils.class.getClassLoader()
.getResourceAsStream("dbconfig.properties");
Properties props = new Properties();
props.load(in);
//加载驱动类
Class.forName(props.getProperty("driverClassName"));
//得到Connection
return DriverManager.getConnection(props.getProperty("url"),
props.getProperty("username"),
props.getProperty("password"));
}
}
dbconfig.properties
/**
* v1.0
* @author Administrator
*
*/
public class JdbcUtils {
private static Properties props = null;
//只在JdbcUtils类被加载一次
static{
//props进行初始化,即加载dbconfig.properties文件到props对象中
try {
/*
* 1.加载配置文件
* 2、加载驱动类
* 3、调用DriverManager.getConnection()
*/
//加载配置文件
InputStream in = JdbcUtils.class.getClassLoader()
.getResourceAsStream("dbconfig.properties");
props = new Properties();
props.load(in);
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
//加载驱动类
Class.forName(props.getProperty("driverClassName"));
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
//获取连接
public static Connection getConnections() throws SQLException{
//得到Connection
return DriverManager.getConnection(props.getProperty("url"),
props.getProperty("username"),
props.getProperty("password"));
}
}