jdbc中PreparedStatement和Statement 爱被打了一巴掌 2022-03-28 05:41 260阅读 0赞 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); } \} 运行结果: ![结果][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyOTAyNDcw_size_16_color_FFFFFF_t_70] 注意:要用到数据库的数据自己添加一个简单表就可以了 DBUtils工具类在前面的jdbc博客中有哦! [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyOTAyNDcw_size_16_color_FFFFFF_t_70]: /images/20220328/d9a6c96c2d1841b08acd7705d73998c5.png
还没有评论,来说两句吧...