MyBatis笔记(四)——动态SQL元素(if,where,set,trim,choose,when,otherwise,foreach),SQL片段

MyBatis笔记(四)——动态SQL元素(if,where,set,trim,choose,when,otherwise,foreach),SQL片段


参考: https://www.bilibili.com/video/BV1NE411Q7Nx

视频原作者CSDN: https://blog.csdn.net/qq_33369905

这篇是自己整理了一下,以便自己复习。

0.准备

博客表:ID(主键),标题,内容,创建时间,更新时间,浏览量,是否推荐

DROP TABLE IF EXISTS `blog`;
CREATE TABLE `blog` (
  `id` varchar(50) NOT NULL,
  `title` varchar(30) NOT NULL,
  `type` varchar(20) NOT NULL,
  `content` tinytext,
  `create_time` datetime NOT NULL,
  `update_time` datetime NOT NULL,
  `views` int(7) NOT NULL,
  `recommend` tinyint(1) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `blog` VALUES ('1', 'MyBatis学习笔记(一)', 'MyBatis', null, '2020-05-12 16:19:26', '2020-05-12 16:19:32', '20', '');
INSERT INTO `blog` VALUES ('2', 'Spring学习笔记(二)', 'Spring', null, '2020-05-13 16:19:54', '2020-05-13 16:19:57', '39', '');
INSERT INTO `blog` VALUES ('3', 'SpringMVC学习笔记(一)', 'SpringMVC', null, '2020-05-13 16:20:34', '2020-05-12 16:20:34', '19', '');

建立对应的JavaBean Blog

//博客表:ID(主键),标题,内容,创建时间,更新时间,浏览量,是否推荐
public class Blog {
    private String id;
    private String title;
    private String content;
    private Date createTime;
    private Date updateTime;
    private int views;
    private Boolean recommend;
//Lombok @Data
//无参构造/有参构造
//getter/setter
//toString
}

由于有字段:

createTime -> create_time

update_time -> updateTime

开启驼峰命名 MyBatis-confg.xml中添加设置:

注意:settings需要写在properties之后

    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

1.动态SQL

概念:动态SQL就是根据不同的条件生成不同的SQL语句

文档推荐:MyBatis官方文档(中文)-动态SQL⚡️

以下三段文字摘自上述MyBatis官方文档:

动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。

使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。

如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。

  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach

2. if 元素

2.1.if标签 使用场景举例:查询条件是可选项

使用场景举例,博客搜索,用户可以博客文章名称,分类名称,是否推荐三个选项任意组合来查询博客:

比如:

  • 只选择分类和推荐的条件来查询
  • 选择博客标题和推荐的条件来查询

总之就是三个条件都可有可无,这样的查询就需要使用动态SQL来解决了,以往的做法是使用Java代码去判断条件后,选择性的拼接SQL语句,这样做很麻烦。

MyBatis动态SQL场景博客搜索

2.2 编写接口类与对应的mapper.xml

思路分为三步:原始的完整SQL,select基础标签下的完整SQL,添加if标签

2.2.1 首先把完整的SQL写好

测试如图:博客名称搜索关键字 笔记 ,分类为 MyBatis ,不推荐(recommend= 0):

select * from blog where title LIKE '%笔记%' and type = 'MyBatis'  and recommend= 0;

MyBattis动态SQL举例

2.2.2 基础的select标签下的SQL

接口类BlogMapper:

public interface BlogMapper {
    List<Blog> selectBlogIf(Map map);
}

BlogMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.piao.dao.BlogMapper">
    
<select id="selectBlogIf" parameterType="map" resultType="com.piao.pojo.Blog">
 select * from blog where title LIKE #{title} and type = #{type} and recommend=#{recommend};
</select>
    
</mapper>
2.2.3 添加if元素

必选项直接写在where后面,可选项写在if元素中

但这个例子有点特殊,三个都是可选项,所以手动加一个必选项条件 1=1(反正会成立),不加的话where后面可能会直接连接and,sql语法错误。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.piao.dao.BlogMapper">

    <!--需求:
    根据 博客名称 , 分类名称 , 是否推荐 来查询博客!
    map传参的key有title,type,recommend
    -->
    <select id="selectBlogIf" parameterType="map" resultType="com.piao.pojo.Blog">
        select * from blog where 1=1
        <if test="title != null">
            and title LIKE #{title}
        </if>
        <if test="author != null">
            and type = #{type}
        </if>
        <if test="recommend != null">
            and recommend=#{recommend};
        </if>

    </select>

</mapper>

2.3测试if元素

public class BlogMapperTest {
    static SqlSession session;
    static BlogMapper blogMapper;

    //测试前初始化SqlSession 与mapper
    @Before
    public void init() {
        session = MybatisUtils.getSession();//获取sqlsession
        //session.selectList("com.kuang.mapper.UserMapper.selectUser");//获取session中的mapper 方法一
        blogMapper = session.getMapper(BlogMapper.class);//获取session中的mapper 方法二(一般用这个)
    }

    //测试后统一关闭SqlSession
    @After
    public void closeSqlSession() {
        //关闭sqlsession
        session.close();
    }

    //动态SQL map传参的key有title,type,recommend
    @Test
    public void testSelectUser() {
        Map<String, Object> map = new HashMap<>();
        map.put("title","%笔记%");
//        map.put("type","MyBatis");
        map.put("recommend",0);
        List<Blog> blogs = blogMapper.selectBlogIf(map);
        for (Blog blog : blogs) {
            System.out.println(blog);
        }
    }
}

testSelectUser方法只传了title和recommend,即博客名称含有 笔记 两个字的 且没有推荐的博客。

结果如下:

Blog{id='1', title='MyBatis学习笔记(一)', content='内容', createTime=Tue May 12 16:19:26 CST 2020, updateTime=Tue May 12 16:19:32 CST 2020, views=20, commend=false}
Blog{id='3', title='SpringMVC学习笔记(一)', content='内容', createTime=Wed May 13 16:20:34 CST 2020, updateTime=Tue May 12 16:20:34 CST 2020, views=19, commend=false}

3.where元素与set元素,自定义trim元素

3.1where 元素

以上述的if标签的例子进行修改:

<!--    修改前-->   
    <select id="selectBlogIf" parameterType="map" resultType="com.piao.pojo.Blog">
        select * from blog where 1=1
        <if test="title != null">
            and title LIKE #{title}
        </if>
        <if test="author != null">
            and type = #{type}
        </if>
        <if test="recommend != null">
            and recommend=#{recommend};
        </if>
    </select>

<!--    修改后-->  
    <select id="selectBlogWhere" parameterType="map" resultType="com.piao.pojo.Blog">
        select * from blog
        <where>
            <if test="title != null">
                AND title LIKE #{title}
            </if>
            <if test="author != null">
                AND type = #{type}
            </if>
            <if test="recommend != null">
                AND recommend=#{recommend};
            </if>
        </where>
    </select>

可看到,就是把SQL语句的where关键字变成where标签,包裹住后面的if标签而已。

作用:

  • 如果它包含的标签中有返回值的话,它就插入一个‘where’。

比如title为null则没有 title LIKE #{title} 这个条件语句,title,author,recommend只要有一个不为null则会插入where。若三个均为null,则会去掉where。SQL变为select * from blog

  • 如果标签返回的内容是以AND 或OR 开头的,则它会剔除掉。 而if元素没这个功能所以需要where有必选条件。

比如上述title和author为null,而recommend不为null,会自动去掉and,使得SQL变为:

select * from blog where recommend=#{recommend};

3.2 set 元素

和where写在一起,是因为作用基本一致,只是set用于更新语句中而已。

注意set用逗号隔开,而where用and/or隔开

举例:根据id更新博客的标题,分类,是否推荐

接口:

    //set测试 与where类似
    void updateBlogBySet(Map map);

xml:

    <!--  set测试 根据id更新博客 参数均为可选项,使用if元素
    update blog set title = #{title},type = #{type},recommend = #{recommend} where id = #{id};
    -->
    <update id="updateBlogBySet" parameterType="map">
        update blog
        <set>
            <if test="title != null">
                title = #{title},
            </if>
            <if test="type != null">
                type = #{type},
            </if>
            <if test="recommend != null">
                recommend = #{recommend}
            </if>
        </set>
        where id = #{id};
    </update>

测试

    //测试set元素
    @Test
    public void updateBlogBySet() {
        Map<String, Object> map = new HashMap<>();
        map.put("title", "笔记");
        map.put("type", "MyBatis");
        map.put("recommend", 1);
        map.put("id", 1);
        blogMapper.updateBlogBySet(map);
        session.commit();//SqlSession提交事务
    }

3.3自定义trim元素

自定义trim元素 摘自MyBatis官方文档: https://mybatis.org/mybatis-3/zh/dynamic-sql.html

3.3.1 与where元素等价的自定义 trim 元素

如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:

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

prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容。

3.3.2 与 set 元素等价的自定义 trim 元素
<trim prefix="SET" suffixOverrides=",">
  ...
</trim>

注意,我们覆盖了后缀值设置,并且自定义了前缀值。

4. choose元素,配合when和otherwise标签

多个条件中选择一个使用

有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

将上述if标签举例的场景变为:传入了 “title” 就按 “title” 查找,传入了 “type” 就按 “type” 查找的情形。若两者都没有传入,recommend=1的博客(即前两个都没传参数,就返回推荐的博客)。

这个和之前if标签例子区别:

  • 一个是 三个条件任意组合

  • 而这个是 前两个满足一个(且优先是博客名称),否则直接返回推荐的博客

<select id="selectBlogChoose" parameterType="map" resultType="com.piao.pojo.Blog">
        select * from blog where
  <choose>
    <when test="title != null">
       title like #{title}
    </when>
        <when test="type != null">
       type = #{type}
    </when>
    <otherwise>
       recommend = 1;
    </otherwise>
  </choose>
</select>

与Java中swith是类似逻辑,以下是用伪代码表示:

//伪代码  
//choose->switch 
//when->case 
//otherwise -> default 
switch(map参数){
    //以博客名称为优先条件
    case 有博客名) :
       select * from blog where title like #{title};
       break;//不用管后面的了
        
    case (没有博客名称)有分类名称 :
       select * from blog where type = #{type};
       break; //不用管后面的了
        
    ...其他case
    
    default : 
        //前面都不满足,默认执行这个 查询推荐的博客
       select * from blog where recommend = 1;
}

5.foreach元素

动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。

foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符,看它多智能!

提示 你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。

解决SQL 中 in 中遍历的问题,因为通常会传递一个集合,以往要使用Java来遍历改集合中的元素,然后拼接SQL语句。例如下面的SQL语句:传递一个集合{3,5,9}

-- 查询学号为3,5,9的博客的标题,分类,是否推荐
SELECT title,type,recommend FROM blog where id IN(3,5,9);
-- 等价于
SELECT title,type,recommend FROM blog where id =3 OR id =5 OR id =9;
-- 也等价于:SELECT id,name,major FROM blog where 1=1 and (id=3 or id=5 or id=9);

mapper接口:

List<Blog> queryBlogForeach(Map map);

SELECT title,type,recommend FROM blog where id IN(3,5,9);

或者其等价SQL:SELECT title,type,recommend FROM blog where id =3 OR id =5 OR id =9;

(3,5,9)以集合形式保存在map中 key为idList
foreach要做的就是 遍历idList对应的value获取值

foreah使用:

  • collection:指定输入对象中的集合属性 上述例子为 idList
  • item:每次遍历生成的对象取的名称 上述例子中可取名id(自定义取名,后面用#{}获取)
  • open:开始遍历时的拼接字符串 上述例子为 左括号 (
  • close:结束时拼接的字符串 上述例子为 右括号 )
  • separator:遍历对象之间需要拼接的字符串 上述例子为 逗号 ,
    <!--SELECT title,type,recommend FROM blog where id IN(3,5,9);-->
    <select id="selectBlogForeach" parameterType="map" resultType="com.piao.pojo.Blog">
        SELECT title,type,recommend FROM blog
        <where>
            id in
            <foreach collection="idList" item="id" open="(" close=");" separator=",">
                    #{id}
            </foreach>
        </where>
    </select>

<!-- 若采取它的的等价sql:
SELECT title,type,recommend FROM blog where id =3 OR id =5 OR id =9;
那么foreach写法略有不同
-->
    <select id="selectBlogForeach" parameterType="map" resultType="com.piao.pojo.Blog">
        SELECT title,type,recommend FROM blog
        <where>
            <foreach collection="idList" item="id" open="(" close=");" separator="OR">
                id = #{id}
            </foreach>
        </where>
    </select>

测试:

public class BlogMapperTest {
    static SqlSession session;
    static BlogMapper blogMapper;

    //测试前初始化SqlSession 与mapper
    @Before
    public void init() {
        session = MybatisUtils.getSession();//获取sqlsession
        //session.selectList("com.kuang.mapper.UserMapper.selectUser");//获取session中的mapper 方法一
        blogMapper = session.getMapper(BlogMapper.class);//获取session中的mapper 方法二(一般用这个)
    }

    //测试后统一关闭SqlSession
    @After
    public void closeSqlSession() {
        //关闭sqlsession
        session.close();
    }

    @Test
    public void testSelectBlogForeach() {
        HashMap map = new HashMap();
        List<Integer> idList = new ArrayList<>();
        idList.add(3);
        idList.add(5);
        idList.add(9);
        map.put("idList", idList);
        List<Blog> blogs = blogMapper.selectBlogForeach(map);
        
        System.out.println("foreach元素测试");
        //输出 title,type,recommend
        for (Blog blog : blogs) {
            System.out.println("title="+blog.getTitle()+",type="+blog.getRecommend()+",recommend="+blog.getRecommend());
        }

    }
}

日志及输出:因为数据表里只有id为1,2,3的博客,所以只有id为3的数据

DEBUG [main] - ==>  Preparing: SELECT title,type,recommend FROM blog WHERE id in ( ? , ? , ? ); 
DEBUG [main] - ==> Parameters: 3(Integer), 5(Integer), 9(Integer)
TRACE [main] - <==    Columns: title, type, recommend
TRACE [main] - <==        Row: SpringMVC学习笔记(一), SpringMVC, 0
DEBUG [main] - <==      Total: 1
foreach元素测试
title=SpringMVC学习笔记(一),type=false,recommend=false

第二种等价sql测试的日志和输出结果:

DEBUG [main] - ==>  Preparing: SELECT title,type,recommend FROM blog WHERE ( id = ? OR id = ? OR id = ? ); 
DEBUG [main] - ==> Parameters: 3(Integer), 5(Integer), 9(Integer)
TRACE [main] - <==    Columns: title, type, recommend
TRACE [main] - <==        Row: SpringMVC学习笔记(一), SpringMVC, 0
DEBUG [main] - <==      Total: 1
foreach元素测试
title=SpringMVC学习笔记(一),type=false,recommend=false

完整代码可查看: https://gitee.com/lipiaoMKX/SSM-study

拓展 1 trim元素常用场景 insert语句

一般插入多个数据,但都是可选项,使用自定义trim元素包裹最合适,就是

--majorid和majorname都是可选项
INSERT INTO `major`(`majorid`,`majorname`) values(9,'大数据科学');
INSERT INTO `major`(`majorid`,) values(9);
INSERT INTO `major`(`majorname`) values('大数据科学');

可用以下insert元素和trim元素实现:

<insert id="insertBlogByTrim" parameterType="">
       <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="majorid != null">
                majorid,
            </if>
            <if test="majorname != null">
                majorname,
            </if>
        </trim>
        <trim prefix="values (" suffix=")" suffixOverrides=",">
            <if test="majorid != null">
                #{adminUserId},
            </if>
            <if test="majorname != null">
                #{loginUserName},
            </if>
        </trim>
</insert>  

拓展2 SQL片段

在***Mapper.xml中可用sql元素提取重复使用的sql片段,在需要复用的地方使用include元素引用即可,类似于c/c++头文件的意思。

  • sql元素使用 id 来唯一标识该sql片段
  • include元素中使用 refid 来指定引用的sql片段

例如以下代码:

  <sql id="Base_Column_List">
    category_id, category_name, category_icon, category_rank, is_deleted, create_time
  </sql>

  <select id="findCategoryList" parameterType="Map" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List"/>
        from tb_blog_category
        where is_deleted=0
        order by category_rank desc,create_time desc
        <if test="start!=null and limit!=null">
            limit #{start},#{limit}
        </if>
 </select>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章