1.PreparedStatement原理
Statement主要用于执行静态SQL语句,即内容固定不变的SQL语句。Statement每执行一次都要对传入的SQL语句编译一次,效率较差。
某些情况下,SQL语句只是其中的参数有所不同,其余子句完全相同,适合使用PreparedStatement
PreparedStatement的另外一个重要好处就是预防sql注入攻击。
PreparedStatement是接口,继承自Statement接口。
使用PreparedStatement时,SQL语句已提前编译,三种常用方法 execute、 executeQuery 和 executeUpdate 已被更改,以使之不再需要参数。
PreparedStatement 实例包含已事先编译的 SQL 语句,SQL 语句可有一个或多个 参数,参数的值在 SQL 语句创建时未被指定。该语句为每个 参数保留一个问号(“?”)作为占位符。
每个问号的值必须在该语句执行之前,通过适当的setInt或者setString 等方法提供。
由于 PreparedStatement 对象已预编译过,所以其执行速度要快于 Statement 对象。因此,多次执行的 SQL 语句经常创建为 PreparedStatement 对象,以提高效率。
通常批量处理时使用PreparedStatement
//SQL语句已发送给数据库,并编译好为执行作好准备
PreparedStatement pstmt = con.prepareStatement(
“UPDATE student SET phone= ? WHERE id= ?”);
//对占位符进行初始化
pstmt.setLong(1, “13433300000”);
pstmt.setInt(2,1001);
pstmt.executeUpdate();//执行SQL语句
2.通过PreparedStatement提升性能
一个sql语句执行过程中,将经历这么几个步骤:
1)、传输SQL给数据库
2)、数据库验证并解析SQL
3)、计算执行计划(Access Plan)。数据库会制定出最优的访问计划。
4)、根据访问计划进行检索,返回数据。
在上面步骤中,第3步是非常耗时的。因此,为了提高性能,数据库会缓存执行语句以及其执行计划。这被称为statement cache。在statement cache中,sql语句本身为key,执行计划为value。当相同的sql语句被发送过来时,数据库会使用缓存中的执行计划以节省cpu时间。
举例:
SELECT a, b FROM x WHERE c = 1;
再次向数据库发送相同的statement时,数据库会对先前使用过的执行计划进行重用,降低开销。
但是,如下两条语句被视作不同的SQL语句,执行计划不可重用:
SELECT a, b FROM x WHERE c = 1;
SELECT a, b FROM x WHERE c = 2;
这就是为什么要使用PreparedStatement:
//视作相同SQL语句,执行计划可重用
String sql =“select a,b from x where c = ?”;
PreparedStatement ps = conn.prepareStatement(sql);
for (int i = 0; i < 50; i++) {
ps.setInt(1, i);
ResultSet rs = ps.executeQuery();
//处理rs,省略
rs.close();
}
ps.close();
3.SQL Injection
SQL Injection就是从一个数据库获得未经授权的访问和直接检索。
SQL注入攻击就其本质而言,它利用的工具是SQL的语法,针对的是应用程序开发者编程过程中的漏洞,当攻击者能够操作数据,往应用程序中插入一些SQL语句时,SQL注入攻击就发生了。
实际上,SQL注入是存在于常见的多连接的应用程序中一种漏洞,攻击者通过在应用程序中预先定义好的查询语句结尾加上额外的SQL语句元素,欺骗数据库服务器执行非授权的任意查询。
假设有如下SQL语句被发送到数据库中:
String sql = “select * from t where username = '” + name + “’ and password = '” + pwd + “’”;
输入用户名和密码参数后,数据库接受到的完整sql语句将是这种形式:
select * from t where username = ‘oracle’ and password = ‘123456’;
但是如果用户输入的pwd参数是:’ or ‘1’='1, 则数据库收到的SQL语句将是:
select * from t where username = ‘oracle’ and password = ‘’ or ‘1’='1’;
此SQL语句的where条件将永远为true。即用户不需要输入正确的帐号密码,也能登录。
这种现象就是SQL注入(SQL Injection)。
4.通过PreparedStatement防止SQL Injection
对JDBC而言,SQL注入攻击只对Statement有效,对PreparedStatement无效,因为PreparedStatement不允许在插入参数时改变SQL语句的逻辑结构。这也是PreparedStatement的重要作用。
使用预编译的语句对象时,用户传入的任何数据不会和原SQL语句发生匹配关系,无需对输入的数据做过滤。
如果用户将’ or ‘1’='1传入赋值给占位符,下述SQL语句将无法执行:
select * from t where username = ? and password = ?;
PreparedStatement和Statement对比源码:
package cn.lyc.dao;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import cn.lyc.util.DBUtils;
/**
- sql依赖注入(缺陷)
- @author JLB
*/
public class Prepare_Statement {
/**
* preparedStatement防止依赖注入
* @param userName
* @param password
* @return
*/
public boolean login_PreparedStatemen(String userName, String password){
boolean flag = false;
Connection cn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
cn = DBUtils.getConnection();
ps = cn.prepareStatement("select * from user_1 where username=? and password=?");
ps.setString(1, userName);
ps.setString(2, password);
rs = ps.executeQuery();
if(rs.next()){
flag = true;
}else{
flag = false;
}
} catch (SQLException e) {
e.printStackTrace();
}finally{
DBUtils.close(cn, ps, rs);
}
return flag;
}
/**
* Statement出现依赖注入
* @param userName
* @param password
* @return
*/
public boolean login_Statement(String userName, String password){
boolean flag = false;
Connection cn = null;
Statement st = null;
ResultSet rs = null;
try {
cn = DBUtils.getConnection();
st = cn.createStatement();
rs = st.executeQuery("select * from user_1 where username='"+userName+"' and password='"+password+"'");
if(rs.next()){
flag = true;
}else{
flag = false;
}
} catch (SQLException e) {
e.printStackTrace();
}finally{
DBUtils.close(cn, st, rs);
}
return flag;
}
public static void main(String[] args) {
Prepare_Statement dao = new Prepare_Statement();
String userName = "java";
String password = "1' or 'a'='a";
boolean b = dao.login_PreparedStatemen(userName, password);//不会出现sql注入 false
boolean c = dao.login_Statement(userName, password);//出现sql注入 true (数据库里的用户名和密码分别为:java,1234,正确的结果应该返回false)
System.out.println(b+" this is not "+c);
}
}
运行结果:
注意:要用到数据库的数据自己添加一个简单表就可以了
DBUtils工具类在前面的jdbc博客中有哦!