该篇文章写于2016年10月21日
一直在使用Mybatis这个ORM框架,都是使用mybatis里的一些常用功能。今天在项目开发中有个业务是需要限制各个用户对某些表里的字段查询以及某些字段是否显示,如某张表的某些字段不让用户查询到。这种情况下,就需要构建sql来动态传入表名、字段名了。现在对解决方法进行下总结,希望对遇到同样问题的伙伴有些帮助。
动态SQL是mybatis的强大特性之一,mybatis在对sql语句进行预编译之前,会对sql进行动态解析,解析为一个BoundSql对象,也是在此处对动态sql进行处理。下面让我们先来熟悉下mybatis里#{}
与${}
的用法。
在动态sql解析过程,#{}
与${}
效果是不一样的:
#{}
解析为一个 JDBC 预编译语句(prepared statement)的参数标记符。
如以下sql语句:
1 | select * from user where name = #{name}; |
会被解析为:
1 | select * from user where name = ?; |
可以看到#{}被解析为一个参数占位符?
。
${ }
仅仅为一个纯碎的 string 替换,在动态 SQL 解析阶段将会进行变量替换
如以下sql语句:
1 | select * from user where name = ${name}; |
当我们传递参数“yuanrengu”时,sql会解析为:
1 | select * from user where name = "yuanrengu"; |
可以看到预编译之前的sql语句已经不包含变量name了。
综上所得, ${ }
的变量的替换阶段是在动态 SQL 解析阶段
,而 #{ }
的变量的替换是在 DBMS 中。
#{}
与${}
的区别可以简单总结如下:
- #{}将传入的参数当成一个字符串,会给传入的参数加一个双引号
${}
将传入的参数直接显示生成在sql中,不会添加引号- #{}能够很大程度上防止sql注入,
${}
无法防止sql注入
${}
在预编译之前已经被变量替换了,这会存在sql注入的风险。如下sql
1 | select * from ${tableName} where name = ${name} |
如果传入的参数tableName为user; delete user; —
,那么sql动态解析之后,预编译之前的sql将变为:
1 | select * from user; delete user; -- where name = ?; |
—
之后的语句将作为注释不起作用,顿时我和我的小伙伴惊呆了!!!看到没,本来的查询语句,竟然偷偷的包含了一个删除表数据的sql,是删除,删除,删除!!!重要的事情说三遍,可想而知,这个风险是有多大。
${}
一般用于传输数据库的表名、字段名等- 能用#{}的地方尽量别用
${}
通过上面的分析,相信大家可能已经对如何动态调用表名和字段名有些思路了。示例如下:
1 | <select id="getUser" resultType="java.util.Map" parameterType="java.lang.String" statementType="STATEMENT"> |
要实现动态调用表名和字段名,就不能使用预编译了,需添加statementType=”STATEMENT”。
1 | statementType:STATEMENT(非预编译),PREPARED(预编译)或CALLABLE中的任意一个,这就告诉 MyBatis 分别使用Statement,PreparedStatement或者CallableStatement。默认:PREPARED。这里显然不能使用预编译,要改成非预编译。 |
其次,sql里的变量取值是${xxx}
,不是#{xxx}。
因为${}
是将传入的参数直接显示生成sql,如${xxx}传入的参数为字符串数据,需在参数传入前加上引号,如:
1 | String name = "sprite"; |
mybatis动态调用表名和字段名,还可以应用于日志的收集上,如数据库的日志表,每隔一个月动态建一个日志表,表名前缀相同(如log_201610,log_201611等),这样实现日志的分月分表存储,方便日志的分析。
使用PrepareStatement执行SQL的好处主要有两个:
- PrepareStatement会预先提交带占位符的SQL到数据库
预处理
,提前生成执行计划,当给定占位符参数,真正执行SQL的时候,执行引擎可以直接执行,效率更好一点。 - 可以
防止SQL注入攻击
。因为一开始构造PrepareStatement的时候就已经提交了SQL,并被数据库预先生成了执行计划
,后面不管提交什么样的字符串,都只能交给这个执行计划去执行,不可能再生成一个新的SQL了,也就不会被攻击了。