这几天在学习JDBC,一开始用的是Statement,后来一位老师给我们讲课时用的是PreparedStatement,一开始只是觉得PreparedStatement方便了很多,不用反复拼接SQL语句,只需要用?占位就可以了。无聊时百度了一下这两个的区别,才发现PreparedStatement要比Statement强大很多,而且在程序中要使用PreparedStatement。
先来说说为什么要使用PreparedStatement,一个很大的原因就是PreparedStatement可以防止SQL注入攻击。
通过代码来说说什么是SQL注入攻击
连接数据库什么的代码就不贴了,这里写了一个方法通过传入的用户名和密码来查询用户的所有信息,用的是Statement
public static void find(String user,String password) throws SQLException {
try {
statement = getConn().createStatement();
rs = statement
.executeQuery("select * from test where name = '"+user+"'and password='"+password+"'");
while (rs.next()){
System.out.print(rs.getInt(1)
+", "+rs.getString(2)
+", "+rs.getString(3));
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
closeResource(connection,statement,rs);
}
}
表中数据,表名为test
在主方法里调用一下这个find方法,传入用户名和密码
public static void main(String[] args) throws SQLException {
find("A","123");
}
查询结果
没什么问题,现在重点来了,当我传入的是一些其他的东西呢?
find("A","' or 1 = '1");
当我给密码传入' or 1 = '1,查询结果为
可以看到所有的数据都查询出来了,这是因为传入的这个密码使SQL语句变成了
执行where 1 = ‘1’ 能查询到表中所有数据,相当于执行了select * from test,这就是一种SQL注入攻击,只要知道是什么原理,理解起来就很简单,所以我们也可以给用户名传入'or 1 = 1 #,也可以查询出所有数据,因为在MySql中#后面的是注释
程序根本不会执行# 后面的语句,所以这句话还是执行的是select * from test。
甚至我们可以传入一个';drop table table_name;这样其他人知道名字的表就能被删除。
现在我们不使用Statement,改用PreparedStatement
修改一下代码
public static void find(String user,String password) throws SQLException {
try {
preparedStatementstatement = getConn()
.prepareStatement("select * from test where name =? and password = ?");
preparedStatementstatement.setObject(1,user);
preparedStatementstatement.setObject(2,password);
rs = preparedStatementstatement.executeQuery();
while (rs.next()){
System.out.print(rs.getInt(1)
+", "+rs.getString(2)
+", "+rs.getString(3));
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
closeResource(connection,preparedStatementstatement,rs);
}
}
当我们传入' or 1 = '1或者'or 1 = 1 #时会发现什么也查询不到了,原因在于PreparedStatement表示预编译的 SQL 语句的对象,SQL 语句被预编译并存储在 PreparedStatement 对象中。然后可以使用此对象多次高效地执行该语句。PreparedStatement使用了?作为占位符,通过预编译,SQL语句的结构就已经存储好了,无法通过传入的参数来改变这条SQL语句的结构,说白了就是像上面的SQL语句,PreparedStatement对象已经知道这条SQL语句有一个where条件并且需要一个name值和一个password值,这个结构就已经固定了,所以我们无法通过传入的参数来改变SQL语句结构。
不仅如此,使用PreparedStatement要比使用Statement的效率高,同样的原因PreparedStatement是预编译的,即使执行1000次查询,只需要改变占位符的值就可以了,不会再编译整条SQL语句,而使用Statement,每执行一条SQL语句都会编译,这样效率明显不如PreparedStatement。