zoukankan      html  css  js  c++  java
  • mybatis之动态SQL

        对于一些复杂的查询,我们可能会指定多个查询条件,但是这些条件可能存在也可能不存在,例如在58同城上面找房子,我们可能会指定面积、楼层和所在位置来查找房源,也可能会指定面积、价格、户型和所在位置来查找房源,此时就需要根据用户指定的条件动态生成SQL语句。如果不使用持久层框架我们可能需要自己拼装SQL语句,还好MyBatis提供了动态SQL的功能来解决这个问题。MyBatis中用于实现动态SQL的元素主要有:

    - if

    - choose (when, otherwise)

    - trim (where, set)

    - foreach

    由于每次建立工程比较复杂,可以参考第一节:mybatis入门来搭建一个简单的工程,然后来测试本节内容。

    1、动态sql参数传递方式


    当我们关注动态sql的时候,其实要查询的参数不止一个,这个时候,我们通常有如下几个选择:

    1. 通过Param注解,在方法中使用多个参数
    2. 使用POJO或者TO
    3. 使用map

    下面测试的时候,将分别介绍,首先构造一个场景,我们按照age和address进行查询。因此mapper接口方法为:

    public interface PersonMapper
    {
        List<Person> getPersonByParam(@Param("age") Integer age, @Param("address") String address);
        List<Person> getPersonByPOJO(Person person);
        List<Person> getPersonByMap(Map<String,Object> map);
    }

    编写好mapper接口之后,再来编写mapper映射文件。上面三个方法虽然参数形式不同,但是下面select里面除了id不同,其它都是相同的。

    <mapper namespace="com.yefengyu.mybatis.mapper.PersonMapper">
        <select id="getPersonByParam" resultType="com.yefengyu.mybatis.entity.Person">
            select id, first_name firstName, last_name lastName, age, email, address  from person where age > #{age} and address = #{address}
        </select>
        <select id="getPersonByPOJO" resultType="com.yefengyu.mybatis.entity.Person">
            select id, first_name firstName, last_name lastName, age, email, address  from person where age > #{age} and address = #{address}
        </select>
        <select id="getPersonByMap" resultType="com.yefengyu.mybatis.entity.Person">
            select id, first_name firstName, last_name lastName, age, email, address  from person where age > #{age} and address = #{address}
        </select>
    </mapper>

    下面进行测试:

    package com.yefengyu.mybatis;
    
    import com.yefengyu.mybatis.mapper.PersonMapper;
    import com.yefengyu.mybatis.entity.Person;
    import org.apache.ibatis.io.Resources;
    import org.apache.ibatis.session.SqlSession;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.apache.ibatis.session.SqlSessionFactoryBuilder;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    
    public class Main
    {
        public static void main(String[] args)
            throws IOException
        {
            InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
            SqlSession sqlSession = sqlSessionFactory.openSession();
            PersonMapper mapper = sqlSession.getMapper(PersonMapper.class);
    
            //注解形式参数
             List<Person> personByParam = mapper.getPersonByParam(20, "beijing");
            System.out.println(personByParam.size());
    
            //POJO形式参数
             Person person = new Person();
            person.setAge(20);
            person.setAddress("beijing");
            List<Person> personByPOJO = mapper.getPersonByPOJO(person);
            System.out.println(personByPOJO.size());
    
            //map形式参数
             Map<String, Object> map = new HashMap<>();
            map.put("age",20);
            map.put("address","beijing");
            List<Person> personByMap = mapper.getPersonByMap(map);
            System.out.println(personByMap.size());
    
            sqlSession.close();
        }
    }

        上面的代码中三种参数传递中都使用age和address两个参数作为过滤条件,因此查询都没有问题。但是现在我们将上面代码修改一下,不传address的值,也就是不使用address作为过滤条件,只使用age作为过滤条件。

    1、mapper.getPersonByParam(20, null);

    2、注释此句://person.setAddress("beijing");

    3、注释此句://map.put("address","beijing");

    //注解形式参数
    List<Person> personByParam = mapper.getPersonByParam(20, null);
    System.out.println(personByParam.size());
    
    //POJO形式参数
    Person person = new Person();
    person.setAge(20);
    //person.setAddress("beijing");
    List<Person> personByPOJO = mapper.getPersonByPOJO(person);
    System.out.println(personByPOJO.size());
    
    //map形式参数
    Map<String, Object> map = new HashMap<>();
    map.put("age",20);
    //map.put("address","beijing");
    List<Person> personByMap = mapper.getPersonByMap(map);
    System.out.println(personByMap.size());

        我们期望如果不传address值的时候,将不再使用address作为过滤条件,但是查询结果却是0条数据,这是因为sql中 address = null 也作为查询条件,自然查询不到期望的结果。那么怎么实现我们的需求呢?

    2、if


    我们可以使用if标签来解决上面的问题。

        <select id="getPersonByPOJO" resultType="com.yefengyu.mybatis.entity.Person">
            select id, first_name firstName, last_name lastName, age, email, address from person
            where
            <if test="age!=null">
                age > #{age}
            </if>
            <if test="address!=null and address!=''">
                and address = #{address}
            </if>
        </select>

    上面使用if标签,如果age不为null,就把age > #{age} 拼接到where之后,同理,如果address的条件满足,也会拼接在后面。

    有4种情况(✔表示传递此属性,❌表示不传递此属性):

    序号 age address sql
    1 select id, first_name firstName, last_name lastName, age, email, address from person where age > ? and address = ?
    2 select id, first_name firstName, last_name lastName, age, email, address from person where age > ?
    3 select id, first_name firstName, last_name lastName, age, email, address from person where  and address = ?
    4 select id, first_name firstName, last_name lastName, age, email, address from person where

    可以看出后两种有明显的问题,简单的解决之法是在where后面添加 1=1,第一个if条件前面加and,如下:

        <select id="getPersonByPOJO" resultType="com.yefengyu.mybatis.entity.Person">
            select id, first_name firstName, last_name lastName, age, email, address from person
            where 1=1
            <if test="age!=null">
                and age > #{age}
            </if>
            <if test="address!=null and address!=''">
                and address = #{address}
            </if>
        </select>
    这个时候4种情况对应的sql为,可以正常查询数据。
    序号 age address sql
    1 select id, first_name firstName, last_name lastName, age, email, address from person where 1=1 and age > ? and address = ?
    2 select id, first_name firstName, last_name lastName, age, email, address from person where 1=1 and  age > ?
    3 select id, first_name firstName, last_name lastName, age, email, address from person where 1=1 and address = ?
    4 select id, first_name firstName, last_name lastName, age, email, address from person where  1=1

    借助1=1可以解决sql拼接出现的一系列问题,但是mybatis自身也提供了更强大的标签来解决这些问题。

    3、where


    使用where标签可以解决上面sql拼接出现的一系列问题,在使用的时候,不能同时在sql中书写where关键字。使用的时候,只需要将if判断包含在where标签里面即可。

    <select id="getPersonByPOJO" resultType="com.yefengyu.mybatis.entity.Person">
        select id, first_name firstName, last_name lastName, age, email, address from person
        <where>
            <if test="age!=null">
                age > #{age}
            </if>
            <if test="address!=null and address!=''">
                and address = #{address}
            </if>
        </where>
    </select>

    where 标签只会在至少有一个子元素的条件返回 SQL 子句的情况下才去插入“WHERE”子句。(解决第四条)

    而且,若语句的开头为“AND”或“OR”,where 标签也会将它们去除。(解决第三条)

    序号 age address sql
    1 select id, first_name firstName, last_name lastName, age, email, address from person where  and age > ? and address = ?
    2 select id, first_name firstName, last_name lastName, age, email, address from person where  age > ?
    3 select id, first_name firstName, last_name lastName, age, email, address from person where address = ?
    4 select id, first_name firstName, last_name lastName, age, email, address from person

    4、set


    该标签主要用在更新操作里面,如果传递了某个属性,就更新对应的字段。此处假如要根据id更新age或者address,传入哪个属性就更新哪个属性,那么使用动态sql就是如下的写法。

    <update id="updatePerson">
        update person
        <set>
            <if test="age!=null">
                age = #{age},
            </if>
            <if test="address!=null and address!=''">
                address = #{address}
            </if>
        </set>
        where id = #{id}
    </update>

    此处讲解一下为什么需要使用set标签:

    如果去掉set标签,在第一个if标签前面加一个 set 关键字,如下,同样会出现上面那种sql拼接问题:

    • 只传入age,where前面将多一个逗号。
    • 如果两个都不传,where 前面将有一个set关键字。
    • 如果都传则正常拼接sql
    • 只传address也正常拼接sql
    <update id="updatePerson">
        update person set
        <if test="age!=null">
            age = #{age},
        </if>
        <if test="address!=null and address!=''">
            address = #{address}
        </if>
        where id = #{id}
    </update>

    这里,set 标签会动态前置 SET 关键字,同时也会删掉无关的逗号,因为用了条件语句之后很可能就会在生成的 SQL 语句的后面留下这些逗号。因为用的是“if”标签,若最后一个“if”没有匹配上而前面的匹配上,SQL 语句的最后就会有一个逗号遗留。

    5、trim


    trim标签功能比较强大,它的使用方式是:

    <trim prefix="" prefixOverrides="" suffix="" suffixOverrides="">
        ......    
    </trim>

    trim标签有四个属性,可以随机组合使用,分别表示:

    • prefix:在包围的语句前面加上前缀
    • prefixOverrides:在包围的语句最前面移除所有指定在 prefixOverrides 属性中的内容
    • suffix: 在包围的语句后面加上后缀
    • suffixOverrides:在包围的语句最后面移除所有指定在 suffixOverrides 属性中的内容

    where标签等价于:注意此例中的空格也是必要的(AND |OR )。

    <trim prefix="WHERE" prefixOverrides="AND |OR ">
      ...
    </trim>

    set标签等价于:

    <trim prefix="SET" suffixOverrides=",">
      ...
    </trim>

    trim和where、set标签是同一类标签。

    6、choose, when, otherwise


        有时我们不想应用到所有的条件语句,而只想从中择其一项。针对这种情况,MyBatis 提供了 choose 标签,它有点像 Java 中的 switch 语句。现在我们想如果提供了age,就按照age条件过滤,不再关注其它条件。如果没有提供age,如果提供了address,就按照address条件过滤,其它即使有十个条件都满足也不过滤。

    <select id="getPersonByPOJO" resultType="com.yefengyu.mybatis.entity.Person">
        select id, first_name firstName, last_name lastName, age, email, address from person where
        <choose>
            <when test="age!=null">
                age = #{age}
            </when>
            <when test="address!=null and address!=''">
                address = #{address}
            </when>
            <otherwise>
                1=1
            </otherwise>
        </choose>
    </select>

    注意:

    1、where关键字是需要手写的。

    2、choose包裹着不同的查询条件,when类似if功能,判断条件是否满足。

    3、每个过滤条件前面不用加and,因为只有一条过滤条件会被执行。

    4、最后可以加个otherwise,所有条件不满足时会执行此处过滤条件。

  • 相关阅读:
    sql -- 获取商品分类的最新销售情况
    sql -- 获取连续签到的用户列表
    sql -- 利用order by 排名作弊
    sql -- update表子查询、多条件判断case when
    sql-- 找到重复数据并删除、有重复数据不插入或更新的处理方法
    sql--自链接(推荐人)
    sql--测试商品的重要度,是否需要及时补货
    sql面试题
    TCP/IP 3次握手
    REST和SOAP
  • 原文地址:https://www.cnblogs.com/ye-feng-yu/p/11007088.html
Copyright © 2011-2022 走看看