关注“苏三说技术”,回复:代码神器、开发手册、时间管理 有惊喜。
如果有用过mybatis的朋友,肯定对#{ }非常熟悉。
让我们先一起看看#{ }的用法。
数据库中有2条数据,如图:
我们先定义一个实体:
@Data
public class JumpLogModel {
/**
* 系统ID
*/
private String id;
/**
* 应用编号
*/
private String app;
/**
* 跳转url
*/
private String url;
/**
* ip地址
*/
private String ip;
/**
* 区域名称
*/
private String areaName;
/**
* 操作系统
*/
private String os;
/**
* 浏览器
*/
private String browser;
/**
* 来源
*/
private String refer;
/**
* 操作时间
*/
private Date inDate;
}
然后定义mapper:
public interface JumpLogService {
JumpLogModel selectById(String id);
}
再定义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.sue.jump.mappers.JumpLogMapper">
<resultMap type="com.sue.jump.model.JumpLogModel"
id="JumpLogResult">
<result property="id" column="id"/>
<result property="app" column="app"/>
<result property="url" column="url"/>
<result property="ip" column="ip"/>
<result property="areaName" column="area_name"/>
<result property="os" column="os"/>
<result property="browser" column="browser"/>
<result property="inDate" column="in_date"/>
</resultMap>
<select id="selectById" resultMap="JumpLogResult">
select
id,app,url,ip,area_name,os,browser
from jump_log
<where>
id = #{id}
</where>
</select>
</mapper>
定义service层:
@Service
public class JumpLogServiceImpl implements JumpLogService {
@Autowired
private JumpLogMapper jumpLogMapper;
@Override
public JumpLogModel selectById(String id) {
return jumpLogMapper.selectById(id);
}
}
定义controller层
@RequestMapping("/jump")
@RestController
public class JumpLogController {
@Autowired
private JumpLogServiceImpl jumpLogService;
@GetMapping("/get/{id}")
public JumpLogModel get(@PathVariable String id) {
return jumpLogService.selectById(id);
}
}
调用接口,id=123456
我们看到可以通过id查询到正确的数据,说明#{ }生效了。
那么我们把#{ },改成${ }再试试。
<select id="selectById" resultMap="JumpLogResult">
select
id,app,url,ip,area_name,os,browser
from jump_log
<where>
id = ${id}
</where>
</select>
再调用接口,id=123456
同样可以根据id查询出正确的数据。那么有人可能会说,#{ } 和 ${ }不是一样吗?二者有什么区别呢?
接下来,我们重点看看二者的区别。
现有#{ }接收参数
<select id="selectById" resultMap="JumpLogResult">
select
id,app,url,ip,area_name,os,browser
from jump_log
<where>
id = #{id}
</where>
</select>
把id的值由123456改成:123456 or 1=1,再调用接口
依然可以返回正确的数据。
再改成${ }接收参数
<select id="selectById" resultMap="JumpLogResult">
select
id,app,url,ip,area_name,os,browser
from jump_log
<where>
id = ${id}
</where>
</select>
报错了。。。。。。
提示了:Expected one result (or null) to be returned by selectOne(), but found: 2
通过id原本只能返回第1条数据,结果返回了2条数据。怎么回事?
原来通过${ }接收参数之后,最后拼接的sql如下:
select id,app,url,ip,area_name,os,browser from jump_log where id = 123456 or 1=1
明白了,这是典型的sql注入,后面的 or 1=1 会让前面的 id=123456条件失效,相当于整个where条件都失效了,最后sql相当于执行了:
select id,app,url,ip,area_name,os,browser from jump_log
肯定会返回2条数据。
那么问题来了,#{ }的方式为什么没有问题呢?
因为#{ }接收参数使用了sql预编译,最后拼接的sql会变成:
select id,app,url,ip,area_name,os,browser from jump_log where id = ?
执行sql时会将参数进行转义,把传入的参数:123456 or 1=1加了单引号',执行时的sql是:
select id,app,url,ip,area_name,os,browser from jump_log where id = '123456 or 1=1'
可以正确返回1条数据。
我们可以得出结论,#{ } 通过预编译可以防止sql注入。
那是不是在实际开发中都用#{ }就好了,不需要使用${ }了?
其实,不然,
比如有这样的场景:数据库的名称需要通过参数统一起来,以便下次修改数据库名时,只有修改一个地方即可。
在mybatis-config.xml文件中配置:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties>
<property name="mallDbName" value="sue_mall_db"/>
</properties>
</configuration>
xml中使用
<select id="selectById" resultMap="JumpLogResult">
select
id,app,url,ip,area_name,os,browser
from ${mallDbName}.jump_log
<where>
id = #{id}
</where>
</select>
接下来,我们分析一下源码,看看#{}是怎么替换成?的
先看看XMLMapperBuilder类的configurationElement方法。
重点看看buildStatementFromContext方法:
会调用XMLStatementBuilder类的parseStatementNode方法:
进入LawLanguageDriver类的createSqlSource方法:
我们一起看看XmlScriptBuilder类的parseScriptNode方法:
看看this方法,即下面的构造方法:
最终我们会发现在SqlSourceBuilder类的GenericTokenParser解析器就是把#{} 符合 替换 为 ?占位符
总结一下:
${ } 直接的 字符串 替换,在mybatis的动态 SQL 解析阶段将会进行变量替换。
#{ } 通过预编译,用占位符的方式传值可以把一些特殊的字符进行转义,这样可以防止一些sql注入。
大家喜欢这篇文章的话,请关注一下 :苏三说技术