mybatis的面试或者实际中,我们经常会遇到这个问题,下面我们先来总结一下他们之间大致的区别,最后通过实例演示一下。
${}:这个其实就是简单的替换,有些傻瓜式,比如name="zhangsan",sql: select * from users where name=${name},最后的语句就是:
select * from users where name = zhangsan
乍一看,没毛病啊,但是执行的时候,会报一个错误:
org.springframework.jdbc.BadSqlGrammarException:
### Error querying database. Cause: java.sql.SQLSyntaxErrorException: Unknown column 'zhangsan' in 'where clause'
### The error may exist in file [D:\eclipse\workspaces\demo\target\classes\mapper\UserMapper.xml]
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### SQL: select * from xx_user where name = zhangsan
### Cause: java.sql.SQLSyntaxErrorException: Unknown column 'zhangsan' in 'where clause'
其实,正常的语句应该是:
select * from users where name = 'zhangsan'
但是这种傻瓜式并不是一点好处也没有,后面会讲到它的特殊之处。默认,${}替换的时候,它的变量名是value,所以在接口声明的时候,如果是一个参数,默认指定为value,比如findById(Integer value),或者findByName(String value)就可以了,最后在sql中,我们需要根据参数数据类型,如果是字符串类型,需要在sql中加上单引号('')如下所示:
select * from users where name = '${value}'
如果是非字符串类型,比如整形,长整形,双精度类型,那么几乎就是和#{}用法没有区别了,这里假定我们的id字段是整形:
select * from users where id = ${value}
默认情况下参数名为value:
接口声明:User findByDefault(String value);
xml配置如下:
<select id="findByDefault" resultType="com.xxx.demo.domain.User">
select * from xx_user where name = '${value}'
</select>
值得一提的是,${}这种替换因为默认参数名是value,如果我们不小心指定了别的,比如name,age,email等等,也不是不可以,如果我们在接口声明的地方不作说明,那么就会报错:
There is no getter for property named 'name' in 'class java.lang.String'
解决这个问题,其实很简单,有两种办法:
一种是通过注解给参数设置@Param("name"),@Param("age"),@Param("email")就行了。
1、保证sql正确被替换,字符串类型,需要加上单引号:select * from xx_user where name = '${name}'
2、接口方法使用@Param("name")注解参数:User findByName(@Param("name")String name);
还有一种办法,就是把参数封装成一个key-value的键值对,即map,这样,参数相当于有了名字,最后替换的时候,就不会出错了。
1、同样要确保sql中参数如果是字符串,需要单引号:select * from xx_user where name = '${name}'
2、参数没有名字,就给他封装为map。需要修改service方法和dao方法声明:
UserMapper.java
User findByName(Map<String,Object> params);
UserService.java
public User findByName(String name){
Map<String,Object> params = new HashMap<>();
params.put("name", name);
return userMapper.findByName(params);
}
前面提到${}替换非常傻瓜,但是这种傻瓜式却有很好的一个场景,比如我们的表名、或者是排序字段也是动态指定的呢,这种场景就非常适合这种傻瓜式的替换了。
比如:table_name = 'users' , order_column = "name" ,sql: select * from ${table_name} order by ${order_column},最后执行的sql会如下:
select * from users order by name
这里,因为table_name,order_column属性都是字符串类型,如果使用#{},那么在执行参数替换之后,执行的sql如下:
select * from 'users' order by 'name'
#{}:mybatis推荐用法,它是占位符替换,开始,会将#{}参数替换为"?",之后,会根据对应的类型做参数替换,字符串类型,会自动加上单引号('')。前面说了如果表名或者排序字段是动态的,也需要做替换,这时候,使用#{},反而就会出现问题,因为表名和字段是字符串类型,替换之后,会默认加上单引号,这个场景适合${}。
至于有的说${}可能会导致sql注入的风险,使用#{}能够避免,个人理解sql注入在实际中并不是很常见,当我们的程序对参数检测做的足够严格,其实就很好规避这类问题,只能说有可能会导致,并不是说使用${}就一定会导致sql注入。