前言

动态SQL是MyBatis的强大特性之一,我们在使用MyBatis作为持久层框架时,经常需要动态传递参数,例如我们需要根据用户的姓名来筛选用户时,SQL如下:

1
select * from user where name = "Lihua";

上述SQL中,我们希望name是动态可变的,即不同的时刻根据不同的姓名来查询用户,那么在MyBatis的xml中可以如下配置:

1
select * from user where name = #{name};

或者

1
select * from user where name = '${name}';

这两种方式的本质是不同的,如果不了解其原理,在某些场景下会导致意想不到的后果。

区别

MyBatis在对SQL语句进行预编译之前,会对SQL进行动态解析,解析为一个BoundSql对象,也正是在这个阶段#{}${}会有不同的表现。

#{}

在动态SQL解析阶段,#{}解析为一个JDBC预编译(PreparedStatement)的参数标记符。例如,如下的SQL语句:

1
select * from user where name = #{name};

会被解析为:

1
select * from user where name = ?;

也就是说,一个#{}被解析为一个参数占位符?

${}

在动态SQL解析阶段,${}仅仅为一个纯粹的字符串替换。例如,如下的SQL语句:

1
select * from user where name = '${name}';

当我们传递的参数为Lihua时,上述SQL解析为:

1
select * from user where name = "Lihua";

也就是说,预编译前的SQL语句已经不包含变量name了。

总结

对于#{},它其实就对应着JDBC中的PreparedStatement?,因此一旦MySQL服务器对SQL模板进行了编译,并且存储了函数,PreparedStatement做的就是把参数进行转义后直接传入参数到数据库,然后让函数执行,也就避免了SQL注入的问题;而${}的变量替换是在动态SQL解析阶段,也就是预编译之前,相当于这个SQL语句已经是个常量了,因此会产生SQL注入的问题。

参考资料