MyBatis — 参数占位符 #{} 和 ${}

导读:本篇文章讲解 MyBatis — 参数占位符 #{} 和 ${},希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

一、准备工作

创建数据库和表

-- 创建数据库
drop database if exists mycnblog;
create database mycnblog DEFAULT CHARACTER SET utf8mb4;

-- 使用数据数据
use mycnblog;

-- 创建表[用户表]
drop table if exists  userinfo;
create table userinfo(
    id int primary key auto_increment,
    username varchar(100) not null,
    password varchar(32) not null,
    photo varchar(500) default '',
    createtime datetime default now(),
    updatetime datetime default now(),
    `state` int default 1
) default charset 'utf8mb4';

添加实体类

package com.example.demo.model;

import lombok.Data;

import java.util.Date;

/**
 * 普通的实体类,用于 Mybatis 做数据库表(userinfo表)的映射
 * 注意事项:保证类属性名称和userinfo表的字段完全一致!
 */
@Data
public class UserInfo {
    private int id;
    private String name;
    private String password;
    private String photo;
    private Date createtime;
    private Date updatetime;
    private int state;
}

添加 mapper 接口 (数据持久层)

package com.example.demo.mapper;

import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper {
    
}

创建与接口对应的 xml 文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">

</mapper>

二、参数占位符 #{} 和 ${}

  • #{}:预编译处理。
    预编译处理是指:MyBatis 在处理#{}时,会将 SQL 中的 #{} 替换为?号,使⽤ PreparedStatement 的 set ⽅法来赋值。

  • ${}:字符直接替换。
    直接替换:是MyBatis 在处理 ${} 时,就是把 ${} 替换成变量的值。

预编译处理和字符串替换的区别故事(头等舱和经济舱乘机分离的故事):
乘坐飞机,头等舱和经济舱的区别是很⼤的。

  • ⼀般航空公司乘机都是头等舱和经济舱分离的,头等舱的⼈先登机,登机完之后,封闭经济舱,然后再让经济舱的乘客登机,这样的好处是可以避免浑⽔摸⻥,经济舱的⼈混到头等舱的情况,这就相当于预处理,可以解决程序中不安全(越权处理)的问题。
    在这里插入图片描述
  • ⽽直接替换的情况相当于,头等舱和经济舱不分离的情况,这样经济舱的乘客在通过安检之后可能越权摸到头等舱,如下图所示:
    在这里插入图片描述
    这就相当于参数直接替换,它的问题是可能会带来越权查询和操作数据等问题,⽐如后⾯会讲的 SQL 注⼊问题。

三、特殊场景

3.1 特殊场景 1 – String

3.1.1 使用 #{}

username 唯一。

插入一条数据:

-- 添加一个用户信息
INSERT INTO `mycnblog`.`userinfo` (`id`, `username`, `password`, `photo`, `createtime`, `updatetime`, `state`) VALUES 
(1, 'admin', 'admin', '', '2021-12-06 17:10:48', '2021-12-06 17:10:48', 1);

UserMapper 接口:

    // 根据用户姓名完全匹配查询
    public UserInfo getUserByName(@Param("username") String username);

UserMapper.xml:

    <select id="getUserByName" resultType="com.example.demo.model.UserInfo">
        select * from userinfo where username=#{username}
    </select>

单元测试:

package com.example.demo.mapper;

import com.example.demo.model.UserInfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest // 当前测试的上下文环境为 springboot
class UserMapperTest {

    @Autowired
    private UserMapper userMapper;

    @Test
    void getUserByName() {
        UserInfo userInfo = userMapper.getUserByName("admin");
        System.out.println("userinfo -> " + userInfo);
    }
}

经测试,成功打印了我们插入的数据信息。

3.1.2 使用 ${}

UserMapper.xml:

    <select id="getUserByName" resultType="com.example.demo.model.UserInfo">
        select * from userinfo where username=${username}
    </select>

此时进行单元测试,报错了!:
在这里插入图片描述

3.1.3 分析与解决

  • #{} 预编译处理:识别出来为字符串,就会自动加上引号''
  • ${} 字符直接替换:缺少引号,所以查找不到 username (去查找字段了!)

在其外加一层单引号:'${username}' 即可。

那么无论是 #{} 还是 ${},都加上 '' 不就可以了吗?
理论上是可以的,但是会产生很多隐式类型转换!一旦存在隐式类型转换就不会走索引了,效率会大大降低!!!

3.2 特殊场景 2 – MySQL 关键字

例如排序时:order by xxx asc/desc

在这里插入图片描述

此时传递的虽然也是一个字符串,但是我们并不希望给它加上引号!
所以只能使用 ${} 字符直接替换!

UserMapper 接口:

    // 查询所有的信息(根据排序条件进行排序)
    public List<UserInfo> getAllByOrder(@Param("order") String order);

UserMapper.xml:

    <select id="getAllByOrder" resultType="com.example.demo.model.UserInfo">
        select * from userinfo order by id ${order}
    </select>

但因为 SQL 注入问题,这种场景使用 ${} 时必须在 controller 中验证一下参数 (字符串必须是 asc/desc,否则代码就不要继续往下执行了!)

3.3 特殊场景 3 – SQL 注入问题 (重要)

    <select id="isLogin" resultType="com.example.demo.model.UserInfo">
        select * from userinfo where username='${name}' and password='${pwd}'
    </select>

sql 注入代码:' or 1='1

在这里插入图片描述

${} 字符直接替换,这时就忽略了密码直接登录了!十分危险!!!
因此绝大部分场景都会避免使用 ${}。

3.4 特殊场景 4 – 模糊查询 like

    <select id="findUserByName2" resultType="com.example.demo.model.UserInfo">
        select * from userinfo where username like '%#{username}%';
    </select>

使用 #{} 会报错!
预编译会识别为字符串类型自动加上引号,不符合预期
相当于是:select * from userinfo where username like ‘%‘username’%’;

此时也并不能直接使用 ${}:会有 SQL 注入风险。在 controller 进行检查判断也不可取,因为传来的字符串是有无数种可能的,无法检查判断!

这时可以使用 MySQL 的内置函数 concat() 来处理,实现代码如下:

    <select id="findUserByName3" resultType="com.example.demo.model.UserInfo">
        select * from userinfo where username like concat('%',#{username},'%');
    </select>

这种写法完全没有问题 ~

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/118546.html

(0)
seven_的头像seven_bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!