【MyBatis学习总结 (五),动态SQL】

在之前的四篇文章中,我们已经学了MyBatis不少东西,剩下能简单学的也不多了, MyBatis的学习也渐渐接近尾声。

以下是之前文章的地址

(MyBatis学习总结(四),注解 & 多对一、一对多)

(MyBatis学习总结(三),ResultMap & 日志、分页)

(MyBatis学习总结(二),MyBatis实现CURD & MyBatis常用配置涉及)

MyBatis学习总结(一),初步理解MyBatis & 简单实现

动态SQL这个点,我们作为MyBatis学习的压轴部分,写在倒数第二篇文章中。

动态SQL也不是三言两语能够讲清楚的,需要一定的篇幅,所以本篇文章只写动态SQL。

动态SQL

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

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

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

在涉及动态sql之前我们先搭建一下环境

搭建动态sql环境

创建表

create table `blog` (
	`id` varchar(50) not null comment '博客id',
    `title` varchar(100) not null comment '博客标题',
    `author` varchar(30) not null comment '作者',
    `createTime` datetime not null comment '创建时间',
    `views` int(30) not null comment '浏览量' 
) engine=InnoDB default charset=utf8;

创建完表后,我们不再使用sqlyog来直接插入数据,折腾一下使用Java代码来插入数据

编写实体类

public class Blog {
    private String id;
    private String title;
    private String author;
    private Date createTime;
    private int views;
}
// 省略了getter、setter、toString方法,和构造方法

编写mapper接口

public interface BlogMapper {
// 在mapper接口中写对应的添加数据方法
    int addBlog (Blog blog);
}

编写对应的mapper.xml文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.molu.mapper.BlogMapper">
<insert id="addBlog" parameterType="Blog">
    insert into mybatis.blog (id,title,author,createTime,views)
    values (#{id},#{title},#{author},#{createTime},#{views});
</insert>
</mapper>

我们再写一个工具类,使用UUID来生成随机的 blogId

工具类

public class BlogGetId {
    public static String getId(){
        return UUID.randomUUID().toString().replaceAll("-","");
    }
}

在测试类中插入数据

public class MapperTest {
    @Test
    public void addBlogTest(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
        // 插入数据
        mapper.addBlog(new Blog(BlogGetId.getId(),"Java入门","moluu",new Date(),9999));
        mapper.addBlog(new Blog(BlogGetId.getId(),"MySQl入门","lin",new Date(),1000));
        mapper.addBlog(new Blog(BlogGetId.getId(),"Mybatis入门","moluu",new Date(),9999));
        mapper.addBlog(new Blog(BlogGetId.getId(),"Spring入门","lin",new Date(),9999));
        // 进行增删改操作,一定要记得提交事务。否则插入不了
        sqlSession.commit();
        sqlSession.close();
    }
}

插入成功

如果插入不成功的话检查一下配置文件是否有对其进行绑定,mapper.xml中的sql是否写得有问题,是否漏掉了提交........

到这里环境就准备好了

简单使用

对于一些复杂的查询,我们可能会指定多个查询条件,但是这些条件可能存在也可能不存在,如果不使用持久层框架我们可能需要自己拼接SQL语句,十分的痛苦。不过MyBatis提供了动态SQL的功能来解决这个问题。

IF

我们先用一用最简单的if

<mapper namespace="com.molu.mapper.BlogMapper">
<insert id="addBlog" parameterType="Blog">
    insert into mybatis.blog (id,title,author,createTime,views)
    values (#{id},#{title},#{author},#{createTime},#{views});
    <!-- if 一般定义在 crud标签体中-->
    <if test="">
        
    </if>
</insert>
</mapper>

现在我们想要实现一个功能,如果我们不对查询条件进行限制则返回所有的博客,如果我们对条件进行限制 则返回对应条件的博客。

使用 if实现该功能

  • 在mapper接口中写对应的方法
public interface BlogMapper {
// 根据条件返回查询结果
    List<Blog> queryBlogIF(Map map);
}
  • 修改mapper.xml文件
<select id="queryBlogIF" parameterType="map" resultType="blog">
    select * from mybatis.blog where 1=1

    <if test="title != null">
        and title = #{title};
    </if>
    <if test="author != null">
        and author = #{author};
    </if>
</select>

我们使用了 if标签,对查询 sql进行了简单的条件追加。

  • 编写测试类
public class MapperTest {
    @Test
    public void queryBlogIFTest(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
        HashMap map = new HashMap();
        List<Blog> blogs = mapper.queryBlogIF(map);
        for (Blog blog : blogs) {
            System.out.println(blog);
        }
        sqlSession.close();
    }
}

我们往查询代码中传入了一个空map,也就是不追加任何条件。

由于我们在 where子句写了个 1=1 的条件,该条件是一定能够被满足的,执行后自然会返回所有的博客。

  • 尝试往 map中 put值(追加条件)
public class MapperTest {
    @Test
    public void queryBlogIFTest(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
        HashMap map = new HashMap();
        // 现在 title有值了
        map.put("title","Java入门");
        List<Blog> blogs = mapper.queryBlogIF(map);
        for (Blog blog : blogs) {
            System.out.println(blog);
        }
        sqlSession.close();
    }
}

可以看到,在追加条件后 会返回对应条件的数据。

也就是通过 if标签,在 test条件满足时 往where子句中追加了条件。这就是最简单的动态sql的使用

<if test="title != null">
    <!--test条件也很简单,title有值就满足了-->
	and title = #{title};
    <!--进而对 where子句进行sql的拼接,即追加条件-->
</if>

我们再玩一玩 if

现在我们查询一下 author为 ‘lin’ 的博客,也很简单,往 map中 put一下 author的值即可。

//map.put("title","Java入门");
map.put("author","lin");

查询结果:

很实用也很简单的一个标签不是么?

Where

where标签能够使我们的sql拼接时,避免一些错误

在正常的开发中,我们是不会在where子句后面写什么 1=1 的,上面我只是为了方便演示偷了个懒才那么写

正常的话,应该是要将 1=1去掉的:

<select id="queryBlogIF" parameterType="map" resultType="blog">
    select * from mybatis.blog where
    <if test="title != null">
        and title = #{title};
    </if>
    <if test="author != null">
        and author = #{author};
    </if>
</select>

写成这样的话,if 标签再拼接就有问题了,我们配置一下日志实现,方便查看拼接的sql

<settings>
    <!--方便起见,我们就不配log4j了-->
	<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

现在我们再运行一下测试类

在 where后面拼接了一个and,直接导致sql语句错误。

实际上还不怎么好彻底解决这个问题,毕竟不是每次都使用一个条件。and自然是必不可少的。

MyBatis 有一个简单且适合大多数场景的解决办法。而在其他场景中,可以对其进行自定义以符合需求。而这,只需要一处简单的改动。

where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。

也就是说,我们只需要用一个where标签将 if标签包裹,就能够避免在 where后拼接 and的问题,最重要的是不需要对我们的拼接sql进行任何改动。

<mapper namespace="com.molu.mapper.BlogMapper">
<select id="queryBlogIF" parameterType="map" resultType="blog">
    select * from mybatis.blog
    <where >
        <if test="title != null">
            and title = #{title};
        </if>
        <if test="author != null">
            and author = #{author};
        </if>
    </where>
</select>
</mapper>

再次运行测试类

可以看到,我们拼接sql开头的and,在运行后直接就被去掉了,从根本上避免了上面的问题。

如果我们不追加条件where是会被自动去掉的,也就实现了 不追加条件则返回全部博客的功能

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

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

如果where标签没办法解决你的问题,你还可以使用 trim标签来改变 where标签的规则,进行一些简单的自定义操作。这个就不再展开了,感兴趣可以尝试一下。

choose、when、otherwise

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

还是上面的例子,但是策略变为:传入了 “title” 就按 “title” 查找,传入了 “author” 就按 “author” 查找的情形。若两者都没有传入,就返回浏览量为 9999 的 Blog(这可能是管理员认为,与其返回大量的无意义随机 Blog,还不如返回一些由管理员精选的 Blog)。

我们来实现一下,首先修改mapper.xml

<mapper namespace="com.molu.mapper.BlogMapper">
    <select id="queryBlogChoose" parameterType="map" resultType="blog">
        select * from mybatis.blog
        <where>
            <choose>
                <when test="title != null">
                    and title = #{title}
                </when>
                <when test="author != null">
                    and author = #{author}
                </when>
                <otherwise>
                    views = 9999
                </otherwise>
            </choose>
        </where>
    </select>
</mapper>

写成这样后,就不存在 不追加条件返回全部博客了。它至少会满足一个条件的,即便我们什么条件都不追加,他也会将 otherwise标签中的views = 9999拼接上,也就实现了与其返回大量的无意义随机 Blog,还不如返回一些由管理员精选的 Blog

尝试不追加条件

尝试将条件全部追加

条件全部追加它只会执行最初满足的条件,类似Java 中的 switch 语句。所以就不会存在什么条件互斥的问题,它只会满足一个条件,也就是第一个满足的条件。

set

set 类似于where,它也是用来避免我们拼接sql时出错的

set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号。因为我们进行sql拼接时,很难避免这个 “ , ” 的问题。

我们写一个修改操作

  • 首先在mapper接口中写对应的方法
public interface BlogMapper {
    // 根据id修改博客
    int updateBlog(Map map);
}
  • 修改 mapper.xml文件
<mapper namespace="com.molu.mapper.BlogMapper">
    <update id="updateBlog" parameterType="map">
        update mybatis.blog
        <set>
            <if test="title != null">
                title = #{title},
            </if>
            <if test="author != null">
                author = #{author},
            </if>
        </set>
        where id = #{id};
    </update>
</mapper>

看到这样一个拼接sql,我们很明显能够感觉到会出错,逗号明显多了一个,最后它会拼接成:

update mybatis.bolg set title = #{title},author = #{author}, where id = #{id};

这样的sql按道理是没办法执行的,但如果是在set标签中,那就无所谓了。

  • 测试类
    @Test
    public void updateBlogTest(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
        HashMap map = new HashMap();
        map.put("title","Web入门");
        map.put("author","lin");
        map.put("id","7280ac58a49040818f97cf3d59e598dd");
        mapper.updateBlog(map);
        sqlSession.commit();
        sqlSession.close();
    }
  • 测试 结果:

set 和 where一样,你也可以使用 trim标签来改变 set标签的规则,进行一些简单的自定义操作。

SQL片段

我们在演示上面这些标签的时候,有一些sql语句频繁被用到,我们可以使用 sql标签将他抽取出来。实现 sql的复用。

比如这几行sql,基本上一直在用。

<if test="title != null">
 	title = #{title}
</if>
<if test="author != null">
	author = #{author}
</if>

我们使用 sql标签将他提取出来,取一个比较符合的id。

<sql id="TA_sql">
	<if test="title != null">
		title = #{title}
	</if>
	<if test="author != null">
		author != #{author}
	</if>
</sql>

之后我们需要用到这段 sql的时候,可以使用 include标签将其引用过来。

    <select id="queryBlog" parameterType="map" resultType="blog">
        select * from mybatis.blog
        <where>
            <include refid="TA_sql"></include>
        </where>
    </select>

测试类

    @Test
    public void queryBlogTest(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
        HashMap map = new HashMap();
        map.put("title","Web入门");
        List<Blog> blogs = mapper.queryBlog(map);
        for (Blog blog : blogs) {
            System.out.println(blog);
        }
        sqlSession.close();
    }

测试结果:

可以看到,通过这种引用sql片段的方式,也能够达我们想要的效果 且更省事。

foreach

foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符,看它多智能!提示 你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。

看这些比较官方的文字还是看不出个所以然,我们仍然通过案例来理解。

既然要遍历集合,那我们的UUID就不适用了,修改成1 2 3 4吧

  • 在 mapper接口中写我们对应的方法
public interface BlogMapper {
    List<Blog> queryBlogForeach(Map map);
}
  • 编写mapper.xml文件
<mapper namespace="com.molu.mapper.BlogMapper">
    <select id="queryBlogForeach" resultType="blog" parameterType="map">
        select * from mybatis.blog
        <where>
            <!--使用了 foreach标签-->
            <foreach collection="Fids" open="(" separator="or" close=")" item="Fid">
                id = #{Fid}
            </foreach>
        </where>
    </select>
</mapper>
  • 测试类
    @Test
    public void queryBlogTest(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
        HashMap map = new HashMap();
        ArrayList Fids = new ArrayList();
        Fids.add(1);
        Fids.add(2);
        map.put("Fids",Fids);
        mapper.queryBlogForeach(map);
        sqlSession.close();
    }
  • 测试结果:

到这里大致应该能猜 foreach标签到底做了什么

其实 foreach标签做的事情很简单,无非也是拼接sql,只是在拼接前 执行了一个遍历集合的操作。

 <!--使用了 foreach标签-->
            <foreach collection="Fids" item="Fid" open="(" separator="or" close=")" >
                id = #{Fid}
            </foreach>

如果我们要实现通过传入的 id来查询指定的博客功能,sql语句应该写成这样:

select * from mybatis.blog where 1=1 and(id = #{id} or id = #{id} or .....)

这样写的话,怎么看怎么不舒服。一来是写死了,有局限性 ,二来这样写sql 不免让人觉得有点low…

而我们通过 foreach标签的话可以做到动态的传入指定的 id。

foreach的属性着手

  • collection,表示要遍历的集合名字,也就是我们要在测试类中要创建的 list
  • item,表示遍历出来的每一个元素的名字,也就是我们要写在下面条件中的 参数(Fid)
  • open,表示拼接 sql的起始
  • separator,表示拼接 sql时使用的分隔符
  • close,表示拼接 sql的结尾

看完这些你应该对foreach标签有了一定的了解。

也就是说,我们通过map传入的list集合,会被 foreach标签进行遍历

遍历出来的每一个元素 都会被作为条件传入到我们的 where子句后面。

由于我们使用了 where标签,所以哪怕我们的集合中什么都没有,sql也是能够执行成功的。

如果集合中有元素,该元素就会被foreach 标签拿去拼接。

我们又对open、separator close、进行了定义,那么拼接进去的sql 也就不会出问题了。

每个传入的元素之间会使用 or 进行分隔,起始和结尾是一对括号,也就实现了 ( id = #{Fid} or id = #{iFd} or …)的拼接。

所谓的的动态SQL本质还是SQL语句,只是我们可以在SQL层面,执行一些逻辑代码

动态SQL就是在拼接SQL语句,我们只需要保证SQL的正确性,按照SQL的格式想办法对其进行排列组合就可以了


放松一下眼睛

原图地址

画师主页


©️2020 CSDN 皮肤主题: 游动-白 设计师:上身试试 返回首页