在学习java的jdbc时学到这么一个东西,我们有时候会忽略这个问题,我们摘出来做一下简单的讨论。
Sql注入问题 及statement和PreparedStatement的区别:
首先我们来复习一下jdbc *** 作数据库的几个步骤:
注册驱动、获取连接、获取数据库 *** 作对象,执行sql语句、处理查询结果集、释放资源
那么之前我们一直用的是prestatement来获取数据库 *** 作对象,那么大家知道prestatement其实是属于预编译的数据库 *** 作对象。那么prestatement是不是全能的呢?并不是,比如京东上的商品价格展示,就要求升序或者降序排列,那么必须拼接asc和desc时,prestatement是否依然有效呢?
我们简单模拟一个升序降序排列功能
示例一:
package jdbc; import java.sql.*; import java.util.Scanner; public class JdbcTest07sql1 { public static void main(String[] args) { //用户控制台输入desc就是降序,输入asc就是升序 Scanner s = new Scanner(System.in); System.out.println("请输入desc或asc,desc表示降序,asc表示升序"); System.out.println("请输入:"); String keyWords = s.nextLine(); //执行SQL Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { //注册驱动 Class.forName("com.mysql.cj.jdbc.Driver"); //获取连接 conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/orcl","root","123456"); //获取数据库 *** 作对象 String sql = "select ename from emp order by empno ? "; ps = conn.prepareStatement(sql); ps.setString(1,keyWords); //执行sql rs=ps.executeQuery(); //遍历结果集 while (rs.next()){ System.out.println(rs.getString("ename")); } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException throwables) { throwables.printStackTrace(); }finally { if(rs!=null){ try { rs.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } }if(ps!=null){ try { ps.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } if (conn!=null){ try { conn.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } } } }
此程序会报这个错误:
java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ‘‘desc’’ at line 1
可以看到是由于sql语句给占位符传值时多加了一个引号。
那么此时必须用到statement,来拼接字符串,此问题就得到了解决。只展示和前面代码不同的地方
try { //注册驱动 Class.forName("com.mysql.cj.jdbc.Driver"); //获取连接 conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/orcl","root","123456"); //获取数据库 *** 作对象 stmt = conn.createStatement(); //执行SQL String sql = "select ename from emp order by empno "+keyWords; rs = stmt.executeQuery(sql); //遍历结果集 while (rs.next()){ System.out.println(rs.getString("ename")); }
那么使用statement就一定好吗?并不是,在进行用户登录注册时又会出现怎样的问题呢?
在这里我们来模拟一个用户登陆注册功能
package jdbc; import java.sql.*; import java.util.HashMap; import java.util.Map; import java.util.Scanner; public class JdbcTest06sql1 { public static void main(String[] args) { //初始化一个界面 MapuserLoginInfo = initUI(); //验证用户名和密码 boolean loginSuccess = login(userLoginInfo); //最后输出结果 System.out.println(loginSuccess ? "登陆成功":"登陆失败"); } private static Map initUI() { Scanner s = new Scanner(System.in); System.out.println("用户名:"); String loginName = s.nextLine(); System.out.println("密码:"); String loginPwd = s.nextLine(); Map userLoginInfo = new HashMap<>(); userLoginInfo.put("loginName",loginName); userLoginInfo.put("loginPwd",loginPwd); return userLoginInfo; } private static boolean login(Map userLoginInfo) { //打标记 boolean loginSuccess = false; //单独定义变量 String loginName = userLoginInfo.get("loginName"); String loginPwd = userLoginInfo.get("loginPwd"); //JBDC代码 Connection conn = null; Statement stmt = null;//这里使用PreparedStatement预编译的数据库 *** 作对象 ResultSet rs = null; try { //1.注册驱动 Class.forName("com.mysql.cj.jdbc.Driver"); //2.获取连接 conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/orcl","root","123456"); //3.获取的数据库 *** 作对象 stmt = conn.createStatement(); //4.执行sql String sql = "select * from t_user where loginName = '"+loginName+"' and loginPwd ='"+loginPwd+"'"; rs = stmt.executeQuery(sql); //5.处理结果集 if(rs.next()){ loginSuccess = true; } } catch (Exception e) { e.printStackTrace(); } finally { //6.释放资源 if (rs != null){ try { rs.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } if (stmt != null){ try { stmt.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } if (conn != null){ try { conn.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } } return loginSuccess; } }
比如我们这样来访问数据库中的数据
用户名:zhangsan
密码:zhangsan’ or ‘1’=’1
密码不正确但依然访问到了数据库的内容
那么这种现象就被称作是sql注入现象。
这种现象被称为sql注入(安全隐患)(黑客经常使用)
sql注入的根本原因:用户输入的信息中含有sql语句的关键字,并且这些关键字参与sql语句的编译过程,导致sql语句的原意被扭曲,进而达到sql注入。
执行到rs = stmt.executeQuery(sql);时,会把sql语句发送给DBMS(数据库管理系统),会把sql语句进行编译,正好将用户提供的非法信息编译进去,导致了原sql语句含义被扭曲了
我们来看一下sql注入的定义:打开ppt
那么我们怎么解决sql注入问题呢?同样也只展示和前面代码不同的部分
//1.注册驱动 Class.forName("com.mysql.cj.jdbc.Driver"); //2.获取连接 conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/orcl","root","123456"); //3.获取预编译的数据库 *** 作对象 //SQL语句的框子。其中一个?,表示一个占位符,一个?将来接收一个”值“,注意:占位符不能使用单引号括起来。 String sql = "select * from t_user where loginName = ? and loginPwd = ?";//?称为占位符 //程序执行到此处,会发送sql语句框子给DBMS,然后DBMS进行sql语句的预先编译。 ps = conn.prepareStatement(sql); //给占位符?传值(第一个问号下标为1,第二个问号下标为2,JDBC中所有下标从1开始。) ps.setString(1,loginName); ps.setString(2,loginPwd); //4.执行sql rs = ps.executeQuery(); //5.处理结果集 if(rs.next()){ loginSuccess = true; }
只要用户提供的信息不参与SQL语句的编译过程,问题就解决了。即使用户提供的信息中含有SQL语句的关键字,但是没有参与编译,不起作用。要想用户信息不参与SQL语句的编译,那么必须使用java.sql.PreparedStatement。现在来看一下PreparedStatement是什么?
PreparedStatement继承了java.sql.Statement,PreparedStatement属于预编译的数据库 *** 作对象,PreparedStatement的原理是:预先对sql语句的框架进行编译,然后再给sql语句传"值";
写到这里,我们顺便来对比一下statement及prestatement的区别:
1.Statement存在sql注入问题,PreparedStatement解决了sql注入问题
2.Statement编译一次执行一次,PreparedStatement编译一次,可执行N次。PreparedStatement效率较高一些
3.PreparedStatement会在编译阶段做类型的安全检查。
综上所述PreparedStatement使用较多,只有极少数情况下需要使用Statement。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)