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 语句发送给数据库预编译

PreparedStatement 是 Statement 接口的子接口,继承于父接口中所有的方法。它是一个预编译的 SQL 语句
 
PreparedSatement 的执行原理

1)、因为有预先编译的功能,提高 SQL 的执行效率。

2)、可以有效的防止 SQL 注入的问题,安全性更高。

Connection 创建 PreparedStatement 对象

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;

 

 

 

 

 

 

 

 

 

 
 
 
posted on 2021-04-20 11:06  周文豪  阅读(358)  评论(0编辑  收藏  举报