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