2023.11.28 MyBatis 中 #{} 和 ${} 的区别

目录

引言

主要区别

sql 注入问题 

使用 ${param} 的两大条件

sql 关键字

模糊查询(like) 

分析

concat 函数


阅读以下文章前建议先点击下方链接了解单元测试

单元测试详解


引言

  • 首先 #{} 和 ${} 均是 MyBatis 获取动态参数的两种实现
  • 为了让我们更加方便观察 这二者之间的区别
  • 我们可先在配置文件 application.properties 中加入相应配置项

# 配置打印 Mybatis 执行 SQL 的日志(debug 级)
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# 配置 日志打印级别 将默认 info 级 修改为 debug 级
logging.level.com.example.demo=debug

  • 下文中的 param 代表参数

主要区别

  • ${param} 是 直接替换
  • #{param} 是 预编译处理

预编译处理

  • 指 MyBatis 在处理 #{param} 时,将 #{param} 替换成了 ?号,代表占位符
  • 再使用 PreparedStatement 的 set 方法来赋值
  • 也就是说如果参数为 Integer 类型,则使用 setInt 方法
  • 如果参数为 Sting 类型,则使用 setString 方法
// ? 是一个占位符,只是占个位置,后面会被替换成其他的东西
String sql = "insert into staff values(?, ?)";
PreparedStatement statement = connection.prepareStatement(sql);
//1 2 数字表示占位符下标,默认从1开始,set方法是一个系列:setXXX(XXX表示类型)
statement.setInt(1, ID);
statement.setString(2, name);
  

实例理解一

  • 使用 ${param}
  • 利用单元测试 执行调用该 sql 语句
@SpringBootTest
class UserMapperTest {

//    正因为该测试单元运行在 Spring Boot 下
//    所以我们可以使用依赖注入 userMapper Bean 对象
    @Autowired
    private UserMapper userMapper;

    @Test
    void getUserByName() {
        User user = userMapper.getUserByName("xiaolin");
        System.out.println(user);
    }
}

执行结果:

  • 发生报错!!

2023.11.28 MyBatis 中 #{} 和 ${} 的区别_第1张图片

  • 正确的 sql 语句应该为 name = 'xiaolin' 
  • 此处直接替换成了 name = xiaolin,缺少 单引号!!
  • 所以我们在使用 ${param} 获取字符串类型的动态参数时,应该主动加上 单引号
  • 例如 name = '${param}' 
  • 但是该种写法存在 sql 注入问题

实例理解二

  • 使用 #{param}
  • 利用单元测试 执行调用该 sql 语句
@SpringBootTest
class UserMapperTest {

//    正因为该测试单元运行在 Spring Boot 下
//    所以我们可以使用依赖注入 userMapper Bean 对象
    @Autowired
    private UserMapper userMapper;

    @Test
    void getUserByName() {
        User user = userMapper.getUserByName("xiaolin");
        System.out.println(user);
    }
}

执行结果:

  • 未发生报错!

2023.11.28 MyBatis 中 #{} 和 ${} 的区别_第2张图片


注意:

  • 当传来的参数为 Integer 类型时使用 #{} 或 ${} 都行

sql 注入问题 

  • ${param} 存在 sql 注入问题
  • #{param} 不存在该问题

实例理解

  • 此处模拟实现一个验证登录功能

初始化数据库

  • 创建一个 message 数据库 和 user 表
  • 其中 user 表中数据为下图所示

2023.11.28 MyBatis 中 #{} 和 ${} 的区别_第3张图片


初始化 UserMapper 接口

  • 此处我们在接口中添加一个 login 方法
import com.example.demo.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

//添加 @Mapper 注解 代表该接口会伴随这 项目的启动而注入到容器中
@Mapper
public interface UserMapper {

//    登录查询
    User login(@Param("user_name") String name,
               @Param("password") String password);
}

初始化 UserMapper XML 文件

  • 在与 接口相对应的 XML 文件中
  • 添加上与 login 方法 相对应的 sql 语句




    


创建 login 的测试方法

  • 注意这里我们传入的 password = " ' or 1 = '1 "
  • 然而 name = xiaolin 用户的正确密码为 123456,此处传入的显然不是正确密码
@Test
    void login() {
        String username = "xiaolin";
        String password = "' or 1 = '1";
        User user = userMapper.login(username,password);
        System.out.println("登录状态:" + (user == null ? "失败" : "成功") + " user = " + user);
    }
}

执行测试方法

  • 测试方法执行成功

2023.11.28 MyBatis 中 #{} 和 ${} 的区别_第4张图片

  • 上述演示出现的问题,属于典型的 sql 注入问题

分析 sql 语句

  • 即该 sql 语句会将 user 表中的所有用户信息全部查询出来

2023.11.28 MyBatis 中 #{} 和 ${} 的区别_第5张图片


使用 ${param} 的两大条件

  • 保证 param 的值一定为可穷举值
  • 在使用之前一定要对传递的值进行合法性校验(可在 Controller 层 通过穷举的方式验证传递值的安全性)

sql 关键字

  • 当我们在 sql 语句中想实现插入 sql 关键字时
  • 此时必须使用 ${param} 来实现
  • 一般传入的关键字也时 字符串形式,如果还是采用 #{param} 这种预处理方式的话
  • #{param} 就会将关键字加上 单引号,从而不能达成我们想要实现的效果
  • 而 使用 ${param} 直接替换,正好符合我们的需求

实例理解

  • 此处实现一个根据关键字  进行排序查询用户信息 的功能

初始化 UserMapper 接口

  • 此处我们在接口中添加一个 getUserByKeyWord 方法
import com.example.demo.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

//添加 @Mapper 注解 代表该接口会伴随这 项目的启动而注入到容器中
@Mapper
public interface UserMapper {

//    根据关键字 查询用户信息
    List getUserByKeyWord(@Param("key_word") String keyWord);
}

初始化 UserMapper XML 文件

  • 在与 接口相对应的 XML 文件中
  • 添加上与 getUserByKeyWord 方法 相对应的 sql 语句
  • 此处使用 ${key_word}




    


创建 getUserByKeyWord 的测试方法

  • 此处我们传入 desc 关键字进行降序查询
@Test
void getUserByKeyWord() {
    List users = userMapper.getUserByKeyWord("desc");
    for (User user:users) {
        System.out.println(user);
    }
}

执行测试方法

  • 测试方法执行成功

2023.11.28 MyBatis 中 #{} 和 ${} 的区别_第6张图片

模糊查询(like) 

实例理解

  • 此处实现一个 根据用户输入的字符串进行模糊查询用户信息 的功能

分析

  • 用户输入的字符串,不是一个可穷举值,是不可控的值
  • 所以我们直接排除使用 ${param}
  • 其次如果我们直接使用 #{param} 的话,也会存在一定问题

2023.11.28 MyBatis 中 #{} 和 ${} 的区别_第7张图片

  • 实际生成的 sql 语句与我们期望生成的 sql 语句显然是不同的
  • 使用预处理时,因为用户传入的是 String 类型,进行赋值时会加上单引号

concat 函数

  • concat 函数为 mysql 中内置的函数,可实现字符串拼接功能

2023.11.28 MyBatis 中 #{} 和 ${} 的区别_第8张图片

  • 由上图所示,我们可以利用 concat 函数来解决我们  #{param} 所遇到的问题

 初始化 UserMapper 接口

  • 此处我们在接口中添加一个 getListByName 方法
import com.example.demo.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

//添加 @Mapper 注解 代表该接口会伴随这 项目的启动而注入到容器中
@Mapper
public interface UserMapper {

//    根据用户名模糊查询
    List getListByName(@Param("key_name") String keyName);
}

初始化 UserMapper XML 文件

  • 在与 接口相对应的 XML 文件中
  • 添加上与 getListByName 方法 相对应的 sql 语句
  • 此处使用 concat 函数 和 #{param}




    


创建 getListByName 的测试方法

  • 此处我们传入字符串 "xiao" 进行模糊查询
@Test
void getListByName() {
    String keyName = "xiao";
    List users = userMapper.getListByName(keyName);
    for (User user: users) {
        System.out.println(user);
    }
}

执行测试方法

  • 测试方法执行成功

2023.11.28 MyBatis 中 #{} 和 ${} 的区别_第9张图片

你可能感兴趣的:(Spring,mybatis)