尽管自己写代码用了preparedstatement, 但是对于它和statement 之间的区别不是非常了解。 正好今天有时间学习实践一下, 尤其是sql 注入。
首先比较一下statement 和preparedstatement 的调用方式, 后者的调用代码量稍多一些 -网上找的
Statement:
stmt.executeUpdate("insert into tb_name (col1,col2,col2,col4) values ('"+var1+"','"+var2+"',"+var3+",'"+var4+"')");
preparedStatement:
perstmt = con.prepareStatement("insert into tb_name (col1,col2,col2,col4) values (?,?,?,?)"); perstmt.setString(1,var1); perstmt.setString(2,var2); perstmt.setString(3,var3); perstmt.setString(4,var4); perstmt.executeUpdate();
其实就是预编译的问题。 PreparedSatement 会预编译, 而Statement基本上无法重用。 所以多次执行的话前者效率高很多。 生产环境中遇到过这种情况, 有几万条sql, 数据库的工作效率严重下降, 后来通过替换成PreparedStatement解决了这个问题
每一种数据库都会尽最大努力对预编译语句提供最大的性能优化.因为预编译语句有可能被重复调用.所以语句在被DB的编译器编译后的执行代码被缓存下来,那 么下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中(相当于一个涵数)就会得到执行.这并不是说只有一个 Connection中多次执行的预编译语句被缓存,而是对于整个DB中,只要预编译的语句语法和缓存中匹配.那么在任何时候就可以不需要再次编译而可以 直接执行.
而statement的语句中,即使是相同一操作,而由于每次操作的数据不同所以使整个语句相匹配的机会极小,几乎不太可能匹配.比如:
insert into tb_name (col1,col2) values ('11','22'); insert into tb_name (col1,col2) values ('11','23');
即使是相同操作但因为数据内容不一样,所以整个个语句本身不能匹配,没有缓存语句的意义.事实是没有数据库会对普通语句编译后的执行代码缓存.
当然并不是所以预编译语句都一定会被缓存,数据库本身会用一种策略,比如使用频度等因素来决定什么时候不再缓存已有的预编译结果.以保存有更多的空间存储新的预编译语句.
开始没有明白这点, 后来从stackflow 查到一个例子,就很容易理解了。
Statement stmt = conn.createStatement("INSERT INTO students VALUES('" + user + "')"); stmt.execute();
Or
Statement stmt = conn.prepareStatement("INSERT INTO student VALUES(?)"); stmt.setString(1, user); stmt.execute();
If "user" came from user input and the user input was
Robert'); DROP TABLE students; --
然后自己写了代码实践了一下, 发现确实如果statement 会把 table drop掉。 而prepared的呢因为已经预编译了, 根本就不会执行这个drop语句。 所以是安全的。
如何使用以及如何测试依赖注入, 这里是本地 postgres 数据库, 用户名密码postgres postgres
import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class PreparedStmtExample { public static void main(String args[]) throws SQLException { System.out.println("-------- PostgreSQL " + "JDBC Connection Testing ------------"); try { Class.forName("org.postgresql.Driver"); } catch (ClassNotFoundException e) { System.out.println("Where is your PostgreSQL JDBC Driver? " + "Include in your library path!"); e.printStackTrace(); return; } System.out.println("PostgreSQL JDBC Driver Registered!"); // statement Connection conn = DriverManager.getConnection( "jdbc:postgresql://127.0.0.1:5432/postgres", "postgres", "postgres"); // String name = "'name'"; String name1 = "'name'; drop table table1;"; Statement stmt = conn.createStatement(); // ResultSet result = stmt // .executeQuery("select * from network where id=" // + name1); ResultSet result = stmt .executeQuery("select * from network where id=" + name1); while (result.next()) { System.out.println("Loan Type: " + result.getString("sid")); } // preparedstatement Connection conn1 = DriverManager.getConnection("jdbc:postgresql://127.0.0.1:5432/postgres", "postgres", "postgres"); PreparedStatement preStatement = conn1 .prepareStatement("select * from network where id=?"); preStatement.setString(1, "name; drop table table1;"); ResultSet result1 = preStatement.executeQuery(); while (result1.next()) { System.out.println("Loan Type: " + result1.getString("sid")); } } }
要预先create 一个table :network, column id,sid; 加一个record name;xxx 即可。
create 第二个table: table1
这次主要理解了如何使用,以及如何防止sql注入。 以后一般就直接用preparedStatement 即可。
Next
1) 关于sql 执行过程原理是否可以理解一下