背景
学习与反思
异同
- PreparedStatement和Statement都是JDBC中执行sql的对象,他们都可以执行sql语句,但是后者会给一些人创建出sql注入的条件,而前者不会。
- prepareStatement :不存在sql注入;效率略高,编译一次,执行多次;在传值后编译时存在类型安全检查。(原理:当历史(预)编译sql语句完全一样时,sql跳过编译,直接执行)
- statement :存在sql注入问题;编译一次,执行一次
本质
- 第一步:假如这里使用Statement
String sql2 = ("select * from t_user where LoginName = '"+loginName+"' and LoginPasswd = '"+loginPasswd+"' ");
resultSet = statement.executeQuery(sql2);
Statement采用的是直接拼接的方式,假如说这里的loginName传入的值是aaa,loginPasswd传入的是aaa’ or ‘1’='1,那么sql就变成了
select * from t_user where LoginName ='aaa' and LoginPasswd ='aaa' or '1'='1'
紧接着就被sql执行对象statement带着sql进入数据库进行编译,查询,这样就变成了恒成立,所有的数据都可以查出来,这也就造成了sql注入;所以使用Statement会造成sql注入的本质是:非法sql参数值进入到DBMS(数据库管理系统)参与了sql语句的编译过程,sql参数值中含有的关键字将‘非法字符’拼接到DBMS的编译,改变了sql原来的意思。
- 第二步:假如这里使用PreparedStatement
// 创建sql,获取预编译的数据库操作对象
//sql语句的框架,一个?代表一个占位符,一个?将来接受一个值,
//占位符不能够用单引号宝齐莱,不然会被人认为是个普通的参数值
String sql = ("select * from t_user where LoginName = ? and LoginPasswd = ? ");
//此处sql已经预编译了一次,在进去DBMS执行的时候已经不会再编译了
prepareStatement = connection.prepareStatement(sql);
// 开始对占位符‘ ?’进行赋值,占位符会根据你选择的类型和填充的数据进行赋值,是setString(),就自动给参数加 '',是setInt()就是数字类型,不会加'',
//这样一来假如说 传入的是 aaa' or '1'='1 那么就会被当做字符串,一个整体 到sql语句中就是' aaa' or '1'='1 ',是一个整体的值,而不是分开的部分
prepareStatement.setString(1,loginName);//第一个占位符下标1,放昵称
prepareStatement.setString(2,loginPasswd);//第二个占位符下标2,放密码
**// resultSet = prepareStatement.executeQuery(sql);//此处(sql)应无参,否则sql将再次参与DBMS的编译过程**
resultSet = prepareStatement.executeQuery();
那么在这里每一个?代表是一个占位符,代表一个值,这样一来假如说 传入的密码是 aaa’ or ‘1’='1 那么就会被当做字符串,传参必须调用setString()方法,在两边各加一个单引号,一个整体 到sql语句中就是**’ aaa’ or ‘1’='1 ',**是一个整体的值,而不是分开的部分。同理还有setInt(),setDouble()等等,最后一句很重要,这里必须是无参,如果在这里又把sql传进去,那么还是会进入数据库参与编译,导致sql注入。
resultSet = prepareStatement.executeQuery();
Statement的用处
- 动态的表
如select * from 表,这个表是动态的,表名肯定是字符串,如果使用PrepareStatement,传入A变成
select * from 'A';
这样肯定是报错的
- 动态的列
与上面是同理 - 动态的排序
传入asc,desc,也会有这样的问题
所以此时就需要Statement来帮助我们,正所谓天生我才必有用!