我们在mybatis框架中写sql语句的时候,分为静态语句和动态语句。

 

静态语句

就是在java中直接写入的SQL语句,在达到程序设计的节点时就会进行SQL语句的查询。

动态语句

相比静态语句来说,mybatis中用得多的是动态语句,因为业务中传过来的值是不一定的,是动态变化的,所以动态SQL是有很多东西需要学习的。

以一个例子来讲,使用Mybatis 动态表名,查询数据,比如最近项目中使用到根据时间进行查询表的需求(即表名是根据时间变化的)

mapper.java 文件:

public Gpsinfo selectGpsByPlateNo(@Param("tableName")String tableName,@Param("plateNo")String plateNo);

mapper.xml 文件:

<select id="selectGpsByPlateNo"  parameterType="String" resultMap="BaseResultMap" statementType="STATEMENT">
       SELECT *FROM
               ${tableName}
        where plateNo='${plateNo}'
        order by createDate desc limit 1
</select>

serviceImpl.java文件:

@Override  
public Gpsinfo selectGpsByPlate(String plateNo) {  
      Gpsinfo gpsinfo = new Gpsinfo();  
      Date date=new Date();
      DateFormat format=new SimpleDateFormat("yyyyMMdd");
      String tableName="gpsinfo"+format.format(date);
      gpsinfo = myGpsMapper.selectGpsByPlateNo(tableName,plateNo);  
      return gpsinfo;  
}

表名根据时间进行拼接

这是一次别人代码优化过程中发现的问题,在功能优化后发现部分数据查出来了,问题就在于一条sql上的#和$。

下图为两条sql:

java Mapper文件 sql 和String拼接 mapper的sql语句_字符串


从图上可以看出 wwlr.LabelId in(${showLabels}) 和 wwlr.LabelId in(#{showLabels}),其中showLabels是传进来一个字符串类型的参数,参数的样子是这样的“4,44,514”,问题就出在这个参数传进来后#和$处理的方式是不一样的。

区别

作用

应用场景

缺点

#{ }

相当于占位符,预编译处理,MyBatis在处理#{ }时,它会将sql中的#{ }替换为?,然后调用PreparedStatement的set方法来赋值,传入字符串后,会在值两边加上单引号,如值 “1,0”就会变成“ ‘1,0’ ”;

获取DAO中的参数数据,在映射文件的SQL语句中出现#{}表达式,底层会创建预编译的SQL;

${ }

字符串拼接与替换, MyBatis在处理${ }时,它会将sql中的${ }替换为变量的值,传入的数据不会在两边加上单引号。

获取配置文件数据,DAO接口中的参数信息,当$出现在映射文件的SQL语句中时创建的不是预编译的SQL,而是字符串的拼接,有可能会导致SQL注入问题.所以一般使用$接收dao参数时,这些参数一般是字段名,表名等,例如order by ${column}。

1.有时需要在${parameter}前后加上单引号;

2.导致sql注入,不利于系统的安全性。

关于#{}底层调用的PreparedStatement中的set方法:

这里涉及到一个知识点叫做SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。常见的有匿名登录(在登录框输入恶意的字符串)、借助异常获取数据库信息等。因此有些情况下能使用#{}达到要求的情况下就尽量不要使用${}了。

模糊查询

mybatis中的模糊查询有3种方式:

1.首先个人推荐使用CONCAT函数(concat,合并多个数组、字符串):

public interface FoodMapper{
    @Select("select * from table where name like CONCAT('%',#{name},'%')")
    List<Food> fuzzySearchByName(String name);
}

对应的Controller类:

public class foodController{
    @Autowired
    FoodMapper foodMapper = new FoodMApper()

    @Component("/fuzzySearch")
    Public List<Food> fuzzySearch(String name){
        return fuzzySearchByName(name)
    }
}

2.#{}

public interface FoodMapper{
    @Select("select * from table where name like '%#{name,jdbcType=VARCHAR}%')")
    List<Food> fuzzySearchByName(String name);
}

3.${}

public interface FoodMapper{
    @Select("select * from table where name like '%${name}%')")
    List<Food> fuzzySearchByName(String name);
}

推荐使用前两种方法,因为前两种方法使用的是#{},所以不会引起SQL注入,风险较小。

条件查询:

假设有以下业务场景,根据3个条件查找出符合的数据,但是这3个条件可以为任何值,空值也是被允许的,现在为了直接将空值过滤掉,只查找有具体条件内容的属性,可以像下面这样写:

<select id="getByParams" resultMap="BaseResultMap" parameterType="string">
        SELECT
        *
        from
            xxx_t
        where
            1 = 1
        <if test = "null != number ">
           and xx_number = #{xxNumber}
        </if>
        <if test = "null != age">
           and xx_age = #{xxAge}
        </if>
        <if test = "null != state">
           and xx_state = #{xxState}
        </if>
</select>

case并列判断:

<choose><when>跟<if>是有区别的,类似于java语法中case与if、else之间的关系。

使用‘if‘ ‘else ‘ 判断 when otherwise,when其实自带一个break,只要有一个条件符合,就自动break跳出。

<select id="getByParams" resultMap="BaseResultMap" parameterType="string">
        SELECT
        *
        from
            xxx_t
        <where>
            <choose>
            <when test = "1 == number">
                number = 100
            </when>
            <when test = "2 == number">
                number = 200
            </when>
            <when test = "3 == number">
                number = 300
            </when>
            </choose>
        </where>
</select>

也可以使用when otherwise。when的意思就是和java 代码中switch 的case一样,只能进一个判断。在所有的when条件都不满足的情况下就是进的otherwise。比如说我此时传了sum=5 那sql就是的aaa=bbb。