添加链接描述
2019.11.01 杭州的一个面试,有问道这个问题:
讲一下Mybatis中 #{}和${}的区别?
部分内容涉及到 MySQL的预编译
情况一:只用 #{}
select * from USER where userName=#{userName} and userPassword =#{userPassword};
结果:
==> Preparing: select * from USER where userName=? and userPassword =?;
> Parameters: mww(String), 123(String)
< Total: 1
结论:
#{}会在预编译期,生成两个 ?,作为占位符。
情况二:一个用 KaTeX parse error: Expected 'EOF', got '#' at position 8: {} 一个用 #̲{} <select id…{userName} and userPassword =#{userPassword};
结果:
==> Preparing: select * from USER where userName=mww and userPassword =?;
==> Parameters: 123(String)
结论:很显然 ${} 是直接拼成字符串的 ,#{} 是生成 ?占位符的。
而且 因为 userName:mww 是字符串,所以 这种写法显然也是错误的。
会报出如下错误:
Error querying database. Cause: java.sql.SQLSyntaxErrorException: Unknown column ‘mww’ in ‘where clause’
所以正确的写法是这样的:为字符串字段加上单引号 ’ ’
select * from USER where userName='${userName}' and userPassword =#{userPassword}; 结果:==> Preparing: select * from USER where userName=‘mww’ and userPassword =?;
> Parameters: 123(String)
< Total: 1
结论:显然这种写法是正确的,从这里可以看出,预编译期 ${} 是直接把参数拼结到SQL中,
运行时,就只传入了一个 #{} 修饰的参数。
${}的这种写法还有一个安全隐患,那就是 SQL注入。
情况三:
S
Q
L
注
入
:
<
s
e
l
e
c
t
i
d
=
"
g
e
t
U
s
e
r
B
y
N
a
m
e
A
n
d
P
s
w
"
r
e
s
u
l
t
T
y
p
e
=
"
c
o
m
.
h
o
t
e
l
3.
m
o
d
e
l
.
U
s
e
r
"
>
s
e
l
e
c
t
∗
f
r
o
m
U
S
E
R
w
h
e
r
e
u
s
e
r
N
a
m
e
=
′
{} SQL注入: <select id="getUserByNameAndPsw" resultType="com.hotel3.model.User"> select * from USER where userName='
SQL注入:<selectid="getUserByNameAndPsw"resultType="com.hotel3.model.User">select∗fromUSERwhereuserName=′{userName}’ and userPassword =#{userPassword};
结果:
==> Preparing: select * from USER where userName=’’ OR 1=1 OR ‘’ and userPassword =?;
> Parameters: 65787682342367(String)
< Total: 2
结论:只要我们在 ${} 输入 ’ OR 1=1 OR ’ 无论后面的密码输入什么都可以,查询到数据,这种情况就是SQL注入。
情况四:#{} 防止SQL注入
select * from USER where userName=#{userName} and userPassword =#{userPassword};
结果:
==> Preparing: select * from USER where userName=? and userPassword =?;
> Parameters: ’ OR 1=1 OR '(String), 123(String)
< Total: 0
结论:上面预编译SQL的参数已经由占位符 { ?} 代替,所以传入的 ’ OR 1=1 OR ’ 只会作为 userName字段的值,
而不会拼入执行的SQL。这样就达到了防止SQL注入的目的。
在这种用法中, #{} 显然比 ${} 用法更好。
那 ${} 为什么经常在 Mybatis 使用那?
${}正确用法(个人见解):
1、同时传入一个字段名和字段值:
User u=userService.getUserByNameAndPsw(“userName,userType,userPassword”,userName,userPassword);
SQL: select ${arg0} from USER
==> Preparing: select userName,userType,userPassword from USER where userName=? and userPassword =?;
> Parameters: mww(String), 123(String)
< Total: 1
结论:
生成了我们想要SQL语句 :select userName,userType,userPassword from USER。。。。
2、传入两个字段名和字段值:
User u=userService.getUserByNameAndPsw(“userName,userType,userPassword”,userName,userName,userPassword);
SQL: select ${arg0} from USER where ${arg1}=#{userName}
==> Preparing: select userName,userType,userPassword from USER where userName=? and userPassword =?;
> Parameters: mww(String), 123(String)
< Total: 1
结论:
按照传参的顺序匹配 SQL 中的 a r g 0 , {arg0}, arg0,{arg1}。。。
生成我们想要的代码,但这个方式会使Mybatis 的 Mapper 文件可读性变差,
如果不看其他的代码,很难辨别 a r g 0 , {arg0} , arg0,{arg1} 代表的含义。
3、使用Map传值,提高 Mapper 文件的可读性
复制代码
Map map =new HashMap();
map.put(“selectValues”,“userName,userType,userPassword”);
map.put(“userNamefieId”,“userName”);
map.put(“userName”,userName);
map.put(“userPassword”,userPassword);
User u=userService.getUserByNameAndPsw(map);
复制代码
Mapper 文件的 xml
==> Preparing: select userName,userType,userPassword from USER where userName=? and userPassword =?;
> Parameters: mww(String), 123(String)
< Total: 1
结论:
结果和arg0、arg1的传值方式相同,但 Mapper 文件的 xml 中的SQL语句可以
通过对 map 中 key 值命名提高可读性。
注:以上SQL,均为预编期生成的SQL。
注2:${} 每次传值不同 SQL 语句不同,可以灵活生成 SQL,
但每次都需要进行预编译,对效率会有影响,至于要不要使用 ${}还要具体情况具体分析。