Statement:使用字符串拼接的方式
案例:用户登陆
需求:
1)、有一张用户表
2)、添加几条用户记录
id int primary key auto_increment, name varchar(20), password varchar(20) ) insert into user values (null,'jack','123'),(null,'rose','456'); -- 登录, SQL 中大小写不敏感 select * from user where name='JACK' and password='123'; -- 登录失败 select * from user where name='JACK' and password='333';
3) 、使用 Statement 字符串拼接的方式实现用户的登录, 用户在控制台上输入用户名和密码。
步骤:
1)、得到用户从控制台上输入的用户名和密码来查询数据库
2)、写一个登录的方法
a)、通过工具类得到连接
b)、创建语句对象,使用拼接字符串的方式生成 SQL 语句
c)、查询数据库,如果有记录则表示登录成功,否则登录失败
d)、释放资源
public class Demo7Login { //从控制台上输入的用户名和密码 public static void main(String[] args) { Scanner sc = new Scanner(System.in); System.out.println("请输入用户名:"); String name = sc.nextLine(); System.out.println("请输入密码:"); String password = sc.nextLine(); login(name, password); } /** * 登录的方法 */ public static void login(String name, String password) { //a) 通过工具类得到连接 Connection connection = null; Statement statement = null; ResultSet rs = null; try { connection = JdbcUtils.getConnection(); //b) 创建语句对象,使用拼接字符串的方式生成 SQL 语句 statement = connection.createStatement(); //c) 查询数据库,如果有记录则表示登录成功,否则登录失败 String sql = "select * from user where name='" + name + "' and password='" + password + "'"; System.out.println(sql); rs = statement.executeQuery(sql); if (rs.next()) { System.out.println("登录成功,欢迎您:" + name); } else { System.out.println("登录失败"); } } catch (SQLException e) { e.printStackTrace(); } finally { //d) 释放资源 JdbcUtils.close(connection, statement, rs); } } }
SQL 注入问题
请输入用户名: newboy 请输入密码: a' or '1'='1 select * from user where name='newboy' and password='a' or '1'='1' 登录成功,欢迎您:newboy
select * from user where name='newboy' and password='a' or '1'='1' name='newboy' and password='a' 为假 '1'='1' 真 相当于 select * from user where true; 查询了所有用户记录,造成重要数据的泄漏
我们让用户输入的密码和 SQL 语句进行字符串拼接。用户输入的内容作为了 SQL 语句语法的一部分,改变了原有 SQL 真正的意义,以上问题称为 SQL 注入。要解决 SQL 注入就不能让用户输入的密码和我们的 SQL 语句进行简单的字符串拼接。
PreparedStatement 将 SQL 语句发送给数据库预编译
1)、因为有预先编译的功能,提高 SQL 的执行效率。
2)、可以有效的防止 SQL 注入的问题,安全性更高。
PreparedSatement 的好处
1、prepareStatement()会先将 SQL 语句发送给数据库预编译。PreparedStatement 会引用着预编译后的结果。可以多次传入不同的参数给 PreparedStatement 对象并执行。减少 SQL 编译次数,提高效率。
2、安全性更高,没有 SQL 注入的隐患。
3、提高了程序的可读性
使用 PreparedStatement 的步骤:
1)、编写 SQL 语句,未知内容使用?占位:"SELECT * FROM user WHERE name=? AND password=?";
2)、获得 PreparedStatement 对象
3)、设置实际参数:setXxx(占位符的位置, 真实的值)
4)、执行参数化 SQL 语句
5)、关闭资源
使用 PreparedStatement 改写上面的登录程序,看有没有 SQL 注入的情况
/** * 使用 PreparedStatement */ public class Demo8Login { //从控制台上输入的用户名和密码 public static void main(String[] args) throws SQLException { Scanner sc = new Scanner(System.in); System.out.println("请输入用户名:"); String name = sc.nextLine(); System.out.println("请输入密码:"); String password = sc.nextLine(); login(name, password); } /** * 登录的方法 * @param name * @param password */ private static void login(String name, String password) throws SQLException { Connection connection = JdbcUtils.getConnection(); //写成登录 SQL 语句,没有单引号 String sql = "select * from user where name=? and password=?"; //得到语句对象 PreparedStatement ps = connection.prepareStatement(sql); //设置参数 ps.setString(1, name); ps.setString(2,password); ResultSet resultSet = ps.executeQuery(); if (resultSet.next()) { System.out.println("登录成功:" + name); } else { System.out.println("登录失败"); } //释放资源,子接口直接给父接口 JdbcUtils.close(connection,ps,resultSet); }
Mybatis使用OGNL表达式解析对象字段的值,#{}或者${}括号中的值为pojo属性名称
#{}与${}的区别:
${} 的作用实际上是字符串拼接,所以要特别小心sql注入问题。
#{}用到了prepareStement,它将 SQL 语句发送给数据库预编译,安全性更高,没有 SQL 注入的隐患,减少 SQL 编译次数,提高效率。
传入普通参数时使用#{},当传入表名时,需要使用${}
#{}
#{}
将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号。
如:WHERE SID = #{sid},如果传入的值是s01,那么解析成SQL时的值为WHERE SID = "s01"。#{}
可以防止防止sql注入。
${}
${}
将传入的数据直接显示生成在sql中。
如:ORDER BY ${sage},如果传入的值是age,那么解析成SQL时的值为ORDER BY age。${}
方式一般用于传入数据库对象,例如传入表名,字段名。
但是在这块还有一个问题就是,通过Dao层传入参数时,不能使用Map集合的方式进行赋值。如下面的SQL中,需要传入两个参数
SELECT COUNT(*) FROM ${tableId} WHERE S_NO = #{sNo}
Dao层的写法参数就不能是Map集合或者两个String参数,这时需要@Param
注解的方法来声明参数。
@Param注解的作用是声明参数时,如果使用 #{} 或 ${} 的方式都可以。不使用@Param注解来声明参数时,必须使用使用 #{}方式,如果使用${} 的方式,会报错。
所以Dao层的写法应该如下:
public int getCnt(@Param("tableId") String tableId,@Param("sNo") String sNo) throws Exception;