MyBatis_动态SQL

7.1. 遇到的问题

客户端查询的信息不确定,因此查询条件不确定,需要动态判断

  • 示例:查询条件的不确定
    在这里插入图片描述
    以前的写法:
  1. 编写mapper接口(dao中的方法)
  2. 在mapper.xml编写SQL片段(statement)
  3. 测试
  • mapper接口
//多条件查询
public List<User> selectByNameAndBirthday(User user);
  • mapper.xml
<!--多条件查询-->
<select id="selectByNameAndBirthday" resultType="user">
    select <include refid="baseColumn"></include>
    from `user`
    where username like #{username}
    AND birthday = #{birthday}
</select>
  • 测试
 /**
     * 多条件查询
     */
@Test
public void selectByNameOrPhone() throws ParseException {
    //使用工具类获取sqlSession
    SqlSession sqlSession = MyBatisUtils.getSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    //模拟接收到的前台参数
    String username = "张";
    Date birthday = new SimpleDateFormat("yyyy-MM-dd")
        .parse("1998-12-25");

    User user = new User();
    user.setUsername("%"+username+"%");
    //user.setBirthday(birthday);

    //根据条件查询
    List<User> users = mapper.selectByNameAndBirthday(user);
    for (User u : users) {
        System.out.println(u);
    }
    //关闭会话
    MyBatisUtils.closeSession();
}
  • 问题:
    当没有输入全部查询条件时,反而查询不到结果,可以从日志的SQL语句中找到原因
==>  Preparing: 
select uid,username,birthday,phone,sex,address from `user` where username like ? AND birthday = ?
[DEBUG] 2020-07-19 23:01:26,937 method:org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:137)
==> Parameters: %张%(String), null

没有传入参数时,参数值为null,对应SQL如下:

select uid,username,birthday,phone,sex,address from `user` where username like " %张%" AND birthday = null

AND birthday = null 肯定查询不到结果,这时就需要动态判断参数是否有值,动态拼接SQL,下面我们就来学习学习MyBatis提供的动态SQL功能。

7.2. 动态SQL

动态 SQL 是 MyBatis 的强大功能之一,可以完成各种复杂的SQL,动态SQL 相关元素如下:

  • if
  • where
  • choose(when otherwise)
  • foreach
  • set(了解)
  • trim(了解)
7.2.1. if

if:对传入参数的属性值进行判断,结果为true则拼接SQL片段,否则不拼接

修改前面根据用户名和手机号查询的SQL片段

  • mapper.xml
<!--动态SQL - if -->
<select id="selectByNameAndBirthday2" resultType="user">
    select <include refid="baseColumn"></include>
    from `user`
    where
    <if test="username != null and username != ''">
        username like #{username}
    </if>
    <if test="birthday != null">
        AND birthday = #{birthday}
    </if>

</select>

这里使用if判断,如果用户输入了username、birthday,则该属性值!=null,拼接对应的查询SQL,否则不拼接

  • 注意

if判断中,条件使用 and 、or拼接,不能使用 & | AND OR

非字符串类型不要进行空串比较,pro != ‘’,因为会先将pro强制转为字符串

  • mapper接口
public List<User> selectByNameAndBirthday2(User user);
  • 测试
/**
     * 动态sql - if
     */
@Test
public void selectByNameOrPhone2() throws ParseException {
    //使用工具类获取sqlSession
    SqlSession sqlSession = MyBatisUtils.getSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    //模拟接收到的前台参数
    String username = "张";
    Date birthday = new SimpleDateFormat("yyyy-MM-dd")
        .parse("1998-12-25");

    User user = new User();
    //user.setUsername("%"+username+"%");
    user.setBirthday(birthday);

    //根据条件查询
    List<User> users = mapper.selectByNameAndBirthday2(user);
    for (User u : users) {
        System.out.println(u);
    }
    //关闭会话
    MyBatisUtils.closeSession();
}

测试:输入birthday 结果:异常,对应SQL

SELECT uid,username,birthday,phone,sex,address FROM `user` WHERE AND birthday = ?

测试:不输入参数 结果 : 异常,对应SQL

SELECT uid,username,birthday,phone,sex,address FROM `user` WHERE

​问题

  • 有where条件时,加上where关键字,并自动判断要不要加and
  • 希望没有条件时,不要where关键字
7.2.2. where

使用where元素解决上述问题

<!--动态SQL - if where -->
<select id="selectByNameAndBirthday2" resultType="user">
    select <include refid="baseColumn"></include>
    from `user`
    <where>
        <if test="username != null and username != ''">
            username like #{username}
        </if>
        <if test="birthday != null">
            AND birthday = #{birthday}
        </if>
    </where>
</select>
7.2.3. choose

choose相当于java中的switch

  • 需求
    • 根据性别查询,注意问题,用户输入的是“男”“女”,数据库中国存储的是1和2表示男女
  • mapper接口
//动态sql - choose
public List<User> dynamicSQLChoose(String sex);
  • mapper.xml
<select id="dynamicSQLChoose" resultType="user">
    select
    <include refid="baseColumn"></include>
    from `user`
    <where>
        <choose>
            <when test='sex != null and sex == "男"'>
                and sex = '1'
            </when>
            <when test='sex != null and sex == "女"'>
                and sex = '2'
            </when>
            <otherwise>
                and sex is not null
            </otherwise>
        </choose>
    </where>
</select>
  • 这里必须注意:当比较的值只有一个字符时,mybatis会把值当成char处理,转成数值,汉字是无法直接转成数值的,因此这里要用双引号,表示是字符串
<when test='sex != null and sex == ""'>
  • 测试
/**
  * 动态SQL choose
  */
@Test
public void dynamicSQLChoose(){
    SqlSession sqlSession = MyBatisUtils.getSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    List<User> list = mapper.dynamicSQLChoose("女");
    for (User user : list) {
        System.out.println(user);
    }
     //关闭会话
    MyBatisUtils.closeSession();
}
7.2.4. foreach

类似于java中for循环,主要用于in语句的条件查询

需求:根据多个ID进行查询

  • UserMapper.xml
<!--
SELECT uid,username,birthday,sex,address
FROM `user`
WHERE uid IN (1,10,12,16)
collection: 遍历的集合类型 arrar -> 数组 list -> list  collection -> set
item ; 变量名,可以自定义,表示遍历的每一项的值
open: 开始内容
separator: 分隔符
close: 结束内容
-->

<select id="dynamicSQLForeach" resultType="user">
    select uid,username,birthday,sex,address
      from `user`
      <if test="collection != null">
        <where>
             <foreach collection="collection" item="item" open="uid IN (" separator="," close=")">
                  #{item}
             </foreach>
        </where>
      </if>
</select>
  • mapper接口
public List<User> dynamicSQLForeach(Set<Integer> ids);
  • 测试
@Test
public void dynamicSQLForeach(){
    //获取连接
    SqlSession sqlSession = MyBatisUtils.getSession();
    //获取mapper代理
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    //查询条件
    //数组类型
    //int[] ids = {1,10,12,16};
    
    //list集合类型
    //ArrayList<Integer> list = new ArrayList<>();
    //list.add(1);
    //list.add(10);
    //list.add(12);
    //list.add(16);
    
    //set集合类型
    HashSet<Integer> set = new HashSet<>();
    set.add(1);
    set.add(10);
    set.add(12);
    //调用接口方法
    //List<User> users = mapper.dynamicSQLForeach(ids);
    //List<User> users = mapper.dynamicSQLForeach(list);

    List<User> users = mapper.dynamicSQLForeach(set);
    
    //List<User> users = mapper.dynamicSQLForeach(null);
    //遍历查询结果
    for (User u : users) {
        System.out.println(u);
    }
    
    //关闭会话
    MyBatisUtils.closeSession();
}
7.2.5. set (了解)

set用于update的动态SQL中。

  1. 使用set标签可以将动态的配置 SET 关键字,并剔除追加到条件末尾的任何不相关的逗号。
  2. 使用 if+set 标签修改后,如果某项为 null 则不进行更新,而是保持数据库原值。
  • 遇到的问题

    用户更新信息的时候,可能只填入部分信息,因此编写update语句时需要判断字段是否更新,按照之前只写if判断,动态拼接SQL过程如下:

  • UserMapper.xml

<!--动态sql - update只用if判断的写法-->
<update id="dynamicUpdateUser" >
    UPDATE
    `user`
    SET 
    <if test="username != null and username != ''">
        username = #{username},
    </if>
    <if test="phone != null and phone != ''">
        phone=#{phone},
    </if>
    <if test="sex != null and sex != ''">
        sex = #{sex}
    </if>
    WHERE uid = #{uid}
</update>
  • 测试
/**
  * 测试动态SQL - UPDATE
  */
@Test
public void dynamicSQLUpdate(){
    //获取会话连接
    SqlSession sqlSession = MyBatisUtils.getSession();

    //模拟用户输入的参数
    User user = new User();
    user.setUid(12);
    user.setUsername("蹦迪小王子");
    user.setPhone("13266666666");
    //模拟不更新性别
    // user.setSex("1");
    //完成更新
    sqlSession.update("test.dynamicUpdateUser",user);

    //关闭事务并提交
    MyBatisUtils.commitAndclose();
}
  • 发现问题
  • 当在 update 语句中使用if标签时,如果后面的if没有执行,导致动态拼接的SQL逗号多余错误。
  • 当任何参数都没输入时,动态拼接的SQL多余一个set关键字

当不传入sex参数时,对应的SQL:

  UPDATE `user` SET username = ?,phone = ?, where uid = ?

当任何参数都没输入时,对应的SQL:

  UPDATE `user` where uid = ?
  • 解决方案:
    • 使用 元素
  • 修改UserMapper.xml
<!--动态sql - update只用if判断的写法-->
<update id="dynamicUpdateUser" >
    UPDATE
    `user`
    <set>
        <if test="username != null and username != ''">
            username = #{username},
        </if>
        <if test="phone != null and phone != ''">
            phone=#{phone},
        </if>
        <if test="sex != null and sex != ''">
            sex = #{sex}
        </if>
    </set>
    WHERE uid = #{uid}
</update>
  • 解决了最后一个条件不输入时多余逗号的问题,但是没有解决所有参数都不输入时的SQL语法问题,需要自己在代码中判断,后续使用mybatis的高级功能(通用mapper)这些问题都已经处理了。
7.2.6. trim(了解)

trim 是更灵活用来去处多余关键字的标签,它可以用来实现 where 和 set 的效果。

trim 元素的主要功能是可以在自己包含的内容前加上某些前缀,也可以在其后加上某些后缀,与之对应的属性是 prefix 和 suffix;

也可以把包含内容的首部或尾部的某些内容覆盖,对应的属性是 prefixOverrides 和 suffixOverrides;

因此也可以非常简单的利用 trim 来代替 where 元素的功能。

  • trim 代替 where

    示例:使用动态SQL多条件查询:根据输入的用户名/手机号/地址进行查询

    使用where的写法

    • UserMapper.xml
<select id="dynamicSQLForWhere" resultType="user">
        SELECT
            uid,
            username,
            birthday,
            phone,
            sex,
            address
        FROM `user`
        <where>
            <if test="username != null and username != ''">
                username LIKE #{username}
            </if>
            <if test="phone != null and phone != ''">
                AND phone = #{phone}
            </if>
           <if test="address != null and address != ''">
               AND address LIKE #{address}
           </if>
        </where>
    </select>
  • 测试
@Test
  public void dynamicSQLForWhere(){
  //获取会话连接
  SqlSession sqlSession = MyBatisUtils.getSession();
  
  //模拟用户输入的参数
  User user = new User();
  user.setUsername("%蹦迪%");
  user.setPhone("13266666666");
  user.setAddress("%江苏%");
  //完成更新
  sqlSession.selectList("test.dynamicSQLForWhere",user);
  
  //关闭事务并提交
  MyBatisUtils.commitAndclose();
  }

使用trim替代where的写法

<select id="dynamicSQLForTrim" resultType="user">
      SELECT
      uid,
      username,
      birthday,
      phone,
      sex,
      address
      FROM `user`
      <trim prefix="WHERE" prefixOverrides="AND | OR">
          <if test="username != null and username != ''">
              username LIKE #{username}
          </if>
          <if test="phone != null and phone != ''">
              AND phone = #{phone}
          </if>
          <if test="address != null and address != ''">
              AND address LIKE #{address}
          </if>
      </trim>
  </select>
  • trim 替代set
    如果set后面出现逗号,则将其覆盖

    • 动态更新使用的写法
<!--动态sql - update只用if判断的写法-->
<update id="dynamicUpdateUser" >
    UPDATE
    `user`
    <set>
        <if test="username != null and username != ''">
            username = #{username},
        </if>
        <if test="phone != null and phone != ''">
            phone=#{phone},
        </if>
        <if test="sex != null and sex != ''">
            sex = #{sex}
        </if>
    </set>
    WHERE uid = #{uid}
</update>
  - 使用<trim>的写法
  
  ```xml
  <update id="dynamicUpdateForTrim" >
      UPDATE
      `user`
      <trim prefix="set" suffixOverrides=",">
          <if test="username != null and username != ''">
              username = #{username},
          </if>
          <if test="phone != null and phone != ''">
              phone=#{phone},
          </if>
          <if test="sex != null and sex != ''">
              sex = #{sex}
          </if>
      </trim>
      WHERE uid = #{uid}
  </update>
- 练习

需求:查询女生中生日在1996-10-01之前的用户,按照uid降序返回

  1. 手写SQL ,保证SQL 没问题
  2. 改写SQL片段(statement)
  3. 编写mapper接口对应的方法
  4. 测试
  • UserMapper.xml
<!--多条件查询+排序-->
<select id="multiplySelect" resultType="user">
    SELECT <include refid="baseColumn"/>
    FROM `user`
    <where>
        <if test="sex != null and sex != ''">
            sex = #{sex}
        </if>
        <if test="birthday != null">
            AND birthday <![CDATA[  < ]]> #{birthday}
        </if>
    </where>
    ORDER BY ${columnName} DESC
</select>
  • UserMapper.java
//练习:多条件查询
public List<User> multiplySelect(Map<String,Object> map);
  • 测试
/**
  * 多条件查询
  */
@Test
public void multiplySelect() throws ParseException {
    //使用工具类获取sqlSession
    SqlSession sqlSession = MyBatisUtils.getSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    //模拟接收到的前台参数
    HashMap<String, Object> map = new HashMap<>();
    map.put("sex","2");
    map.put("birthday","1996-10-01");
    map.put("columnName","uid");

    //根据条件查询
    List<User> users = mapper.multiplySelect(map);
    for (User u : users) {
        System.out.println(u);
    }
    //关闭会话
    MyBatisUtils.closeSession();
}
7.3. CDATA
7.3.1. 特殊符号的问题

假如您在 XML 文档中放置了一个类似 “<” 字符,那么这个文档会产生一个错误,这是因为解析器会把它解释为新元素的开始。因此你不能这样写:

AND birthday < #{birthday}

为了避免此类错误,需要把字符 “<” 替换为实体引用,就像这样:

AND birthday &lt; #{birthday}

在 XML 中有 5 个预定义的实体引用:

实体类符号意义
&lt;<小于
&gt;>大于
&amp;&和号
&apos;省略号
&quot;"引号

在 XML 中仅有字符 “<“和”&” 是非法的。省略号、引号和大于号是合法的

7.3.2. CDATA

CDATA 指的是不应由 XML 解析器进行解析的文本数据(Unparsed CHARACTER DATA)。

在 XML 元素中,"<" 和 “&” 是非法的。

“<” 会产生错误,因为解析器会把该字符解释为新元素的开始。

“&” 也会产生错误,因为解析器会把该字符解释为字符实体的开始。

  • CDATA 部分中的所有内容都会被解析器忽略。
  • CDATA 用法 <![CDATA[ 特殊符号 ]]>
  • 等这些标签都不要放入<![CDATA[ ]]>中,否则也不会解析,们只把有特殊字符的语句放进去即可, 尽量缩小 <![CDATA[ ]]> 的范围
 AND birthday <![CDATA[  < ]]> #{birthday}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值