MyBatis06-动态SQL

动态SQL

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

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

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

  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach
    我们之前写的 SQL 语句都比较简单,如果有比较复杂的业务,我们需要写复杂的 SQL 语句,往往需要拼接,而拼接 SQL ,稍微不注意,由于引号,空格等缺失可能都会导致错误。

那么怎么去解决这个问题呢?这就要使用 mybatis 动态SQL,通过 if, choose, when, otherwise, trim, where, set, foreach等标签,可组合成非常灵活的SQL语句,从而在提高 SQL 语句的准确性的同时,也大大提高了开发人员的效率。

== 搭建环境==
新建一个数据库表:blog
字段:id,title,author,create_time,views

CREATE TABLE `blog`(
`id` varchar(50) NOT NULL COMMENT `博客id`,
`title`varchar(100) NOT  NULL COMMENT`博客标题`
`author` varchar(30) NOT NULL COMMENT `博客作者`,
`create_time` datetime NOT NULL COMMENT`创建时间`,
`views` int (30)NOT NULL COMMENT `浏览量`
)ENGINE=InnoDB DEFAULT CHARSET=utf-8

1、创建Mybatis基础工程

package com.liu.utils;

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;

//sqlSessionFactory --> sqlSession
public class MybatisUtils {
    private static SqlSessionFactory sqlSessionFactory;

    static{
        try {
            //使用Mybatis第一步:获取sqlSessionFactory对象
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
    public static SqlSession  getSqlSession(){
        return sqlSessionFactory.openSession(true);
    }

}

2、IDutil工具类
public class IDutil{
public static String genId(){
public static
}
}
3、实体类编写

import java.util.Date;

public class Blog {

   private String id;
   private String title;
   private String author;
   private Date createTime;
   private int views;
   //set,get....
}

4、编写Mapper接口及xml文件

public interface BlogMapper {
//新增一个博客
int addBlog(Blog blog);
}
<?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.liu.mapper.BlogMapper">

</mapper>

5、mybatis核心配置文件,下划线驼峰自动转换

<settings>
   <setting name="mapUnderscoreToCamelCase" value="true"/>
   <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!--注册Mapper.xml-->
<mappers>
 <mapper class="com.liu.mapper.BlogMapper"/>
</mappers>

6、插入初始数据
sql配置文件

<insert id="addBlog" parameterType="blog">
  insert into blog (id, title, author, create_time, views)
  values (#{id},#{title},#{author},#{createTime},#{views});
</insert>

创建blog类

@Test
public void addInitBlog(){
   SqlSession session = MybatisUtils.getSession();
   BlogMapper mapper = session.getMapper(BlogMapper.class);

   Blog blog = new Blog();
   blog.setId(IDUtil.genId());
   blog.setTitle("Mybatis如此简单");
   blog.setAuthor("六六");
   blog.setCreateTime(new Date());
   blog.setViews(9999);

   mapper.addBlog(blog);

   blog.setId(IDUtil.genId());
   blog.setTitle("Java如此简单");
   mapper.addBlog(blog);

   blog.setId(IDUtil.genId());
   blog.setTitle("Spring如此简单");
   mapper.addBlog(blog);

   blog.setId(IDUtil.genId());
   blog.setTitle("哈哈哈哈");
   mapper.addBlog(blog);
  session.commit();
   session.close();
}

== if语句==

需求:根据作者名字和博客名字来查询博客!如果作者名字为空,那么只根据博客名字查询,反之,则根据作者名来查询

1、编写接口类

//需求1
List<Blog> queryBlogIf(Map map);

2、编写SQL语句

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

3、测试

@Test
public void testQueryBlogIf(){
  SqlSession session=MybatisUtils.getSession();
  BlogMapper mapper=session.getMapper(BlogMapper.class);
  HashMap<String,String>map=new HashMap<String,String>();
  map.put("title","Mybatis如此简单");
  map.put("author","狂神说");
  List<Blog> blogs=mapper.queryBlogIf(map);
   System.out.println(blogs);

   session.close();

}

这样写其实是有错误的,如果author等于null,那么查询语句为select * from user where title=#{title},但是当title为空呐?就变成了 select * from user where and author=#{author},这是错误的SQL语句,用where可以解决

== Where==
修改上面的SQL语句:

<select id="queryBlogIf" parameterType="map" resultType="blog">
  select * from blog
   <where>
      <if test="title != null">
         title =#{title}
       </if>
       <if test="author !=null">
          and author =#{author}
        </if>
   </where>
</select>
choose、when、otherwise

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

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

<select id="findActiveBlogLike"
     resultType="Blog">
     SELECT * FROM BLOG WHERE state = ‘ACTIVE’
     <choose>
        <when test="title != null">
          AND title like #{title}
        </when>
       <when test="author != null and author.name != null">
      AND author_name like #{author.name}
       </when>
       <otherwise>
      AND featured = 1
    </otherwise>
  </choose>
</select>

这种情况是必须得选择一个 and 但是如果我们需要0个或者1个或者多个的情况应该怎么办?

trim、where、set
<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG
  <where>
    <if test="state != null">
         state = #{state}
    </if>
    <if test="title != null">
        AND title like #{title}
    </if>
    <if test="author != null and author.name != null">
        AND author_name like #{author.name}
    </if>
  </where>
</select>

where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:

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

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

用于动态更新语句的类似解决方案叫做 set。set 元素可以用于动态包含需要更新的列,忽略其它不更新的列。比如:

<update id="updateAuthorIfNecessary">
  update Author
    <set>
      <if test="username != null">username=#{username},</if>
      <if test="password != null">password=#{password},</if>
      <if test="email != null">email=#{email},</if>
      <if test="bio != null">bio=#{bio}</if>
    </set>
  where id=#{id}
</update>

这个例子中,set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。

来看看与 set 元素等价的自定义 trim 元素吧:

<trim prefix="SET" suffixOverrides=",">
  ...
</trim>
示例:

== set==
同理,上面的对于查询 SQL 语句包含 where 关键字,如果在进行更新操作的时候,含有 set 关键词,我们怎么处理呢?
1、编写接口方法

int updateBlog(Map map);

2、sql配置文件

注意set是用的逗号
<update id="updateBlog" parameterType="map">
   update blog
      <set>
          <if test="title != null">
             title=#{title},
          </if>
          <if test="author != null">
             author  =#{author}
           </if>
         </set>
where id = #{id};
</update>           

3、测试

@Test
public void testUpdateBlog(){
   SqlSession session = MybatisUtils.getSession();
   BlogMapper mapper = session.getMapper(BlogMapper.class);

   HashMap<String, String> map = new HashMap<String, String>();
   map.put("title","卫庄");
   map.put("author","盖聂");
   map.put("id","9d6a763f5e1347cebda43e2a32687a77");

   mapper.updateBlog(map);
   session.close();
}

== choose语句==
有时候,我们不想用到所有的查询条件,只想选择其中的一个,查询条件有一个满足即可,使用 choose 标签可以解决此类问题,类似于 Java 的 switch 语句
1、编写接口方法

List <Blog> queryBlogChoose(Map map);

2、sql配置文件

<select id="queryBlogChoose" parameterType="map" resultType="blog">
   select * from  blog
   <where>
     <choose>
        <when test="title != null">
           title =#{title}
         </when>
         <when test="author != null">
           and author =#{author}
          </when>
          <otherwise>
              and views = #{views}
           </otherwise>
       </choose>
   </where>
</select>

3、测试类

@Test
public void testQueryBlogChoose(){
   SqlSession session = MybatisUtils.getSession();
   BlogMapper mapper = session.getMapper(BlogMapper.class);

   HashMap<String, Object> map = new HashMap<String, Object>();
   map.put("title","百步飞剑");
   map.put("author","横贯八方");
   map.put("views",9999);
   List<Blog> blogs = mapper.queryBlogChoose(map);
   System.out.println(blogs);
   session.close();
}

== SQL片段==
有时候可能某个 sql 语句我们用的特别多,为了增加代码的重用性,简化代码,我们需要将这些代码抽取出来,然后使用时直接调用。

提取SQL片段
<sql id="if-title-author">
   <if test="title != null">
      title = #{title}
   </if>
   <if test="author != null">
      and author = #{author}
   </if>
</sql>

引用SQL片段:

<select id="queryBlogIf" parameterType="map" resultType="blog">
   select * from blog
     <where>
     <!-- 引用 sql 片段,如果refid 指定的不在本文件中,那么需要在前面加上 namespace -->
     <include refid="if-title-author"></include>
       <!-- 在这里还可以引用其他的 sql 片段 -->
   </where>
</select>

注意:
1、最好基于单表来定义sql片段,提高片段的可重用性
2、在sql片段中不要包括where

== Foreach==
将数据库中前三个数据的id修改为1,2,3;
需求:我们需要查询blog表中id分别为1,2,3的博客信息
1、编写接口

List<Blog> queryBlogForeach(Map map);

2、编写SQL语句

<select id="queryBlogForeach" parameterType="map" resultType="blog">
  select * from blog
   <where>
       <!--
       collection:指定输入对象中的集合属性
       item:每次遍历生成的对象
       open:开始遍历时的拼接字符串
       close:结束时拼接的字符串
       separator:遍历对象之间需要拼接的字符串
       select * from blog where 1=1 and (id=1 or id=2 or id=3)
     -->
       <foreach collection="ids"  item="id" open="and (" close=")" separator="or">
          id=#{id}
       </foreach>
   </where>
</select>

3、测试

@Test
public void testQueryBlogForeach(){
   SqlSession session = MybatisUtils.getSession();
   BlogMapper mapper = session.getMapper(BlogMapper.class);

   HashMap map = new HashMap();
   List<Integer> ids = new ArrayList<Integer>();
   ids.add(1);
   ids.add(2);
   ids.add(3);
   map.put("ids",ids);

   List<Blog> blogs = mapper.queryBlogForeach(map);

   System.out.println(blogs);

   session.close();
}

动态SQL在开发中大量的使用,一定要熟练掌握!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值