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}
<select id="getUserByName" resultType="com.example.demo.entity.User">
        select * from user where name = ${user_name}
</select>
  • 利用单元测试 执行调用该 sql 语句
@SpringBootTest
class UserMapperTest {

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

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

执行结果:

  • 发生报错!!

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

实例理解二

  • 使用 #{param}
<select id="getUserByName" resultType="com.example.demo.entity.User">
        select * from user where name = #{user_name}
</select>
  • 利用单元测试 执行调用该 sql 语句
@SpringBootTest
class UserMapperTest {

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

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

执行结果:

  • 未发生报错!


注意:

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

sql 注入问题 

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

实例理解

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

初始化数据库

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


初始化 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 语句
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybati
s.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">

    <select id="login" resultType="com.example.demo.entity.User">
        select * from user where name = '${user_name}' and password = '${password}'
    </select>

</mapper>

创建 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);
    }
}

执行测试方法

  • 测试方法执行成功

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

分析 sql 语句

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


使用 ${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<User> getUserByKeyWord(@Param("key_word") String keyWord);
}

初始化 UserMapper XML 文件

  • 在与 接口相对应的 XML 文件中
  • 添加上与 getUserByKeyWord 方法 相对应的 sql 语句
  • 此处使用 ${key_word}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybati
s.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">

    <select id="getUserByKeyWord" resultType="com.example.demo.entity.User">
        select * from user order by id ${key_word}
    </select>

</mapper>

创建 getUserByKeyWord 的测试方法

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

执行测试方法

  • 测试方法执行成功

模糊查询(like) 

实例理解

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

分析

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

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

concat 函数

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

  • 由上图所示,我们可以利用 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<User> getListByName(@Param("key_name") String keyName);
}

初始化 UserMapper XML 文件

  • 在与 接口相对应的 XML 文件中
  • 添加上与 getListByName 方法 相对应的 sql 语句
  • 此处使用 concat 函数 和 #{param}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybati
s.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">

    <select id="getListByName" resultType="com.example.demo.entity.User">
        select * from user where name like concat('%',#{key_name},'%')
    </select>

</mapper>

创建 getListByName 的测试方法

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

执行测试方法

  • 测试方法执行成功

  • 20
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
{ "timestamp": "2023-07-14T11:17:15.188+0000", "status": 500, "error": "Internal Server Error", "message": "\r\n### Error querying database. Cause: java.sql.SQLException: sql injection violation, syntax error: syntax error, error in :&#39;RDER BY province_id LIMIT 1&#39;, expect BY, actual BY pos 98, line 3, column 56, token BY : SELECT *\n FROM batch_control_line\n WHERE province_id = ? AND `year`=? LIMIT ORDER BY province_id LIMIT 1\r\n### The error may exist in mybatis/mapper/BatchControlLineMapper.xml\r\n### The error may involve com.college.collegesystem.dao.BatchControlLineMapper.findBatchControlLineByID\r\n### The error occurred while executing a query\r\n### SQL: SELECT * FROM batch_control_line WHERE province_id = ? AND `year`=? LIMIT ORDER BY province_id LIMIT 1\r\n### Cause: java.sql.SQLException: sql injection violation, syntax error: syntax error, error in :&#39;RDER BY province_id LIMIT 1&#39;, expect BY, actual BY pos 98, line 3, column 56, token BY : SELECT *\n FROM batch_control_line\n WHERE province_id = ? AND `year`=? LIMIT ORDER BY province_id LIMIT 1\n; uncategorized SQLException; SQL state [null]; error code [0]; sql injection violation, syntax error: syntax error, error in :&#39;RDER BY province_id LIMIT 1&#39;, expect BY, actual BY pos 98, line 3, column 56, token BY : SELECT *\n FROM batch_control_line\n WHERE province_id = ? AND `year`=? LIMIT ORDER BY province_id LIMIT 1; nested exception is java.sql.SQLException: sql injection violation, syntax error: syntax error, error in :&#39;RDER BY province_id LIMIT 1&#39;, expect BY, actual BY pos 98, line 3, column 56, token BY : SELECT *\n FROM batch_control_line\n WHERE province_id = ? AND `year`=? LIMIT ORDER BY province_id LIMIT 1", "trace": "org.springframework.jdbc.UncategorizedSQLException: \r\n### Error querying database. Cause: java.sql.SQLException: sql injection violation, syntax error: syntax error, error in :&#39;RDER BY province_id LIMIT 1&#39;, expect BY, actual BY pos 98, lin
07-15

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

茂大师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值