jdbc中PreparedStatement和Statement

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博客中有哦!

猜你喜欢

转载自blog.csdn.net/qq_42902470/article/details/86167181